Allow rules to manage Scarpet apps (Scarpet Rules) (#519)

by altrisi
This commit is contained in:
altrisi authored and GitHub committed 2020-11-02 21:52:21 -04:00
1 parent 5c76723091
commit b9eee6e58b
12 files changed
+165 -25

No files matched your search

+1
View File
@@ -469,6 +469,7 @@ Available options in the scarpet app space:
* `world_seed` - a numeric seed of the world
* `world_path` - full path to the world saves folder
* `world_folder` - name of the direct folder in the saves that holds world files
* `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_change(rule, newValue)` event.
Relevant gameplay related properties
* `game_difficulty` - current difficulty of the game: `'peacefu'`, `'easy'`, `'normal'`, or `'hard'`
+3
View File
@@ -44,6 +44,9 @@ at the same time.
Triggered right after a lightning strikes. Lightning entity as well as potential horseman trap would
already be spawned at that point. `mode` is `true` if the lightning did cause a trap to spawn.
### `__on_carpet_rule_changes(rule, new_value)`
Triggered when a Carpet rule is changed. It includes extension rules, which will be namespaced as `namespace:rule`.
## Player events
These are triggered with a player context. For apps with a 'player' scope, they trigger once for the appropriate
+1 -1
View File
@@ -84,7 +84,7 @@ public class CarpetServer implements ClientModInitializer,DedicatedServerModInit
public static void onServerLoadedWorlds(MinecraftServer minecraftServer)
{
extensions.forEach(e -> e.onServerLoadedWorlds(minecraftServer));
scriptServer.loadAllWorldScripts();
scriptServer.initializeForWorld();
}
public static void tick(MinecraftServer server)
@@ -244,13 +244,13 @@ public class ScriptCommand
suggests( (cc, bb) -> suggestMatching(CarpetServer.scriptServer.listAvailableModules(true),bb)).
executes((cc) ->
{
boolean success = CarpetServer.scriptServer.addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), true, false);
boolean success = CarpetServer.scriptServer.addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), true, false, false);
return success?1:0;
}).
then(literal("global").
executes((cc) ->
{
boolean success = CarpetServer.scriptServer.addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), false, false);
boolean success = CarpetServer.scriptServer.addScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), false, false, false);
return success?1:0;
}
)
@@ -258,10 +258,10 @@ public class ScriptCommand
);
LiteralArgumentBuilder<ServerCommandSource> f = literal("unload").requires( (player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandScriptACE) ).
then(argument("app", StringArgumentType.word()).
suggests( (cc, bb) -> suggestMatching(CarpetServer.scriptServer.modules.keySet(),bb)).
suggests( (cc, bb) -> suggestMatching(CarpetServer.scriptServer.unloadableModules,bb)).
executes((cc) ->
{
boolean success =CarpetServer.scriptServer.removeScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), true);
boolean success =CarpetServer.scriptServer.removeScriptHost(cc.getSource(), StringArgumentType.getString(cc, "app"), true, false);
return success?1:0;
}));
@@ -30,7 +30,7 @@ public abstract class ArmorStandEntity_scarpetMarkerMixin extends LivingEntity
@Inject(method = "readCustomDataFromTag", at = @At("HEAD"))
private void checkScarpetMarkerUnloaded(CallbackInfo ci)
{
if ((CarpetSettings.scriptsAutoload || !CarpetSettings.commandScript.equals("false")) && !world.isClient)
if (!world.isClient)
{
if (getScoreboardTags().contains(Auxiliary.MARKER_STRING))
{
@@ -11,6 +11,7 @@ import carpet.script.value.NBTSerializableValue;
import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
import carpet.settings.ParsedRule;
import carpet.utils.Messenger;
import net.minecraft.block.BlockState;
import net.minecraft.entity.EntityType;
@@ -653,6 +654,25 @@ public class CarpetEventServer
);
}
};
public static final Event CARPET_RULE_CHANGES = new Event("carpet_rule_changes", 2, true)
{
@Override
public void onCarpetRuleChanges(ParsedRule<?> rule, ServerCommandSource source)
{
String identifier = rule.settingsManager.getIdentifier();
final String namespace;
if (!identifier.equals("carpet"))
{
namespace = identifier+":";
} else { namespace = "";}
handler.call(
() -> Arrays.asList(
new StringValue(namespace+rule.name),
new StringValue(rule.getAsString())
), () -> source
);
}
};
public static String getLoadEvent(EntityType<? extends Entity> et)
{
@@ -721,6 +741,7 @@ public class CarpetEventServer
public void onWorldEvent(ServerWorld world, BlockPos pos) { }
public void onWorldEventFlag(ServerWorld world, BlockPos pos, int flag) { }
public void onCarpetRuleChanges(ParsedRule<?> rule, ServerCommandSource source) { }
}
@@ -26,6 +26,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -41,18 +42,31 @@ public class CarpetScriptServer
public final MinecraftServer server;
public CarpetScriptHost globalHost;
public Map<String, CarpetScriptHost> modules;
public Set<String> unloadableModules;
public long tickStart;
public boolean stopAll;
private Set<String> holyMoly;
public CarpetEventServer events;
private static final List<Module> bundledModuleData = new ArrayList<>();
private static final List<Module> ruleModuleData = new ArrayList<>();
public static void registerBuiltInScript(BundledModule app)
{
bundledModuleData.add(app);
}
/**
* Registers a Scarpet App to be used as a Rule App
* (to be controlled with the value of a Carpet rule)
*
* @param app is the BundledModule of an app. Libraries
* should be registered as builtInScripts instead
*/
public static void registerRuleScript(BundledModule app) {
ruleModuleData.add(app);
}
static
{
registerBuiltInScript(BundledModule.carpetNative("camera", false));
@@ -75,20 +89,27 @@ public class CarpetScriptServer
ScriptHost.systemGlobals.clear();
events = new CarpetEventServer(server);
modules = new HashMap<>();
unloadableModules = new HashSet<String>();
tickStart = 0L;
stopAll = false;
holyMoly = server.getCommandManager().getDispatcher().getRoot().getChildren().stream().map(CommandNode::getName).collect(Collectors.toSet());
globalHost = CarpetScriptHost.create(this, null, false, null);
}
public void loadAllWorldScripts()
public void initializeForWorld()
{
CarpetServer.settingsManager.initializeScarpetRules();
CarpetServer.extensions.forEach(e -> {
if (e.customSettingsManager() != null) {
e.customSettingsManager().initializeScarpetRules();
}
});
if (CarpetSettings.scriptsAutoload)
{
Messenger.m(server.getCommandSource(), "Auto-loading world scarpet apps");
for (String moduleName: listAvailableModules(false))
{
addScriptHost(server.getCommandSource(), moduleName, true, true);
addScriptHost(server.getCommandSource(), moduleName, true, true, false);
}
}
@@ -120,6 +141,18 @@ public class CarpetScriptServer
return null;
}
public Module getRuleModule(String name)
{
for (Module moduleData : ruleModuleData)
{
if (moduleData.getName().equalsIgnoreCase(name))
{
return moduleData;
}
}
return null;
}
public List<String> listAvailableModules(boolean includeBuiltIns)
{
List<String> moduleNames = new ArrayList<>();
@@ -152,17 +185,18 @@ public class CarpetScriptServer
return modules.get(name);
}
public boolean addScriptHost(ServerCommandSource source, String name, boolean perPlayer, boolean autoload)
public boolean addScriptHost(ServerCommandSource source, String name, boolean perPlayer, boolean autoload, boolean isRuleApp)
{
//TODO add per player modules to support player actions better on a server
name = name.toLowerCase(Locale.ROOT);
boolean reload = false;
if (modules.containsKey(name))
{
removeScriptHost(source, name, false);
if (isRuleApp) return false;
removeScriptHost(source, name, false, isRuleApp);
reload = true;
}
Module module = getModule(name, false);
Module module = isRuleApp ? getRuleModule(name) : getModule(name, false);
if (module == null)
{
Messenger.m(source, "r Failed to add "+name+" app");
@@ -194,18 +228,19 @@ public class CarpetScriptServer
}
modules.put(name, newHost);
if (!isRuleApp) unloadableModules.add(name);
if (autoload && !newHost.persistenceRequired)
{
removeScriptHost(source, name, false);
removeScriptHost(source, name, false, false);
return false;
}
//addEvents(source, name);
addLegacyCommand(source, name, reload); // this needs to be moved to config reader, only supporting legacy command here
addLegacyCommand(source, name, reload, !isRuleApp); // this needs to be moved to config reader, only supporting legacy command here
return true;
}
private void addLegacyCommand(ServerCommandSource source, String hostName, boolean isReload)
private void addLegacyCommand(ServerCommandSource source, String hostName, boolean isReload, boolean notifySource)
{
CarpetScriptHost host = modules.get(hostName);
String loaded = isReload?"reloaded":"loaded";
@@ -215,7 +250,7 @@ public class CarpetScriptServer
}
if (host.getFunction("__command") == null)
{
Messenger.m(source, "gi "+hostName+" app "+loaded+".");
if (notifySource) Messenger.m(source, "gi "+hostName+" app "+loaded+".");
return;
}
if (holyMoly.contains(hostName))
@@ -261,7 +296,7 @@ public class CarpetScriptServer
})));
}
}
Messenger.m(source, "gi "+hostName+" app "+loaded+" with /"+hostName+" command");
if (notifySource) Messenger.m(source, "gi "+hostName+" app "+loaded+" with /"+hostName+" command");
server.getCommandManager().getDispatcher().register(command);
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
}
@@ -308,10 +343,10 @@ public class CarpetScriptServer
public boolean removeScriptHost(ServerCommandSource source, String name, boolean notifySource)
public boolean removeScriptHost(ServerCommandSource source, String name, boolean notifySource, boolean isRuleApp)
{
name = name.toLowerCase(Locale.ROOT);
if (!modules.containsKey(name))
if (!modules.containsKey(name) || (!isRuleApp && !unloadableModules.contains(name)))
{
if (notifySource) Messenger.m(source, "r No such app found: ", "wb " + name);
return false;
@@ -320,6 +355,7 @@ public class CarpetScriptServer
events.removeAllHostEvents(name);
modules.get(name).onClose();
modules.remove(name);
if (!isRuleApp) unloadableModules.remove(name);
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
if (notifySource) Messenger.m(source, "gi Removed "+name+" app");
return true;
@@ -367,9 +403,9 @@ public class CarpetScriptServer
{
Map<String, Boolean> apps = new HashMap<>();
modules.forEach((s, h) -> apps.put(s, h.perUser));
apps.keySet().forEach(s -> removeScriptHost(server.getCommandSource(), s, false));
apps.keySet().forEach(s -> removeScriptHost(server.getCommandSource(), s, false, false));
events.clearAll();
init();
apps.forEach((s, pp) -> addScriptHost(server.getCommandSource(), s, pp, false));
apps.forEach((s, pp) -> addScriptHost(server.getCommandSource(), s, pp, false, false));
}
}
@@ -1,5 +1,6 @@
package carpet.script.utils;
import carpet.CarpetServer;
import carpet.script.CarpetContext;
import carpet.script.CarpetScriptHost;
import carpet.script.value.ListValue;
@@ -7,11 +8,14 @@ import carpet.script.value.MapValue;
import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
import carpet.settings.ParsedRule;
import carpet.settings.SettingsManager;
import com.sun.management.OperatingSystemMXBean;
import net.minecraft.util.WorldSavePath;
import java.lang.management.ManagementFactory;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -102,6 +106,23 @@ public class SystemInfo {
OperatingSystemMXBean.class);
return new NumericValue(osBean.getProcessCpuLoad());
});
put("world_carpet_rules", c -> {
Collection<ParsedRule<?>> rules = CarpetServer.settingsManager.getRules();
MapValue carpetRules = new MapValue(Collections.emptyList());
rules.forEach(rule -> {
carpetRules.put(new StringValue(rule.name), new StringValue(rule.getAsString()));
});
CarpetServer.extensions.forEach(e -> {
SettingsManager manager = e.customSettingsManager();
if (manager == null) return;
Collection<ParsedRule<?>> extensionRules = manager.getRules();
extensionRules.forEach(rule -> {
carpetRules.put(new StringValue(manager.getIdentifier()+":"+rule.name), new StringValue(rule.getAsString()));
});
});
return carpetRules;
});
+15 -4
View File
@@ -4,6 +4,7 @@ import carpet.CarpetServer;
import carpet.utils.Translations;
import carpet.utils.Messenger;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import net.minecraft.server.command.ServerCommandSource;
import java.lang.reflect.Constructor;
@@ -12,6 +13,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import static carpet.utils.Translations.tr;
@@ -19,6 +21,7 @@ public final class ParsedRule<T> implements Comparable<ParsedRule> {
public final Field field;
public final String name;
public final String description;
public final String scarpetApp;
public final ImmutableList<String> extraInfo;
public final ImmutableList<String> categories;
public final ImmutableList<String> options;
@@ -28,8 +31,10 @@ public final class ParsedRule<T> implements Comparable<ParsedRule> {
public final List<Validator<T>> validators;
public final T defaultValue;
public final String defaultAsString;
public final SettingsManager settingsManager;
private static final Set<Class<?>> NUMBER_CLASSES = Sets.newHashSet(byte.class, short.class, int.class, long.class, float.class, double.class);
ParsedRule(Field field, Rule rule)
ParsedRule(Field field, Rule rule, SettingsManager settingsManager)
{
this.field = field;
this.name = rule.name().isEmpty() ? field.getName() : rule.name();
@@ -38,6 +43,8 @@ public final class ParsedRule<T> implements Comparable<ParsedRule> {
this.isStrict = rule.strict();
this.extraInfo = ImmutableList.copyOf(rule.extra());
this.categories = ImmutableList.copyOf(rule.category());
this.scarpetApp = rule.scarpetApp();
this.settingsManager = settingsManager;
this.validators = new ArrayList<>();
for (Class v : rule.validate())
this.validators.add((Validator<T>) callConstructor(v));
@@ -50,6 +57,10 @@ public final class ParsedRule<T> implements Comparable<ParsedRule> {
this.validators.add((Validator<T>) callConstructor(Validator._COMMAND_LEVEL_VALIDATOR.class));
}
}
if (!scarpetApp.isEmpty())
{
this.validators.add((Validator<T>) callConstructor(Validator._SCARPET.class));
}
this.isClient = categories.contains(RuleCategory.CLIENT);
if (this.isClient)
{
@@ -105,7 +116,7 @@ public final class ParsedRule<T> implements Comparable<ParsedRule> {
public ParsedRule<T> set(ServerCommandSource source, String value)
{
if (CarpetServer.settingsManager != null && CarpetServer.settingsManager.locked)
if (settingsManager != null && settingsManager.locked)
return null;
if (type == String.class)
{
@@ -156,7 +167,7 @@ public final class ParsedRule<T> implements Comparable<ParsedRule> {
if (!value.equals(get()) || source == null)
{
this.field.set(null, value);
if (source != null) CarpetServer.settingsManager.notifyRuleChanged(source, this, stringValue);
if (source != null) settingsManager.notifyRuleChanged(source, this, stringValue);
}
}
catch (IllegalAccessException e)
@@ -187,7 +198,7 @@ public final class ParsedRule<T> implements Comparable<ParsedRule> {
public boolean getBoolValue()
{
if (type == boolean.class) return (Boolean) get();
if (type.isAssignableFrom(Number.class)) return ((Number) get()).doubleValue() > 0;
if (NUMBER_CLASSES.contains(type)) return ((Number) get()).doubleValue() > 0;
return false;
}
+8
View File
@@ -52,6 +52,14 @@ public @interface Rule
*/
boolean strict() default true;
/**
* If specified, the rule will automatically enable or disable
* a builtin Scarpet Rule App with this name.
* Consider telling the rule name so users can edit globals
* (in case there are relevant globals to edit ofc)
*/
String scarpetApp() default "";
/**
* The class of the validator checked when the rule is changed.
*/
@@ -43,6 +43,7 @@ import java.util.TreeMap;
import java.util.stream.Collectors;
import static carpet.utils.Translations.tr;
import static carpet.script.CarpetEventServer.Event.CARPET_RULE_CHANGES;
import static net.minecraft.command.CommandSource.suggestMatching;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
@@ -71,6 +72,9 @@ public class SettingsManager
this.fancyName = fancyName;
}
public String getIdentifier() {
return identifier;
}
public void attachServer(MinecraftServer server)
{
@@ -90,7 +94,7 @@ public class SettingsManager
{
Rule rule = f.getAnnotation(Rule.class);
if (rule == null) continue;
ParsedRule parsed = new ParsedRule(f, rule);
ParsedRule parsed = new ParsedRule(f, rule, this);
rules.put(parsed.name, parsed);
}
}
@@ -104,6 +108,30 @@ public class SettingsManager
{
observers.forEach(observer -> observer.accept(source, rule, userTypedValue));
ServerNetworkHandler.updateRuleWithConnectedClients(rule);
switchScarpetRule(source, rule);
CARPET_RULE_CHANGES.onCarpetRuleChanges(rule, source);
}
void switchScarpetRule(ServerCommandSource source, ParsedRule<?> rule)
{
if (!rule.scarpetApp.isEmpty())
{
if (rule.getBoolValue() || (rule.type == String.class && !rule.get().equals("false")))
{
CarpetServer.scriptServer.addScriptHost(source, rule.scarpetApp, false, false, true);
} else {
CarpetServer.scriptServer.removeScriptHost(source, rule.scarpetApp, false, true);
}
}
}
public void initializeScarpetRules() {
for (ParsedRule<?> rule : rules.values())
{
if (!rule.scarpetApp.isEmpty()) {
switchScarpetRule(server.getCommandSource(), rule);
}
}
}
public Iterable<String> getCategories()
@@ -58,6 +58,17 @@ public abstract class Validator<T>
public String description() { return "Can be limited to 'ops' only, or a custom permission level";}
}
public static class _SCARPET<T> extends Validator<T> {
@Override
public T validate(ServerCommandSource source, ParsedRule<T> currentRule, T newValue, String string)
{
return newValue;
}
public String description() {
return "It controls an accompanying Scarpet App";
}
}
public static class WIP<T> extends Validator<T>
{
@Override