diff --git a/core/src/main/java/tc/oc/pgm/action/ActionModule.java b/core/src/main/java/tc/oc/pgm/action/ActionModule.java index 9b6984b032..002ed2d0aa 100644 --- a/core/src/main/java/tc/oc/pgm/action/ActionModule.java +++ b/core/src/main/java/tc/oc/pgm/action/ActionModule.java @@ -2,6 +2,7 @@ import com.google.common.collect.ImmutableList; import java.util.Collection; +import java.util.Set; import java.util.logging.Logger; import org.jdom2.Document; import org.jdom2.Element; @@ -15,6 +16,7 @@ import tc.oc.pgm.api.module.exception.ModuleLoadException; import tc.oc.pgm.filters.FilterMatchModule; import tc.oc.pgm.util.xml.InvalidXMLException; +import tc.oc.pgm.util.xml.XMLUtils; import tc.oc.pgm.variables.VariablesModule; public class ActionModule implements MapModule { @@ -57,17 +59,14 @@ public ActionModule parse(MapFactory factory, Logger logger, Document doc) throws InvalidXMLException { ActionParser parser = new ActionParser(factory); - for (Element actions : doc.getRootElement().getChildren("actions")) { - for (Element action : actions.getChildren()) { - if (parser.isAction(action)) parser.parse(action, null); - } + for (Element action : + XMLUtils.flattenElements(doc.getRootElement(), Set.of("actions"), parser.actionTypes())) { + parser.parse(action, null); } ImmutableList.Builder> triggers = ImmutableList.builder(); - for (Element actions : doc.getRootElement().getChildren("actions")) { - for (Element rule : actions.getChildren("trigger")) { - triggers.add(parser.parseTrigger(rule)); - } + for (Element rule : XMLUtils.flattenElements(doc.getRootElement(), "actions", "trigger")) { + triggers.add(parser.parseTrigger(rule)); } return new ActionModule(triggers.build()); diff --git a/core/src/main/java/tc/oc/pgm/action/ActionParser.java b/core/src/main/java/tc/oc/pgm/action/ActionParser.java index fc9f8b5697..c7d239fc69 100644 --- a/core/src/main/java/tc/oc/pgm/action/ActionParser.java +++ b/core/src/main/java/tc/oc/pgm/action/ActionParser.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; import net.kyori.adventure.title.Title; @@ -36,12 +37,16 @@ import tc.oc.pgm.api.feature.FeatureValidation; import tc.oc.pgm.api.filter.Filter; import tc.oc.pgm.api.filter.Filterables; +import tc.oc.pgm.api.filter.query.PartyQuery; +import tc.oc.pgm.api.map.MapProtos; import tc.oc.pgm.api.map.factory.MapFactory; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.features.FeatureDefinitionContext; import tc.oc.pgm.features.XMLFeatureReference; import tc.oc.pgm.filters.Filterable; import tc.oc.pgm.filters.matcher.StaticFilter; +import tc.oc.pgm.filters.matcher.player.ParticipatingFilter; +import tc.oc.pgm.filters.operator.AllFilter; import tc.oc.pgm.filters.parse.DynamicFilterValidation; import tc.oc.pgm.filters.parse.FilterParser; import tc.oc.pgm.kits.Kit; @@ -58,7 +63,7 @@ import tc.oc.pgm.util.xml.InvalidXMLException; import tc.oc.pgm.util.xml.Node; import tc.oc.pgm.util.xml.XMLUtils; -import tc.oc.pgm.variables.VariableDefinition; +import tc.oc.pgm.variables.Variable; import tc.oc.pgm.variables.VariablesModule; public class ActionParser { @@ -66,6 +71,7 @@ public class ActionParser { private static final NumberFormat DEFAULT_FORMAT = NumberFormat.getIntegerInstance(); private final MapFactory factory; + private final boolean legacy; private final FeatureDefinitionContext features; private final FilterParser filters; private final RegionParser regions; @@ -74,6 +80,7 @@ public class ActionParser { public ActionParser(MapFactory factory) { this.factory = factory; + this.legacy = !factory.getProto().isNoOlderThan(MapProtos.ACTION_REVAMP); this.features = factory.getFeatures(); this.filters = factory.getFilters(); this.regions = factory.getRegions(); @@ -106,8 +113,8 @@ public > Action parse(Element el, @Nullable C return result; } - public boolean isAction(Element el) { - return getParserFor(el) != null; + public Set actionTypes() { + return methodParsers.keySet(); } private boolean maybeReference(Element el) { @@ -175,14 +182,6 @@ private > Action parseDynamic(Element el, Class } } - public > Trigger parseTrigger(Element el) throws InvalidXMLException { - Class cls = Filterables.parse(Node.fromRequiredAttr(el, "scope")); - return new Trigger<>( - cls, - filters.parseRequiredProperty(el, "filter", DynamicFilterValidation.of(cls)), - parseProperty(Node.fromRequiredChildOrAttr(el, "action", "trigger"), cls)); - } - private > Class parseScope(Element el, Class scope) throws InvalidXMLException { return parseScope(el, scope, "scope"); @@ -199,9 +198,32 @@ private > Class parseScope(Element el, Class scope return scope; } - @MethodParser("action") - public > ActionNode parseAction(Element el, Class scope) + private > boolean includeObs(Element el, Class scope) throws InvalidXMLException { + return PartyQuery.class.isAssignableFrom(scope) + || XMLUtils.parseBoolean(el.getAttribute("observers"), legacy); + } + + private Filter wrapFilter(Filter outer, boolean includeObs) { + if (includeObs || outer == StaticFilter.DENY) return outer; + if (outer == StaticFilter.ALLOW) return ParticipatingFilter.PARTICIPATING; + return AllFilter.of(outer, ParticipatingFilter.PARTICIPATING); + } + + // Parser for elements + public > Trigger parseTrigger(Element el) throws InvalidXMLException { + Class cls = Filterables.parse(Node.fromRequiredAttr(el, "scope")); + return new Trigger<>( + cls, + wrapFilter( + filters.parseRequiredProperty(el, "filter", DynamicFilterValidation.of(cls)), + includeObs(el, cls)), + parseProperty(Node.fromRequiredChildOrAttr(el, "action", "trigger"), cls)); + } + + // Generic action with N children parser + public > ActionNode parseAction( + Element el, Class scope, boolean includeObs) throws InvalidXMLException { scope = parseScope(el, scope); ImmutableList.Builder> builder = ImmutableList.builder(); @@ -209,19 +231,30 @@ public > ActionNode parseAction(Element el, C builder.add(parse(child, scope)); } - Filter filter = filters.parseFilterProperty(el, "filter", StaticFilter.ALLOW); - Filter untriggerFilter = filters.parseFilterProperty(el, "untrigger-filter", StaticFilter.DENY); + Filter filter = + wrapFilter(filters.parseFilterProperty(el, "filter", StaticFilter.ALLOW), includeObs); + Filter untriggerFilter = wrapFilter( + filters.parseFilterProperty( + el, "untrigger-filter", legacy ? StaticFilter.DENY : StaticFilter.ALLOW), + includeObs); return new ActionNode<>(builder.build(), filter, untriggerFilter, scope); } + // Parsers + @MethodParser("action") + public > ActionNode parseAction(Element el, Class scope) + throws InvalidXMLException { + return parseAction(el, scope, true); + } + @MethodParser("switch-scope") public , I extends Filterable> Action parseSwitchScope( Element el, Class outer) throws InvalidXMLException { outer = parseScope(el, outer, "outer"); Class inner = parseScope(el, null, "inner"); - ActionDefinition child = parseAction(el, inner); + ActionDefinition child = parseAction(el, inner, includeObs(el, inner)); Action result = ScopeSwitchAction.of(child, outer, inner); if (result == null) { @@ -307,27 +340,30 @@ public SoundAction parseSoundAction(Element el, Class scope) throws InvalidXM @MethodParser("set") public > SetVariableAction parseSetVariable(Element el, Class scope) throws InvalidXMLException { - VariableDefinition var = - features.resolve(Node.fromRequiredAttr(el, "var"), VariableDefinition.class); + var node = Node.fromRequiredAttr(el, "var"); + Variable var = features.resolve(node, Variable.class); scope = parseScope(el, scope); if (!Filterables.isAssignable(scope, var.getScope())) throw new InvalidXMLException( "Wrong variable scope for '" - + var.getId() + + node.getValue() + "', expected " + var.getScope().getSimpleName() + " which cannot be found in " + scope.getSimpleName(), el); + if (var.isReadonly()) + throw new InvalidXMLException("You may not use a read-only variable in set", el); + Formula formula = Formula.of(Node.fromRequiredAttr(el, "value").getValue(), variables.getContext(scope)); - if (var.isIndexed()) { + if (var.isIndexed() && var instanceof Variable.Indexed indexedVar) { Formula idx = Formula.of(Node.fromRequiredAttr(el, "index").getValue(), variables.getContext(scope)); - return new SetVariableAction.Indexed<>(scope, var, idx, formula); + return new SetVariableAction.Indexed<>(scope, indexedVar, idx, formula); } return new SetVariableAction<>(scope, var, formula); @@ -411,6 +447,7 @@ public Action parseTeleport(Element el, Class scope) @MethodParser("paste-structure") public > PasteStructureAction parseStructure( Element el, Class scope) throws InvalidXMLException { + scope = parseScope(el, scope); Formula xFormula = Formula.of(Node.fromRequiredAttr(el, "x").getValue(), variables.getContext(scope)); Formula yFormula = @@ -418,7 +455,7 @@ public > PasteStructureAction parseStructure( Formula zFormula = Formula.of(Node.fromRequiredAttr(el, "z").getValue(), variables.getContext(scope)); - XMLFeatureReference structure = + var structure = features.createReference(Node.fromRequiredAttr(el, "structure"), StructureDefinition.class); return new PasteStructureAction<>(scope, xFormula, yFormula, zFormula, structure); diff --git a/core/src/main/java/tc/oc/pgm/action/actions/SetVariableAction.java b/core/src/main/java/tc/oc/pgm/action/actions/SetVariableAction.java index b22a04d7fb..5b350fd9fe 100644 --- a/core/src/main/java/tc/oc/pgm/action/actions/SetVariableAction.java +++ b/core/src/main/java/tc/oc/pgm/action/actions/SetVariableAction.java @@ -2,15 +2,14 @@ import tc.oc.pgm.filters.Filterable; import tc.oc.pgm.util.math.Formula; -import tc.oc.pgm.variables.VariableDefinition; -import tc.oc.pgm.variables.types.IndexedVariable; +import tc.oc.pgm.variables.Variable; public class SetVariableAction> extends AbstractAction { - protected final VariableDefinition variable; + protected final Variable variable; protected final Formula formula; - public SetVariableAction(Class scope, VariableDefinition variable, Formula formula) { + public SetVariableAction(Class scope, Variable variable, Formula formula) { super(scope); this.variable = variable; this.formula = formula; @@ -18,7 +17,7 @@ public SetVariableAction(Class scope, VariableDefinition variable, Formula @Override public void trigger(T t) { - variable.getVariable(t.getMatch()).setValue(t, formula.applyAsDouble(t)); + variable.setValue(t, formula.applyAsDouble(t)); } public static class Indexed> extends SetVariableAction { @@ -26,7 +25,7 @@ public static class Indexed> extends SetVariableAction idx; public Indexed( - Class scope, VariableDefinition variable, Formula idx, Formula formula) { + Class scope, Variable.Indexed variable, Formula idx, Formula formula) { super(scope, variable, formula); this.idx = idx; } @@ -34,7 +33,7 @@ public Indexed( @Override @SuppressWarnings("unchecked") public void trigger(T t) { - ((IndexedVariable) variable.getVariable(t.getMatch())) + ((Variable.Indexed) variable) .setValue(t, (int) idx.applyAsDouble(t), formula.applyAsDouble(t)); } } diff --git a/core/src/main/java/tc/oc/pgm/api/filter/ReactorFactory.java b/core/src/main/java/tc/oc/pgm/api/filter/ReactorFactory.java index 8076dbb60b..78b62e3fa9 100644 --- a/core/src/main/java/tc/oc/pgm/api/filter/ReactorFactory.java +++ b/core/src/main/java/tc/oc/pgm/api/filter/ReactorFactory.java @@ -3,6 +3,7 @@ import org.bukkit.event.Event; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.match.event.MatchLoadEvent; +import tc.oc.pgm.features.StateHolder; import tc.oc.pgm.filters.FilterMatchModule; import tc.oc.pgm.filters.Filterable; @@ -11,7 +12,12 @@ * properly invalidate {@link Filterable}s. A filter can theoretically both depend on listening to * some events AND have a reactor, but this is rarely the case. */ -public interface ReactorFactory extends FilterDefinition { +public interface ReactorFactory + extends FilterDefinition, StateHolder { + + default void register(Match match, FilterMatchModule fmm) { + match.getFeatureContext().registerState(this, createReactor(match, fmm)); + } /** * Get an instance of this filter's reactor. This will only be called once per match and always @@ -23,9 +29,9 @@ public interface ReactorFactory extends Filter R createReactor(Match match, FilterMatchModule fmm); /** - * A match scoped singleton responsible for match time invalidation of filterables that the {@link - * ReactorFactory} that created this might have changed its opinion about. This is created at the - * end of match load. + * A match scoped singleton responsible for match time invalidation of filterables that the + * {@link ReactorFactory} that created this might have changed its opinion about. This is created + * at the end of match load. * * @see FilterMatchModule#onMatchLoad(MatchLoadEvent) */ diff --git a/core/src/main/java/tc/oc/pgm/api/filter/query/MatchQuery.java b/core/src/main/java/tc/oc/pgm/api/filter/query/MatchQuery.java index 71868658d1..8086ab8229 100644 --- a/core/src/main/java/tc/oc/pgm/api/filter/query/MatchQuery.java +++ b/core/src/main/java/tc/oc/pgm/api/filter/query/MatchQuery.java @@ -2,10 +2,9 @@ import java.util.Optional; import org.jetbrains.annotations.Nullable; -import tc.oc.pgm.api.filter.ReactorFactory; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.match.MatchModule; -import tc.oc.pgm.filters.FilterMatchModule; +import tc.oc.pgm.features.StateHolder; import tc.oc.pgm.filters.Filterable; public interface MatchQuery extends Query { @@ -27,8 +26,8 @@ default Optional moduleOptional(Class cls) { return Optional.ofNullable(getMatch().getModule(cls)); } - default T reactor(ReactorFactory factory) { - return getMatch().needModule(FilterMatchModule.class).getReactor(factory); + default T state(StateHolder factory) { + return getMatch().getFeatureContext().getState(factory); } @Nullable diff --git a/core/src/main/java/tc/oc/pgm/api/map/MapProtos.java b/core/src/main/java/tc/oc/pgm/api/map/MapProtos.java index 871fe0eec3..255152fbd8 100644 --- a/core/src/main/java/tc/oc/pgm/api/map/MapProtos.java +++ b/core/src/main/java/tc/oc/pgm/api/map/MapProtos.java @@ -40,4 +40,10 @@ public interface MapProtos { // Various changes to support dynamic filters Version DYNAMIC_FILTERS = new Version(1, 4, 2); + + // Make several singletons have built-in default ids + Version FEATURE_SINGLETON_IDS = new Version(1, 5, 0); + + // Several fixes to actions & scopes + Version ACTION_REVAMP = new Version(1, 5, 0); } diff --git a/core/src/main/java/tc/oc/pgm/command/MapDevCommand.java b/core/src/main/java/tc/oc/pgm/command/MapDevCommand.java index 6c1ceb583b..9091bd7239 100644 --- a/core/src/main/java/tc/oc/pgm/command/MapDevCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/MapDevCommand.java @@ -4,8 +4,8 @@ import static net.kyori.adventure.text.Component.text; import static tc.oc.pgm.command.util.ParserConstants.CURRENT; -import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.IntStream; import net.kyori.adventure.text.Component; @@ -20,13 +20,12 @@ import org.incendo.cloud.annotations.Permission; import tc.oc.pgm.api.Permissions; import tc.oc.pgm.api.filter.Filter; -import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.util.Audience; import tc.oc.pgm.util.PrettyPaginatedComponentResults; import tc.oc.pgm.util.text.TextFormatter; import tc.oc.pgm.variables.Variable; -import tc.oc.pgm.variables.types.IndexedVariable; +import tc.oc.pgm.variables.VariablesMatchModule; public class MapDevCommand { @@ -39,55 +38,44 @@ public class MapDevCommand { public void showVariables( Audience audience, CommandSender sender, - Match match, + VariablesMatchModule vmm, @Argument("target") @Default(CURRENT) MatchPlayer target, @Argument("page") @Default("1") int page, @Flag(value = "query", aliases = "q") String query, @Flag(value = "all", aliases = "a") boolean all) { - List> variables = - match.getFeatureContext().getAll().stream() - .filter(Variable.class::isInstance) - .map(v -> (Variable) v) - .filter(v -> query == null || v.getId().contains(query)) - .sorted(Comparator.comparing(Variable::getId)) - .collect(Collectors.toList()); + List>> variables = vmm.getVariables() + .filter(e -> query == null || e.getKey().contains(query)) + .sorted(Map.Entry.comparingByKey()) + .collect(Collectors.toList()); int resultsPerPage = all ? variables.size() : 8; int pages = all ? 1 : (variables.size() + resultsPerPage - 1) / resultsPerPage; - Component title = - TextFormatter.paginate( - text("Variables for ").append(target.getName()), - page, - pages, - NamedTextColor.DARK_AQUA, - NamedTextColor.AQUA, - true); + Component title = TextFormatter.paginate( + text("Variables for ").append(target.getName()), + page, + pages, + NamedTextColor.DARK_AQUA, + NamedTextColor.AQUA, + true); Component header = TextFormatter.horizontalLineHeading(sender, title, NamedTextColor.BLUE); PrettyPaginatedComponentResults.display( - audience, - variables, - page, - resultsPerPage, - header, - (v, pageIndex) -> { + audience, variables, page, resultsPerPage, header, (e, pageIndex) -> { Component value; - if (v.isIndexed()) { - IndexedVariable idx = (IndexedVariable) v; - value = - join( - JoinConfiguration.commas(true), - IntStream.range(0, Math.min(ARRAY_CAP, idx.size())) - .mapToObj(i -> text(idx.getValue(target, i))) - .collect(Collectors.toList())); + if (e.getValue().isIndexed() && e.getValue() instanceof Variable.Indexed idx) { + value = join( + JoinConfiguration.commas(true), + IntStream.range(0, Math.min(ARRAY_CAP, idx.size())) + .mapToObj(i -> text(idx.getValue(target, i))) + .collect(Collectors.toList())); if (idx.size() > ARRAY_CAP) value = value.append(text(" ...")); } else { - value = text(v.getValue(target)); + value = text(e.getValue().getValue(target)); } - return text().append(text(v.getId() + ": ", NamedTextColor.AQUA), value); + return text().append(text(e.getKey() + ": ", NamedTextColor.AQUA), value); }); } @@ -96,18 +84,18 @@ public void showVariables( @Permission(Permissions.DEBUG) @SuppressWarnings({"rawtypes", "unchecked"}) public void setVariable( + VariablesMatchModule vmm, Audience audience, @Argument("variable") Variable variable, @Argument("value") double value, @Argument("target") @Default(CURRENT) MatchPlayer target) { variable.setValue(target, value); - audience.sendMessage( - text("Variable ", NamedTextColor.YELLOW) - .append(text(variable.getId(), NamedTextColor.AQUA)) - .append(text(" set to ", NamedTextColor.YELLOW)) - .append(text(value + "", NamedTextColor.AQUA)) - .append(text(" for ", NamedTextColor.YELLOW)) - .append(target.getName())); + audience.sendMessage(text("Variable ", NamedTextColor.YELLOW) + .append(text(vmm.getId(variable), NamedTextColor.AQUA)) + .append(text(" set to ", NamedTextColor.YELLOW)) + .append(text(value + "", NamedTextColor.AQUA)) + .append(text(" for ", NamedTextColor.YELLOW)) + .append(target.getName())); } @Command("filter [target]") @@ -117,10 +105,9 @@ public void evaluateFilter( Audience audience, @Argument("filter") Filter filter, @Argument("target") @Default(CURRENT) MatchPlayer target) { - audience.sendMessage( - text("Filter responded with ", NamedTextColor.YELLOW) - .append(text(filter.query(target) + "", NamedTextColor.AQUA)) - .append(text(" to ", NamedTextColor.YELLOW)) - .append(target.getName())); + audience.sendMessage(text("Filter responded with ", NamedTextColor.YELLOW) + .append(text(filter.query(target) + "", NamedTextColor.AQUA)) + .append(text(" to ", NamedTextColor.YELLOW)) + .append(target.getName())); } } diff --git a/core/src/main/java/tc/oc/pgm/command/parsers/FilterArgumentParser.java b/core/src/main/java/tc/oc/pgm/command/parsers/FilterParser.java similarity index 65% rename from core/src/main/java/tc/oc/pgm/command/parsers/FilterArgumentParser.java rename to core/src/main/java/tc/oc/pgm/command/parsers/FilterParser.java index f69b8b1519..dc33a896cd 100644 --- a/core/src/main/java/tc/oc/pgm/command/parsers/FilterArgumentParser.java +++ b/core/src/main/java/tc/oc/pgm/command/parsers/FilterParser.java @@ -1,29 +1,26 @@ package tc.oc.pgm.command.parsers; -import java.util.Collection; import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; import org.bukkit.command.CommandSender; import org.incendo.cloud.CommandManager; import org.incendo.cloud.parser.ParserParameters; import tc.oc.pgm.api.filter.Filter; import tc.oc.pgm.filters.FilterMatchModule; -public final class FilterArgumentParser +public final class FilterParser extends MatchObjectParser, FilterMatchModule> { - public FilterArgumentParser(CommandManager manager, ParserParameters options) { + public FilterParser(CommandManager manager, ParserParameters options) { super(manager, options, Filter.class, FilterMatchModule.class, "filters"); } @Override @SuppressWarnings("unchecked") - protected Collection> objects(FilterMatchModule module) { - return StreamSupport.stream(module.getFilterContext().spliterator(), false) + protected Iterable> objects(FilterMatchModule module) { + return () -> module.getFilterContext().stream() .filter(entry -> entry != null && entry.getValue() instanceof Filter) .map(entry -> (Map.Entry) entry) - .collect(Collectors.toList()); + .iterator(); } @Override diff --git a/core/src/main/java/tc/oc/pgm/command/parsers/VariableParser.java b/core/src/main/java/tc/oc/pgm/command/parsers/VariableParser.java index 28b00d3300..eba463a679 100644 --- a/core/src/main/java/tc/oc/pgm/command/parsers/VariableParser.java +++ b/core/src/main/java/tc/oc/pgm/command/parsers/VariableParser.java @@ -1,5 +1,6 @@ package tc.oc.pgm.command.parsers; +import java.util.Map; import org.bukkit.command.CommandSender; import org.incendo.cloud.CommandManager; import org.incendo.cloud.parser.ParserParameters; @@ -7,19 +8,25 @@ import tc.oc.pgm.variables.VariablesMatchModule; @SuppressWarnings("rawtypes") -public class VariableParser extends MatchObjectParser.Simple { +public class VariableParser + extends MatchObjectParser>, VariablesMatchModule> { public VariableParser(CommandManager manager, ParserParameters options) { super(manager, options, Variable.class, VariablesMatchModule.class, "variables"); } @Override - protected Iterable objects(VariablesMatchModule module) { - return module.getVariables(); + protected Iterable>> objects(VariablesMatchModule module) { + return () -> module.getVariables().iterator(); } @Override - protected String getName(Variable variable) { - return variable.getId(); + protected String getName(Map.Entry> obj) { + return obj.getKey(); + } + + @Override + protected Variable getValue(Map.Entry> obj) { + return obj.getValue(); } } diff --git a/core/src/main/java/tc/oc/pgm/command/util/PGMCommandGraph.java b/core/src/main/java/tc/oc/pgm/command/util/PGMCommandGraph.java index 876f7a1984..0d5111bdff 100644 --- a/core/src/main/java/tc/oc/pgm/command/util/PGMCommandGraph.java +++ b/core/src/main/java/tc/oc/pgm/command/util/PGMCommandGraph.java @@ -64,7 +64,7 @@ import tc.oc.pgm.command.parsers.DurationParser; import tc.oc.pgm.command.parsers.EnumParser; import tc.oc.pgm.command.parsers.ExposedActionParser; -import tc.oc.pgm.command.parsers.FilterArgumentParser; +import tc.oc.pgm.command.parsers.FilterParser; import tc.oc.pgm.command.parsers.MapInfoParser; import tc.oc.pgm.command.parsers.MapPoolParser; import tc.oc.pgm.command.parsers.MatchPlayerParser; @@ -192,7 +192,7 @@ protected void setupParsers() { registerParser( TypeFactory.parameterizedClass(Optional.class, VictoryCondition.class), new VictoryConditionParser()); - registerParser(Filter.class, FilterArgumentParser::new); + registerParser(Filter.class, FilterParser::new); registerParser(SettingKey.class, new EnumParser<>(SettingKey.class, CommandKeys.SETTING_KEY)); registerParser(SettingValue.class, new SettingValueParser()); registerParser(Phase.Phases.class, PhasesParser::new); diff --git a/core/src/main/java/tc/oc/pgm/compass/CompassMatchModule.java b/core/src/main/java/tc/oc/pgm/compass/CompassMatchModule.java index 7bbd127e89..ae70aac13c 100644 --- a/core/src/main/java/tc/oc/pgm/compass/CompassMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/compass/CompassMatchModule.java @@ -27,12 +27,15 @@ import org.bukkit.inventory.meta.ItemMeta; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.match.MatchModule; +import tc.oc.pgm.api.match.MatchScope; import tc.oc.pgm.api.match.Tickable; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.api.time.Tick; +import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.events.PlayerResetEvent; import tc.oc.pgm.spawns.events.ParticipantKitApplyEvent; +@ListenerScope(MatchScope.RUNNING) public class CompassMatchModule implements MatchModule, Tickable, Listener { private static final long REFRESH_TICKS = 20; diff --git a/core/src/main/java/tc/oc/pgm/features/FeatureDefinitionContext.java b/core/src/main/java/tc/oc/pgm/features/FeatureDefinitionContext.java index 220076ac7a..1e3568c79f 100644 --- a/core/src/main/java/tc/oc/pgm/features/FeatureDefinitionContext.java +++ b/core/src/main/java/tc/oc/pgm/features/FeatureDefinitionContext.java @@ -54,19 +54,18 @@ public void add(String name, FeatureDefinition obj) { */ public void addFeature(@Nullable Element node, @Nullable String id, FeatureDefinition definition) throws InvalidXMLException { - if (definitions.add(definition)) { - if (id != null) { - FeatureDefinition old = this.store.put(id, definition); - if (old != null && old != definition) { - this.store.put(id, old); - throw new InvalidXMLException( - "The ID '" + id + "' is already in use by a different feature", node); - } + definitions.add(definition); + if (id != null) { + FeatureDefinition old = this.store.put(id, definition); + if (old != null && old != definition) { + this.store.put(id, old); + throw new InvalidXMLException( + "The ID '" + id + "' is already in use by a different feature", node); } + } - if (node != null) { - definitionNodes.put(definition, node); - } + if (node != null) { + definitionNodes.put(definition, node); } } diff --git a/core/src/main/java/tc/oc/pgm/features/MatchFeatureContext.java b/core/src/main/java/tc/oc/pgm/features/MatchFeatureContext.java index 53207d79c1..554cc72303 100644 --- a/core/src/main/java/tc/oc/pgm/features/MatchFeatureContext.java +++ b/core/src/main/java/tc/oc/pgm/features/MatchFeatureContext.java @@ -1,10 +1,17 @@ package tc.oc.pgm.features; +import static tc.oc.pgm.util.Assert.assertNotNull; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Map; import tc.oc.pgm.api.feature.Feature; import tc.oc.pgm.util.collection.ContextStore; public class MatchFeatureContext extends ContextStore { + private final Map, Object> states = new IdentityHashMap<>(); + public String add(Feature feature) { super.add(feature.getId(), feature); return feature.getId(); @@ -13,4 +20,17 @@ public String add(Feature feature) { public T get(String id, Class type) { return (T) this.get(id); } + + public void registerState(StateHolder stateHolder, T state) { + states.put(stateHolder, state); + } + + public T getState(StateHolder stateHolder) { + //noinspection unchecked + return (T) assertNotNull(states.get(stateHolder), "state"); + } + + public Map, Object> getStates() { + return Collections.unmodifiableMap(states); + } } diff --git a/core/src/main/java/tc/oc/pgm/features/StateHolder.java b/core/src/main/java/tc/oc/pgm/features/StateHolder.java new file mode 100644 index 0000000000..d19b2c48a8 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/features/StateHolder.java @@ -0,0 +1,3 @@ +package tc.oc.pgm.features; + +public interface StateHolder {} diff --git a/core/src/main/java/tc/oc/pgm/filters/FilterMatchModule.java b/core/src/main/java/tc/oc/pgm/filters/FilterMatchModule.java index c827282b68..164e6a0dc1 100644 --- a/core/src/main/java/tc/oc/pgm/filters/FilterMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/filters/FilterMatchModule.java @@ -1,6 +1,5 @@ package tc.oc.pgm.filters; -import static tc.oc.pgm.util.Assert.assertNotNull; import static tc.oc.pgm.util.bukkit.MiscUtils.MISC_UTILS; import static tc.oc.pgm.util.nms.NMSHacks.NMS_HACKS; @@ -29,7 +28,6 @@ import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.plugin.EventExecutor; -import org.jetbrains.annotations.NotNull; import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.filter.Filter; import tc.oc.pgm.api.filter.FilterListener; @@ -79,8 +77,6 @@ public class FilterMatchModule implements MatchModule, FilterDispatcher, Tickabl private final Set> listeningFor = new HashSet<>(); private final AtomicBoolean loaded = new AtomicBoolean(false); - private final Map, ReactorFactory.Reactor> activeReactors = new HashMap<>(); - private final DummyListener dummyListener = new DummyListener(); private final Table>, ListenerSet> listeners = @@ -113,7 +109,7 @@ public ContextStore getFilterContext() { return filterContext; } - @EventHandler + @EventHandler(priority = EventPriority.LOW) public void onMatchLoad(MatchLoadEvent event) { // These two have to be on separate passes, in case a reactor wants to register a child filter @@ -164,17 +160,17 @@ public void onMatchLoad(MatchLoadEvent event) { } private void findAndCreateReactorFactories(Filter filter) { - filter - .deepDependencies(Filter.class) - .filter(f -> f instanceof ReactorFactory) - .forEach(factory -> activeReactors.computeIfAbsent( - (ReactorFactory) factory, f -> f.createReactor(match, this))); + filter.deepDependencies(Filter.class).forEach(dep -> { + if (dep instanceof ReactorFactory rf) rf.register(match, this); + }); } @Override public void unload() { HandlerList.unregisterAll(this.dummyListener); - this.activeReactors.values().forEach(ReactorFactory.Reactor::unload); + for (Object state : match.getFeatureContext().getStates().values()) { + if (state instanceof ReactorFactory.Reactor r) r.unload(); + } } /** @@ -524,17 +520,5 @@ public void onFlagStateChange(FlagStateChangeEvent event) { this.invalidate(match); } - /** - * Gets the active {@link ReactorFactory.Reactor} created by the given factory for this match. - * - * @param factory a factory which was used to create a reactor for this match - * @return an active reactor - * @throws NullPointerException if no active reactor is found for the given factory - */ - @SuppressWarnings("unchecked") - public @NotNull T getReactor(ReactorFactory factory) { - return (T) assertNotNull(this.activeReactors.get(factory), "reactor"); - } - private static class DummyListener implements Listener {} } diff --git a/core/src/main/java/tc/oc/pgm/filters/FilterModule.java b/core/src/main/java/tc/oc/pgm/filters/FilterModule.java index c2aa4346ed..e5fd8cc75e 100644 --- a/core/src/main/java/tc/oc/pgm/filters/FilterModule.java +++ b/core/src/main/java/tc/oc/pgm/filters/FilterModule.java @@ -14,6 +14,14 @@ import tc.oc.pgm.api.match.Match; import tc.oc.pgm.classes.ClassModule; import tc.oc.pgm.filters.matcher.StaticFilter; +import tc.oc.pgm.filters.matcher.block.VoidFilter; +import tc.oc.pgm.filters.matcher.match.MatchPhaseFilter; +import tc.oc.pgm.filters.matcher.player.CanFlyFilter; +import tc.oc.pgm.filters.matcher.player.FlyingFilter; +import tc.oc.pgm.filters.matcher.player.GroundedFilter; +import tc.oc.pgm.filters.matcher.player.ParticipatingFilter; +import tc.oc.pgm.filters.matcher.player.PlayerMovementFilter; +import tc.oc.pgm.filters.matcher.player.PlayerStateFilter; import tc.oc.pgm.filters.parse.FilterParser; import tc.oc.pgm.regions.EmptyRegion; import tc.oc.pgm.regions.EverywhereRegion; @@ -51,13 +59,43 @@ public Collection>> getWeakDependencies() { public FilterModule parse(MapFactory factory, Logger logger, Document doc) throws InvalidXMLException { boolean unified = factory.getProto().isNoOlderThan(MapProtos.FILTER_FEATURES); + boolean featureIds = factory.getProto().isNoOlderThan(MapProtos.FEATURE_SINGLETON_IDS); FilterParser parser = factory.getFilters(); + var features = factory.getFeatures(); if (unified) { - factory.getFeatures().addFeature(null, "always", StaticFilter.ALLOW); - factory.getFeatures().addFeature(null, "never", StaticFilter.DENY); - factory.getFeatures().addFeature(null, "everywhere", EverywhereRegion.INSTANCE); - factory.getFeatures().addFeature(null, "nowhere", EmptyRegion.INSTANCE); + features.addFeature(null, "always", StaticFilter.ALLOW); + features.addFeature(null, "never", StaticFilter.DENY); + features.addFeature(null, "everywhere", EverywhereRegion.INSTANCE); + features.addFeature(null, "nowhere", EmptyRegion.INSTANCE); + } + + if (featureIds) { + // Participating + features.addFeature(null, "observing", ParticipatingFilter.OBSERVING); + features.addFeature(null, "participating", ParticipatingFilter.PARTICIPATING); + + // Player state + features.addFeature(null, "alive", PlayerStateFilter.ALIVE); + features.addFeature(null, "dead", PlayerStateFilter.DEAD); + + // Match state + features.addFeature(null, "match-idle", MatchPhaseFilter.IDLE); + features.addFeature(null, "match-starting", MatchPhaseFilter.STARTING); + features.addFeature(null, "match-running", MatchPhaseFilter.RUNNING); + features.addFeature(null, "match-finished", MatchPhaseFilter.FINISHED); + features.addFeature(null, "match-started", MatchPhaseFilter.STARTED); + + // Void + features.addFeature(null, "void", VoidFilter.INSTANCE); + + // Player poses + features.addFeature(null, "crouching", PlayerMovementFilter.CROUCHING); + features.addFeature(null, "walking", PlayerMovementFilter.WALKING); + features.addFeature(null, "sprinting", PlayerMovementFilter.SPRINTING); + features.addFeature(null, "grounded", GroundedFilter.INSTANCE); + features.addFeature(null, "flying", FlyingFilter.INSTANCE); + features.addFeature(null, "can-fly", CanFlyFilter.INSTANCE); } for (Element filtersEl : doc.getRootElement().getChildren("filters")) { diff --git a/core/src/main/java/tc/oc/pgm/filters/matcher/block/BlocksFilter.java b/core/src/main/java/tc/oc/pgm/filters/matcher/block/BlocksFilter.java index 34870cb517..e45f35f1a7 100644 --- a/core/src/main/java/tc/oc/pgm/filters/matcher/block/BlocksFilter.java +++ b/core/src/main/java/tc/oc/pgm/filters/matcher/block/BlocksFilter.java @@ -28,7 +28,7 @@ public BlocksFilter(Region region, Filter filter) { @Override public boolean matches(LocationQuery query) { - return query.reactor(this).region.contains(query); + return query.state(this).region.contains(query); } @Override @@ -60,7 +60,8 @@ private Region createBlockRegion(Match match, Region region) { return Union.of(result); } - return FiniteBlockRegion.fromWorld(region, match.getWorld(), filter, match.getMap().getProto()); + return FiniteBlockRegion.fromWorld( + region, match.getWorld(), filter, match.getMap().getProto()); } protected static final class Reactor extends ReactorFactory.Reactor { diff --git a/core/src/main/java/tc/oc/pgm/filters/matcher/block/VoidFilter.java b/core/src/main/java/tc/oc/pgm/filters/matcher/block/VoidFilter.java index 052d6b71e6..08a04cd2ca 100644 --- a/core/src/main/java/tc/oc/pgm/filters/matcher/block/VoidFilter.java +++ b/core/src/main/java/tc/oc/pgm/filters/matcher/block/VoidFilter.java @@ -9,6 +9,8 @@ /** Matches blocks that have only air/void below them */ public class VoidFilter extends TypedFilter.Impl { + public static final VoidFilter INSTANCE = new VoidFilter(); + @Override public Class queryType() { return BlockQuery.class; diff --git a/core/src/main/java/tc/oc/pgm/filters/matcher/match/MatchPhaseFilter.java b/core/src/main/java/tc/oc/pgm/filters/matcher/match/MatchPhaseFilter.java index f92f53bc42..3f71960929 100644 --- a/core/src/main/java/tc/oc/pgm/filters/matcher/match/MatchPhaseFilter.java +++ b/core/src/main/java/tc/oc/pgm/filters/matcher/match/MatchPhaseFilter.java @@ -12,13 +12,12 @@ public class MatchPhaseFilter extends TypedFilter.Impl { + public static final MatchPhaseFilter IDLE = new MatchPhaseFilter(MatchPhase.IDLE); + public static final MatchPhaseFilter STARTING = new MatchPhaseFilter(MatchPhase.STARTING); public static final MatchPhaseFilter RUNNING = new MatchPhaseFilter(MatchPhase.RUNNING); public static final MatchPhaseFilter FINISHED = new MatchPhaseFilter(MatchPhase.FINISHED); - public static final MatchPhaseFilter STARTING = new MatchPhaseFilter(MatchPhase.STARTING); - public static final MatchPhaseFilter IDLE = new MatchPhaseFilter(MatchPhase.IDLE); - public static final Filter STARTED = - AnyFilter.of( - new MatchPhaseFilter(MatchPhase.RUNNING), new MatchPhaseFilter(MatchPhase.FINISHED)); + public static final Filter STARTED = AnyFilter.of( + new MatchPhaseFilter(MatchPhase.RUNNING), new MatchPhaseFilter(MatchPhase.FINISHED)); private final MatchPhase matchPhase; diff --git a/core/src/main/java/tc/oc/pgm/filters/matcher/match/MonostableFilter.java b/core/src/main/java/tc/oc/pgm/filters/matcher/match/MonostableFilter.java index 6adfc77485..0bf1dd9c5a 100644 --- a/core/src/main/java/tc/oc/pgm/filters/matcher/match/MonostableFilter.java +++ b/core/src/main/java/tc/oc/pgm/filters/matcher/match/MonostableFilter.java @@ -65,7 +65,7 @@ public Class queryType() { @Override public boolean matches(MatchQuery query) { - return query.reactor(this).matches(query); + return query.state(this).matches(query); } @Override @@ -122,12 +122,11 @@ boolean matches(Filterable filterable, boolean response) { public void tick(Match match, Tick tick) { final Instant now = tick.instant; - endTimes.forEach( - (filterable, end) -> { - if (!now.isBefore(end) && lastTick.isBefore(end)) { - this.invalidate(filterable); - } - }); + endTimes.forEach((filterable, end) -> { + if (!now.isBefore(end) && lastTick.isBefore(end)) { + this.invalidate(filterable); + } + }); lastTick = now; } } @@ -175,19 +174,18 @@ public void tick(Match match, Tick tick) { final Instant now = tick.instant; - endTimes.forEach( - (filterable, end) -> { - if (now.isBefore(end)) { - // If the entry is still valid, check if its elapsed time crossed a second - // boundary over the last tick, and update the boss bar if it did. - long oldSeconds = lastTick.until(end, ChronoUnit.SECONDS); - long newSeconds = now.until(end, ChronoUnit.SECONDS); + endTimes.forEach((filterable, end) -> { + if (now.isBefore(end)) { + // If the entry is still valid, check if its elapsed time crossed a second + // boundary over the last tick, and update the boss bar if it did. + long oldSeconds = lastTick.until(end, ChronoUnit.SECONDS); + long newSeconds = now.until(end, ChronoUnit.SECONDS); - // Round up as going from 4s to 3.95s should show 4s - if (oldSeconds != newSeconds) - updateBossBar(filterable, Duration.ofSeconds(newSeconds + 1)); - } - }); + // Round up as going from 4s to 3.95s should show 4s + if (oldSeconds != newSeconds) + updateBossBar(filterable, Duration.ofSeconds(newSeconds + 1)); + } + }); lastTick = now; } @@ -195,10 +193,9 @@ public void tick(Match match, Tick tick) { private void createBossBar(Filterable filterable) { Component message = getMessage(duration); - BossBar bar = - bars.computeIfAbsent( - filterable, - f -> BossBar.bossBar(message, 1f, BossBar.Color.YELLOW, BossBar.Overlay.PROGRESS)); + BossBar bar = bars.computeIfAbsent( + filterable, + f -> BossBar.bossBar(message, 1f, BossBar.Color.YELLOW, BossBar.Overlay.PROGRESS)); bar.name(message).progress(1f); filterable.showBossBar(bar); @@ -233,13 +230,15 @@ private void updatePlayerBar(MatchPlayer player, boolean response) { } private Component getMessage(Duration remaining) { - Component duration = - colons - ? clock(remaining).color(NamedTextColor.AQUA) - : seconds(remaining.getSeconds(), NamedTextColor.AQUA); - - return message.replaceText( - TextReplacementConfig.builder().once().matchLiteral("{0}").replacement(duration).build()); + Component duration = colons + ? clock(remaining).color(NamedTextColor.AQUA) + : seconds(remaining.getSeconds(), NamedTextColor.AQUA); + + return message.replaceText(TextReplacementConfig.builder() + .once() + .matchLiteral("{0}") + .replacement(duration) + .build()); } private float progress(Duration remaining) { diff --git a/core/src/main/java/tc/oc/pgm/filters/matcher/match/PlayerCountFilter.java b/core/src/main/java/tc/oc/pgm/filters/matcher/match/PlayerCountFilter.java index c986b3e26c..c000f1ee3b 100644 --- a/core/src/main/java/tc/oc/pgm/filters/matcher/match/PlayerCountFilter.java +++ b/core/src/main/java/tc/oc/pgm/filters/matcher/match/PlayerCountFilter.java @@ -34,9 +34,8 @@ public PlayerCountFilter( @Override public Collection> getRelevantEvents() { - return ImmutableList.copyOf( - Iterables.concat( - filter.getRelevantEvents(), ImmutableList.of(PlayerPartyChangeEvent.class))); + return ImmutableList.copyOf(Iterables.concat( + filter.getRelevantEvents(), ImmutableList.of(PlayerPartyChangeEvent.class))); } @Override @@ -51,7 +50,7 @@ public Class queryType() { @Override public boolean matches(MatchQuery query) { - return query.reactor(this).response(); + return query.state(this).response(); } protected final class Reactor extends ReactorFactory.Reactor diff --git a/core/src/main/java/tc/oc/pgm/filters/matcher/match/PulseFilter.java b/core/src/main/java/tc/oc/pgm/filters/matcher/match/PulseFilter.java index 4bcfbf9282..4c35c2758d 100644 --- a/core/src/main/java/tc/oc/pgm/filters/matcher/match/PulseFilter.java +++ b/core/src/main/java/tc/oc/pgm/filters/matcher/match/PulseFilter.java @@ -34,7 +34,7 @@ public Class queryType() { @Override public boolean matches(MatchQuery query) { - return query.reactor(this).matches(query); + return query.state(this).matches(query); } @Override @@ -88,13 +88,12 @@ boolean matches(Filterable filterable, boolean response) { public void tick(Match match, Tick tick) { final long now = tick.tick; - startTimes.forEach( - (filterable, start) -> { - long ticks = (now - start) % period; - if (ticks == 0 || ticks == duration) { - this.invalidate(filterable); - } - }); + startTimes.forEach((filterable, start) -> { + long ticks = (now - start) % period; + if (ticks == 0 || ticks == duration) { + this.invalidate(filterable); + } + }); } } } diff --git a/core/src/main/java/tc/oc/pgm/filters/matcher/match/VariableFilter.java b/core/src/main/java/tc/oc/pgm/filters/matcher/match/VariableFilter.java index fbd093c58b..aed2733c85 100644 --- a/core/src/main/java/tc/oc/pgm/filters/matcher/match/VariableFilter.java +++ b/core/src/main/java/tc/oc/pgm/filters/matcher/match/VariableFilter.java @@ -13,21 +13,18 @@ import tc.oc.pgm.util.xml.InvalidXMLException; import tc.oc.pgm.util.xml.Node; import tc.oc.pgm.variables.Variable; -import tc.oc.pgm.variables.VariableDefinition; -import tc.oc.pgm.variables.types.IndexedVariable; public abstract class VariableFilter implements WeakTypedFilter { - private final VariableDefinition variable; + private final Variable variable; private final Range values; - private VariableFilter(VariableDefinition variable, Range values) { + private VariableFilter(Variable variable, Range values) { this.variable = variable; this.values = values; } - public static VariableFilter of( - VariableDefinition var, Integer idx, Range range, Node node) + public static VariableFilter of(Variable var, Integer idx, Range range, Node node) throws InvalidXMLException { if (var.isIndexed()) { if (idx == null) @@ -47,8 +44,7 @@ public QueryResponse queryTyped(Q query) { Filterable filterable = query.extractFilterable(); if (!Filterables.isAssignable(filterable, variable.getScope())) return QueryResponse.ABSTAIN; - return QueryResponse.fromBoolean( - values.contains(getValue(variable.getVariable(filterable.getMatch()), filterable))); + return QueryResponse.fromBoolean(values.contains(getValue(variable, filterable))); } protected double getValue(Variable variable, Filterable filterable) { @@ -74,7 +70,7 @@ public String toString() { public static class Generic extends VariableFilter { - public Generic(VariableDefinition variable, Range values) { + public Generic(Variable variable, Range values) { super(variable, values); } @@ -90,7 +86,7 @@ public Class queryType() { */ public static class Team extends VariableFilter implements CompetitorFilter { - public Team(VariableDefinition variable, Range values) { + public Team(Variable variable, Range values) { super(variable, values); } @@ -107,28 +103,28 @@ public boolean matches(MatchQuery query, Competitor competitor) { public static class Indexed extends Generic { private final int idx; - public Indexed(VariableDefinition variable, int idx, Range values) { + public Indexed(Variable variable, int idx, Range values) { super(variable, values); this.idx = idx; } @Override protected double getValue(Variable variable, Filterable filterable) { - return ((IndexedVariable) variable).getValue(filterable, idx); + return ((Variable.Indexed) variable).getValue(filterable, idx); } } public static class TeamIndexed extends Team { private final int idx; - public TeamIndexed(VariableDefinition variable, int idx, Range values) { + public TeamIndexed(Variable variable, int idx, Range values) { super(variable, values); this.idx = idx; } @Override protected double getValue(Variable variable, Filterable filterable) { - return ((IndexedVariable) variable).getValue(filterable, idx); + return ((Variable.Indexed) variable).getValue(filterable, idx); } } } diff --git a/core/src/main/java/tc/oc/pgm/filters/matcher/player/CanFlyFilter.java b/core/src/main/java/tc/oc/pgm/filters/matcher/player/CanFlyFilter.java index eaea657063..731b26eb75 100644 --- a/core/src/main/java/tc/oc/pgm/filters/matcher/player/CanFlyFilter.java +++ b/core/src/main/java/tc/oc/pgm/filters/matcher/player/CanFlyFilter.java @@ -4,6 +4,7 @@ import tc.oc.pgm.api.player.MatchPlayer; public class CanFlyFilter extends ParticipantFilter { + public static final CanFlyFilter INSTANCE = new CanFlyFilter(); @Override protected boolean matches(PlayerQuery query, MatchPlayer player) { diff --git a/core/src/main/java/tc/oc/pgm/filters/matcher/player/FlyingFilter.java b/core/src/main/java/tc/oc/pgm/filters/matcher/player/FlyingFilter.java index 238db23388..2300f6ad9e 100644 --- a/core/src/main/java/tc/oc/pgm/filters/matcher/player/FlyingFilter.java +++ b/core/src/main/java/tc/oc/pgm/filters/matcher/player/FlyingFilter.java @@ -8,6 +8,7 @@ import tc.oc.pgm.util.event.PlayerCoarseMoveEvent; public class FlyingFilter extends ParticipantFilter { + public static final FlyingFilter INSTANCE = new FlyingFilter(); @Override public Collection> getRelevantEvents() { diff --git a/core/src/main/java/tc/oc/pgm/filters/matcher/player/PlayerMovementFilter.java b/core/src/main/java/tc/oc/pgm/filters/matcher/player/PlayerMovementFilter.java index 006e47a965..7e6873581a 100644 --- a/core/src/main/java/tc/oc/pgm/filters/matcher/player/PlayerMovementFilter.java +++ b/core/src/main/java/tc/oc/pgm/filters/matcher/player/PlayerMovementFilter.java @@ -4,27 +4,33 @@ import java.util.Collection; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.event.player.PlayerToggleSprintEvent; import tc.oc.pgm.api.filter.query.PlayerQuery; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.util.event.PlayerCoarseMoveEvent; public class PlayerMovementFilter extends ParticipantFilter { - private final boolean sprinting; - private final boolean sneaking; + public static final PlayerMovementFilter CROUCHING = new PlayerMovementFilter(null, true); + public static final PlayerMovementFilter WALKING = new PlayerMovementFilter(false, false); + public static final PlayerMovementFilter SPRINTING = new PlayerMovementFilter(true, null); - public PlayerMovementFilter(boolean sprinting, boolean sneaking) { + private final Boolean sprinting; + private final Boolean sneaking; + + public PlayerMovementFilter(Boolean sprinting, Boolean sneaking) { this.sprinting = sprinting; this.sneaking = sneaking; } @Override public Collection> getRelevantEvents() { - return ImmutableList.of(PlayerCoarseMoveEvent.class, PlayerToggleSneakEvent.class); + return ImmutableList.of( + PlayerCoarseMoveEvent.class, PlayerToggleSprintEvent.class, PlayerToggleSneakEvent.class); } @Override public boolean matches(PlayerQuery query, MatchPlayer player) { - return sprinting == player.getBukkit().isSprinting() - && sneaking == player.getBukkit().isSneaking(); + return (sprinting == null || sprinting == player.getBukkit().isSprinting()) + && (sneaking == null || sneaking == player.getBukkit().isSneaking()); } } diff --git a/core/src/main/java/tc/oc/pgm/filters/parse/FeatureFilterParser.java b/core/src/main/java/tc/oc/pgm/filters/parse/FeatureFilterParser.java index 1af55e2fd8..6102df5a53 100644 --- a/core/src/main/java/tc/oc/pgm/filters/parse/FeatureFilterParser.java +++ b/core/src/main/java/tc/oc/pgm/filters/parse/FeatureFilterParser.java @@ -27,7 +27,7 @@ import tc.oc.pgm.util.xml.InvalidXMLException; import tc.oc.pgm.util.xml.Node; import tc.oc.pgm.util.xml.XMLUtils; -import tc.oc.pgm.variables.VariableDefinition; +import tc.oc.pgm.variables.Variable; import tc.oc.pgm.variables.VariablesModule; public class FeatureFilterParser extends FilterParser { @@ -96,11 +96,10 @@ public Filter parseNot(Element el) throws InvalidXMLException { } private static final Pattern INLINE_VARIABLE = - Pattern.compile( - "(%VAR%)(?:\\[(\\d+)])?\\s*=\\s*(%RANGE%|%NUM%)" - .replace("%VAR%", VariablesModule.Factory.VARIABLE_ID.pattern()) - .replace("%RANGE%", XMLUtils.RANGE_DOTTED.pattern()) - .replace("%NUM%", "-?\\d*\\.?\\d+")); + Pattern.compile("(%VAR%)(?:\\[(\\d+)])?\\s*=\\s*(%RANGE%|%NUM%)" + .replace("%VAR%", VariablesModule.Factory.VARIABLE_ID.pattern()) + .replace("%RANGE%", XMLUtils.RANGE_DOTTED.pattern()) + .replace("%NUM%", "-?\\d*\\.?\\d+")); private @Nullable Filter parseInlineFilter(Node node, String text) throws InvalidXMLException { // Formula-style inline filter @@ -115,8 +114,7 @@ public Filter parseNot(Element el) throws InvalidXMLException { // Parse variable filter Matcher match = INLINE_VARIABLE.matcher(text); if (match.matches()) { - VariableDefinition variable = - features.resolve(node, match.group(1), VariableDefinition.class); + Variable variable = features.resolve(node, match.group(1), Variable.class); Integer index = match.group(2) == null ? null : XMLUtils.parseNumber(node, match.group(2), Integer.class); Range range = XMLUtils.parseNumericRange(node, match.group(3), Double.class); @@ -128,22 +126,16 @@ public Filter parseNot(Element el) throws InvalidXMLException { private Filter buildFilter(Node node, ParsingNode parsed) throws InvalidXMLException { if (parsed.getChildren() == null) return parseReference(node, parsed.getBase()); - switch (parsed.getBase()) { - case "all": - return AllFilter.of(buildChildren(node, parsed)); - case "any": - return AnyFilter.of(buildChildren(node, parsed)); - case "one": - return OneFilter.of(buildChildren(node, parsed)); - case "not": - return new InverseFilter(buildChild(node, parsed)); - case "deny": - return new DenyFilter(buildChild(node, parsed)); - case "allow": - return new AllowFilter(buildChild(node, parsed)); - default: - throw new SyntaxException("Unknown inline filter type " + parsed.getBase(), parsed); - } + return switch (parsed.getBase()) { + case "all" -> AllFilter.of(buildChildren(node, parsed)); + case "any" -> AnyFilter.of(buildChildren(node, parsed)); + case "one" -> OneFilter.of(buildChildren(node, parsed)); + case "not" -> new InverseFilter(buildChild(node, parsed)); + case "deny" -> new DenyFilter(buildChild(node, parsed)); + case "allow" -> new AllowFilter(buildChild(node, parsed)); + default -> throw new SyntaxException( + "Unknown inline filter type " + parsed.getBase(), parsed); + }; } private List buildChildren(Node node, ParsingNode parent) throws InvalidXMLException { @@ -156,6 +148,6 @@ private Filter buildChild(Node node, ParsingNode parent) throws InvalidXMLExcept if (parent.getChildrenCount() != 1) throw new SyntaxException( "Expected exactly one child but got " + parent.getChildrenCount(), parent); - return buildFilter(node, parent.getChildren().get(0)); + return buildFilter(node, parent.getChildren().getFirst()); } } diff --git a/core/src/main/java/tc/oc/pgm/filters/parse/FilterParser.java b/core/src/main/java/tc/oc/pgm/filters/parse/FilterParser.java index aa9d79b719..ccd4a32d0f 100644 --- a/core/src/main/java/tc/oc/pgm/filters/parse/FilterParser.java +++ b/core/src/main/java/tc/oc/pgm/filters/parse/FilterParser.java @@ -91,7 +91,7 @@ import tc.oc.pgm.util.xml.InvalidXMLException; import tc.oc.pgm.util.xml.Node; import tc.oc.pgm.util.xml.XMLUtils; -import tc.oc.pgm.variables.VariableDefinition; +import tc.oc.pgm.variables.Variable; public abstract class FilterParser implements XMLParser { @@ -288,7 +288,7 @@ public MaterialFilter parseMaterial(Element el) throws InvalidXMLException { @MethodParser("void") public VoidFilter parseVoid(Element el) throws InvalidXMLException { - return new VoidFilter(); + return VoidFilter.INSTANCE; } @MethodParser("entity") @@ -361,27 +361,27 @@ public RandomFilter parseRandom(Element el) throws InvalidXMLException { @MethodParser("crouching") public PlayerMovementFilter parseCrouching(Element el) throws InvalidXMLException { - return new PlayerMovementFilter(false, true); + return PlayerMovementFilter.CROUCHING; } @MethodParser("walking") public PlayerMovementFilter parseWalking(Element el) throws InvalidXMLException { - return new PlayerMovementFilter(false, false); + return PlayerMovementFilter.WALKING; } @MethodParser("sprinting") public PlayerMovementFilter parseSprinting(Element el) throws InvalidXMLException { - return new PlayerMovementFilter(true, false); + return PlayerMovementFilter.SPRINTING; } @MethodParser("flying") public FlyingFilter parseFlying(Element el) throws InvalidXMLException { - return new FlyingFilter(); + return FlyingFilter.INSTANCE; } @MethodParser("can-fly") public CanFlyFilter parseCanFly(Element el) throws InvalidXMLException { - return new CanFlyFilter(); + return CanFlyFilter.INSTANCE; } @MethodParser("grounded") @@ -567,40 +567,29 @@ public Filter parseScoreFilter(Element el) throws InvalidXMLException { @MethodParser("match-phase") public Filter parseMatchPhase(Element el) throws InvalidXMLException { - return parseMatchPhaseFilter(el.getValue(), el); + return switch (el.getTextNormalize()) { + case "running" -> MatchPhaseFilter.RUNNING; + case "finished" -> MatchPhaseFilter.FINISHED; + case "starting" -> MatchPhaseFilter.STARTING; + case "idle" -> MatchPhaseFilter.IDLE; + case "started" -> MatchPhaseFilter.STARTED; + default -> throw new InvalidXMLException("Invalid or no match state found", el); + }; } @MethodParser("match-started") public Filter parseMatchStarted(Element el) throws InvalidXMLException { - return parseMatchPhaseFilter("started", el); + return MatchPhaseFilter.STARTED; } @MethodParser("match-running") public Filter parseMatchRunning(Element el) throws InvalidXMLException { - return parseMatchPhaseFilter("running", el); + return MatchPhaseFilter.RUNNING; } @MethodParser("match-finished") public Filter parseMatchFinished(Element el) throws InvalidXMLException { - return parseMatchPhaseFilter("finished", el); - } - - private Filter parseMatchPhaseFilter(String matchState, Element el) throws InvalidXMLException { - - switch (matchState) { - case "running": - return MatchPhaseFilter.RUNNING; - case "finished": - return MatchPhaseFilter.FINISHED; - case "starting": - return MatchPhaseFilter.STARTING; - case "idle": - return MatchPhaseFilter.IDLE; - case "started": - return MatchPhaseFilter.STARTED; - } - - throw new InvalidXMLException("Invalid or no match state found", el); + return MatchPhaseFilter.FINISHED; } // Methods for parsing QueryModifiers @@ -632,8 +621,7 @@ public PlayerCountFilter parsePlayerCountFilter(Element el) throws InvalidXMLExc @MethodParser("variable") public Filter parseVariableFilter(Element el) throws InvalidXMLException { - VariableDefinition varDef = - features.resolve(Node.fromRequiredAttr(el, "var"), VariableDefinition.class); + Variable varDef = features.resolve(Node.fromRequiredAttr(el, "var"), Variable.class); Integer index = null; if (varDef.isIndexed()) index = XMLUtils.parseNumber(Node.fromRequiredAttr(el, "index"), Integer.class); diff --git a/core/src/main/java/tc/oc/pgm/flag/Flag.java b/core/src/main/java/tc/oc/pgm/flag/Flag.java index 48a833618f..ef170c20ea 100644 --- a/core/src/main/java/tc/oc/pgm/flag/Flag.java +++ b/core/src/main/java/tc/oc/pgm/flag/Flag.java @@ -81,9 +81,9 @@ public class Flag extends TouchableGoal implements Listener { private final ItemStack legacyBannerItem; private final AngleProvider bannerYawProvider; private final @Nullable Team owner; - private final Set capturers; - private final Set controllers; - private final Set completers; + private Set capturers; + private Set controllers; + private Set completers; private BaseState state; private boolean transitioning; @@ -100,33 +100,6 @@ protected Flag(Match match, FlagDefinition definition, ImmutableSet capturersBuilder = ImmutableSet.builder(); - if (tmm != null) { - for (Team team : tmm.getTeams()) { - Query query = team.getQuery(); - if (getDefinition().canPickup(query) && canCapture(query)) { - capturersBuilder.add(team); - } - } - } - this.capturers = capturersBuilder.build(); - - ImmutableSet.Builder controllersBuilder = ImmutableSet.builder(); - ImmutableSet.Builder completersBuilder = ImmutableSet.builder(); - for (NetDefinition net : nets) { - PostDefinition netPost = net.getReturnPost(); - if (netPost != null && netPost.getFallback().getOwner() != null && tmm != null) { - Team controller = tmm.getTeam(netPost.getFallback().getOwner()); - controllersBuilder.add(controller); - - if (net.getReturnPost().getFallback().isPermanent()) { - completersBuilder.add(controller); - } - } - } - this.controllers = controllersBuilder.build(); - this.completers = completersBuilder.build(); - Banner banner = null; pointLoop: for (PointProvider returnPoint : definition.getDefaultPost().getFallback().getReturnPoints()) { @@ -304,6 +277,34 @@ public boolean isProximityRelevant(Competitor team) { // Misc public void load(FlagMatchModule fmm) { + TeamMatchModule tmm = match.getModule(TeamMatchModule.class); + ImmutableSet.Builder capturersBuilder = ImmutableSet.builder(); + if (tmm != null) { + for (Team team : tmm.getTeams()) { + Query query = team.getQuery(); + if (getDefinition().canPickup(query) && canCapture(query)) { + capturersBuilder.add(team); + } + } + } + this.capturers = capturersBuilder.build(); + + ImmutableSet.Builder controllersBuilder = ImmutableSet.builder(); + ImmutableSet.Builder completersBuilder = ImmutableSet.builder(); + for (NetDefinition net : nets) { + PostDefinition netPost = net.getReturnPost(); + if (netPost != null && netPost.getFallback().getOwner() != null && tmm != null) { + Team controller = tmm.getTeam(netPost.getFallback().getOwner()); + controllersBuilder.add(controller); + + if (net.getReturnPost().getFallback().isPermanent()) { + completersBuilder.add(controller); + } + } + } + this.controllers = controllersBuilder.build(); + this.completers = completersBuilder.build(); + this.state = new Returned(this, fmm.getPost(this.getDefinition().getDefaultPost()), this.bannerLocation); this.state.enterState(); diff --git a/core/src/main/java/tc/oc/pgm/flag/FlagMatchModule.java b/core/src/main/java/tc/oc/pgm/flag/FlagMatchModule.java index ef16f15015..19c0f829d4 100644 --- a/core/src/main/java/tc/oc/pgm/flag/FlagMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/flag/FlagMatchModule.java @@ -4,13 +4,19 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.match.MatchModule; +import tc.oc.pgm.api.match.MatchScope; +import tc.oc.pgm.api.match.event.MatchLoadEvent; import tc.oc.pgm.api.module.exception.ModuleLoadException; +import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.flag.post.PostDefinition; import tc.oc.pgm.goals.GoalMatchModule; -public class FlagMatchModule implements MatchModule { +@ListenerScope(MatchScope.LOADED) +public class FlagMatchModule implements MatchModule, Listener { private final ImmutableMap posts; private final ImmutableMap flags; @@ -47,8 +53,8 @@ public FlagMatchModule( this.flags = flags.build(); } - @Override - public void load() { + @EventHandler + public void onMatchLoad(MatchLoadEvent event) { for (Flag flag : this.flags.values()) { flag.load(this); } diff --git a/core/src/main/java/tc/oc/pgm/goals/GoalMatchModule.java b/core/src/main/java/tc/oc/pgm/goals/GoalMatchModule.java index f39740d67f..cec333e993 100644 --- a/core/src/main/java/tc/oc/pgm/goals/GoalMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/goals/GoalMatchModule.java @@ -14,10 +14,12 @@ import java.util.Map.Entry; import net.kyori.adventure.sound.Sound; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.match.MatchModule; import tc.oc.pgm.api.match.MatchScope; +import tc.oc.pgm.api.match.event.MatchLoadEvent; import tc.oc.pgm.api.match.factory.MatchModuleFactory; import tc.oc.pgm.api.module.exception.ModuleLoadException; import tc.oc.pgm.api.party.Competitor; @@ -92,10 +94,6 @@ public void addGoal(Goal goal) { } goals.add(goal); - - for (Competitor competitor : match.getCompetitors()) { - addCompetitorGoal(competitor, goal); - } } private void addCompetitorGoal(Competitor competitor, Goal goal) { @@ -107,6 +105,15 @@ private void addCompetitorGoal(Competitor competitor, Goal goal) { } } + @EventHandler(priority = EventPriority.MONITOR) + public void onMatchLoad(MatchLoadEvent event) { + for (Goal goal : goals) { + for (Competitor competitor : match.getCompetitors()) { + addCompetitorGoal(competitor, goal); + } + } + } + @EventHandler public void onCompetitorAdd(CompetitorAddEvent event) { match.getLogger().fine("Competitor added " + event.getCompetitor()); diff --git a/core/src/main/java/tc/oc/pgm/variables/Variable.java b/core/src/main/java/tc/oc/pgm/variables/Variable.java index c643d8112b..a3c55309ff 100644 --- a/core/src/main/java/tc/oc/pgm/variables/Variable.java +++ b/core/src/main/java/tc/oc/pgm/variables/Variable.java @@ -1,23 +1,48 @@ package tc.oc.pgm.variables; -import tc.oc.pgm.api.feature.Feature; +import tc.oc.pgm.api.feature.FeatureDefinition; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.filters.Filterable; -public interface Variable> extends Feature> { - - @Override - default String getId() { - return getDefinition().getId(); - } +public interface Variable> extends FeatureDefinition { double getValue(Filterable context); void setValue(Filterable context, double value); + Class getScope(); + + default boolean isDynamic() { + return false; + } + default boolean isIndexed() { - return getDefinition().isIndexed(); + return false; + } + + default boolean isReadonly() { + return false; } - default void postLoad(Match match) {} + default void load(Match match) {} + + /** + * Variable that has indexing, ie: arrays Note: you must always check {@link #isIndexed} to know + * if it actually is an indexed variable or not. + * + * @param The filter type of this variable + */ + interface Indexed> extends Variable { + + @Override + default boolean isIndexed() { + return true; + } + + double getValue(Filterable context, int idx); + + void setValue(Filterable context, int idx, double value); + + int size(); + } } diff --git a/core/src/main/java/tc/oc/pgm/variables/VariableDefinition.java b/core/src/main/java/tc/oc/pgm/variables/VariableDefinition.java deleted file mode 100644 index 638c4d4949..0000000000 --- a/core/src/main/java/tc/oc/pgm/variables/VariableDefinition.java +++ /dev/null @@ -1,52 +0,0 @@ -package tc.oc.pgm.variables; - -import java.util.function.Function; -import tc.oc.pgm.api.match.Match; -import tc.oc.pgm.features.SelfIdentifyingFeatureDefinition; -import tc.oc.pgm.filters.Filterable; - -public class VariableDefinition> extends SelfIdentifyingFeatureDefinition { - - private final Class scope; - private final boolean isDynamic, isIndexed; - private final Function, Variable> builder; - - public VariableDefinition( - String id, - Class scope, - boolean isDynamic, - boolean isIndexed, - Function, Variable> builder) { - super(id); - this.scope = scope; - this.isDynamic = isDynamic; - this.isIndexed = isIndexed; - this.builder = builder; - } - - public static > VariableDefinition ofStatic( - String id, Class scope, Function, Variable> builder) { - return new VariableDefinition<>(id, scope, false, false, builder); - } - - public Class getScope() { - return scope; - } - - public boolean isDynamic() { - return isDynamic; - } - - public boolean isIndexed() { - return isIndexed; - } - - public Variable buildInstance() { - return builder.apply(this); - } - - @SuppressWarnings("unchecked") - public Variable getVariable(Match match) { - return (Variable) match.getFeatureContext().get(this.getId()); - } -} diff --git a/core/src/main/java/tc/oc/pgm/variables/VariableParser.java b/core/src/main/java/tc/oc/pgm/variables/VariableParser.java index 47b72093bc..78532e523d 100644 --- a/core/src/main/java/tc/oc/pgm/variables/VariableParser.java +++ b/core/src/main/java/tc/oc/pgm/variables/VariableParser.java @@ -5,7 +5,6 @@ import java.util.Map; import java.util.regex.Pattern; import org.jdom2.Element; -import tc.oc.pgm.api.feature.FeatureReference; import tc.oc.pgm.api.filter.Filterables; import tc.oc.pgm.api.map.factory.MapFactory; import tc.oc.pgm.api.match.Match; @@ -19,8 +18,8 @@ import tc.oc.pgm.util.xml.Node; import tc.oc.pgm.util.xml.XMLUtils; import tc.oc.pgm.variables.types.ArrayVariable; -import tc.oc.pgm.variables.types.BlitzVariable; import tc.oc.pgm.variables.types.DummyVariable; +import tc.oc.pgm.variables.types.LivesVariable; import tc.oc.pgm.variables.types.MaxBuildVariable; import tc.oc.pgm.variables.types.PlayerLocationVariable; import tc.oc.pgm.variables.types.ScoreVariable; @@ -39,7 +38,7 @@ public VariableParser(MapFactory factory) { this.methodParsers = MethodParsers.getMethodParsersForClass(getClass()); } - public VariableDefinition parse(Element el) throws InvalidXMLException { + public Variable parse(Element el) throws InvalidXMLException { String id = Node.fromRequiredAttr(el, "id").getValue(); if (!VARIABLE_ID.matcher(id).matches()) throw new InvalidXMLException( @@ -49,7 +48,7 @@ public VariableDefinition parse(Element el) throws InvalidXMLException { Method parser = methodParsers.get(el.getName().toLowerCase()); if (parser != null) { try { - return (VariableDefinition) parser.invoke(this, el, id); + return (Variable) parser.invoke(this, el); } catch (Exception e) { throw InvalidXMLException.coerce(e, new Node(el)); } @@ -59,76 +58,63 @@ public VariableDefinition parse(Element el) throws InvalidXMLException { } @MethodParser("variable") - public VariableDefinition parseDummy(Element el, String id) throws InvalidXMLException { + public Variable parseDummy(Element el) throws InvalidXMLException { Class> scope = Filterables.parse(Node.fromRequiredAttr(el, "scope")); double def = XMLUtils.parseNumber(Node.fromAttr(el, "default"), Double.class, 0d); Integer excl = XMLUtils.parseNumberInRange( Node.fromAttr(el, "exclusive"), Integer.class, Range.closed(1, 50), null); - return new VariableDefinition<>( - id, scope, true, false, vd -> new DummyVariable<>(vd, def, excl)); + return new DummyVariable<>(scope, def, excl); } @MethodParser("array") - public VariableDefinition parseArray(Element el, String id) throws InvalidXMLException { + public Variable parseArray(Element el) throws InvalidXMLException { Class> scope = Filterables.parse(Node.fromRequiredAttr(el, "scope")); int size = XMLUtils.parseNumberInRange( Node.fromRequiredAttr(el, "size"), Integer.class, Range.closed(1, 1024)); double def = XMLUtils.parseNumber(Node.fromAttr(el, "default"), Double.class, 0d); - return new VariableDefinition<>( - id, scope, true, true, vd -> new ArrayVariable<>(vd, size, def)); + return new ArrayVariable<>(scope, size, def); } @MethodParser("lives") - public VariableDefinition parseBlitzLives(Element el, String id) - throws InvalidXMLException { - return VariableDefinition.ofStatic(id, MatchPlayer.class, BlitzVariable::new); + public Variable parseBlitzLives(Element el) { + return LivesVariable.INSTANCE; } @MethodParser("score") - public VariableDefinition parseScore(Element el, String id) throws InvalidXMLException { - return VariableDefinition.ofStatic(id, Party.class, ScoreVariable::new); + public Variable parseScore(Element el) { + return ScoreVariable.INSTANCE; } @MethodParser("timelimit") - public VariableDefinition parseTimeLimit(Element el, String id) - throws InvalidXMLException { - return VariableDefinition.ofStatic(id, Match.class, TimeLimitVariable::new); + public Variable parseTimeLimit(Element el) { + return TimeLimitVariable.INSTANCE; } @MethodParser("with-team") - public VariableDefinition parseTeamAdapter(Element el, String id) - throws InvalidXMLException { + public Variable parseTeamAdapter(Element el) throws InvalidXMLException { + var features = factory.getFeatures(); @SuppressWarnings("unchecked") - VariableDefinition var = - factory.getFeatures().resolve(Node.fromRequiredAttr(el, "var"), VariableDefinition.class); + Variable var = features.resolve(Node.fromRequiredAttr(el, "var"), Variable.class); if (var.getScope() != Party.class) { throw new InvalidXMLException( "Team scope is required for with-team variable, got " + var.getScope().getSimpleName(), el); } - FeatureReference team = - factory.getFeatures().createReference(Node.fromRequiredAttr(el, "team"), TeamFactory.class); + var team = features.createReference(Node.fromRequiredAttr(el, "team"), TeamFactory.class); - return new VariableDefinition<>( - id, - Match.class, - var.isDynamic(), - var.isIndexed(), - vd -> new TeamVariableAdapter(vd, var, team)); + return new TeamVariableAdapter(var, team); } @MethodParser("maxbuildheight") - public VariableDefinition parseMaxBuild(Element el, String id) throws InvalidXMLException { - return VariableDefinition.ofStatic(id, Match.class, MaxBuildVariable::new); + public Variable parseMaxBuild(Element el) { + return MaxBuildVariable.INSTANCE; } @MethodParser("player-location") - public VariableDefinition parsePlayerLocation(Element el, String id) - throws InvalidXMLException { - PlayerLocationVariable.Component component = + public Variable parsePlayerLocation(Element el) throws InvalidXMLException { + var component = XMLUtils.parseEnum(Node.fromAttr(el, "component"), PlayerLocationVariable.Component.class); - return new VariableDefinition<>( - id, MatchPlayer.class, false, false, vd -> new PlayerLocationVariable(vd, component)); + return PlayerLocationVariable.INSTANCES.get(component); } } diff --git a/core/src/main/java/tc/oc/pgm/variables/VariablesMatchModule.java b/core/src/main/java/tc/oc/pgm/variables/VariablesMatchModule.java index c6137426b4..3eca38a68b 100644 --- a/core/src/main/java/tc/oc/pgm/variables/VariablesMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/variables/VariablesMatchModule.java @@ -1,24 +1,44 @@ package tc.oc.pgm.variables; +import java.util.Map; +import java.util.stream.Stream; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.match.MatchModule; -import tc.oc.pgm.api.module.exception.ModuleLoadException; +import tc.oc.pgm.api.match.MatchScope; +import tc.oc.pgm.api.match.event.MatchLoadEvent; +import tc.oc.pgm.events.ListenerScope; +import tc.oc.pgm.features.FeatureDefinitionContext; +import tc.oc.pgm.util.collection.ContextStore; +@ListenerScope(MatchScope.LOADED) public class VariablesMatchModule implements MatchModule, Listener { private final Match match; + private final FeatureDefinitionContext context; - public VariablesMatchModule(Match match) { + public VariablesMatchModule(Match match, FeatureDefinitionContext context) { this.match = match; + this.context = context; } - public Iterable getVariables() { - return match.getFeatureContext().getAll(Variable.class); + public Stream>> getVariables() { + //noinspection unchecked + return ((ContextStore>) context) + .stream().filter(e -> e.getValue() instanceof Variable).map(e -> + (Map.Entry>) e); } - @Override - public void load() throws ModuleLoadException { - getVariables().forEach(v -> v.postLoad(match)); + public String getId(Variable variable) { + return context.getName(variable); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onMatchLoad(MatchLoadEvent event) { + for (Variable var : context.getAll(Variable.class)) { + var.load(match); + } } } diff --git a/core/src/main/java/tc/oc/pgm/variables/VariablesModule.java b/core/src/main/java/tc/oc/pgm/variables/VariablesModule.java index a802d488a6..9a5f584c85 100644 --- a/core/src/main/java/tc/oc/pgm/variables/VariablesModule.java +++ b/core/src/main/java/tc/oc/pgm/variables/VariablesModule.java @@ -1,11 +1,9 @@ package tc.oc.pgm.variables; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import java.util.Collection; import java.util.HashMap; -import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.logging.Logger; @@ -15,79 +13,64 @@ import org.jdom2.Document; import org.jdom2.Element; import org.jetbrains.annotations.Nullable; +import tc.oc.pgm.api.feature.FeatureDefinition; import tc.oc.pgm.api.filter.Filterables; import tc.oc.pgm.api.map.MapModule; +import tc.oc.pgm.api.map.MapProtos; import tc.oc.pgm.api.map.factory.MapFactory; import tc.oc.pgm.api.map.factory.MapModuleFactory; import tc.oc.pgm.api.match.Match; -import tc.oc.pgm.api.match.MatchModule; import tc.oc.pgm.api.module.exception.ModuleLoadException; -import tc.oc.pgm.blitz.BlitzMatchModule; +import tc.oc.pgm.features.FeatureDefinitionContext; import tc.oc.pgm.filters.Filterable; -import tc.oc.pgm.regions.RegionMatchModule; -import tc.oc.pgm.score.ScoreMatchModule; -import tc.oc.pgm.teams.TeamMatchModule; import tc.oc.pgm.util.math.Formula; import tc.oc.pgm.util.xml.InvalidXMLException; import tc.oc.pgm.util.xml.XMLUtils; -import tc.oc.pgm.variables.types.IndexedVariable; +import tc.oc.pgm.variables.types.LivesVariable; +import tc.oc.pgm.variables.types.MaxBuildVariable; +import tc.oc.pgm.variables.types.PlayerLocationVariable; +import tc.oc.pgm.variables.types.ScoreVariable; +import tc.oc.pgm.variables.types.TimeLimitVariable; public class VariablesModule implements MapModule { - private final ImmutableList> variables; + private final FeatureDefinitionContext context; private final ImmutableMap>, Context> variablesByScope; - public VariablesModule(ImmutableList> variables) { - this.variables = variables; + public VariablesModule(FeatureDefinitionContext context) { + this.context = context; ImmutableMap.Builder>, Context> varsBuilder = ImmutableMap.builder(); for (Class> scope : Filterables.SCOPES) { - varsBuilder.put(scope, Context.of(scope, variables)); + varsBuilder.put(scope, Context.of(scope, context)); } this.variablesByScope = varsBuilder.build(); } - @Override - public @Nullable Collection> getWeakDependencies() { - return ImmutableList.of( - TeamMatchModule.class, - BlitzMatchModule.class, - ScoreMatchModule.class, - RegionMatchModule.class); - } - @SuppressWarnings("unchecked") public > Formula.ContextFactory getContext(Class scope) { return (Formula.ContextFactory) variablesByScope.get(scope); } - private static class Context> implements Formula.ContextFactory { - private final ImmutableSet variables; - private final ImmutableSet arrays; - private final Map> vars; - - public Context( - ImmutableSet variables, - ImmutableSet arrays, - Map> vars) { - this.variables = variables; - this.arrays = arrays; - this.vars = vars; - } + private record Context>( + ImmutableSet variables, ImmutableSet arrays, Map> vars) + implements Formula.ContextFactory { public static > Context of( - Class scope, List> variables) { + Class scope, FeatureDefinitionContext context) { ImmutableSet.Builder variableNames = ImmutableSet.builder(); ImmutableSet.Builder arrayNames = ImmutableSet.builder(); - ImmutableMap.Builder> variableMap = ImmutableMap.builder(); + ImmutableMap.Builder> variableMap = ImmutableMap.builder(); - for (VariableDefinition variable : variables) { - if (!Filterables.isAssignable(scope, variable.getScope())) continue; + for (Map.Entry definition : context) { + if (definition.getValue() instanceof Variable variable) { + if (!Filterables.isAssignable(scope, variable.getScope())) continue; - (variable.isIndexed() ? arrayNames : variableNames).add(variable.getId()); - variableMap.put(variable.getId(), variable); + (variable.isIndexed() ? arrayNames : variableNames).add(definition.getKey()); + variableMap.put(definition.getKey(), variable); + } } return new Context<>(variableNames.build(), arrayNames.build(), variableMap.build()); } @@ -104,7 +87,6 @@ public Set getArrays() { @Override public ExpressionContext withContext(T scope) { - Match match = scope.getMatch(); Map variableCache = new HashMap<>(); Map arrayCache = new HashMap<>(); @@ -116,8 +98,7 @@ public Set getVariables() { @Override public Double getVariable(String id) { - return variableCache.computeIfAbsent( - id, key -> vars.get(key).getVariable(match).getValue(scope)); + return variableCache.computeIfAbsent(id, key -> vars.get(key).getValue(scope)); } @Override @@ -127,20 +108,17 @@ public Set getFunctions() { @Override public Function getFunction(String id) { - return arrayCache.computeIfAbsent( - id, - key -> { - VariableDefinition def = vars.get(key); - if (def == null) return null; - - IndexedVariable variable = (IndexedVariable) def.getVariable(match); - return new Function(id, 1) { - @Override - public double apply(double... doubles) { - return variable.getValue(scope, (int) doubles[0]); - } - }; - }); + return arrayCache.computeIfAbsent(id, key -> { + Variable.Indexed def = (Variable.Indexed) vars.get(key); + if (def == null) return null; + + return new Function(id, 1) { + @Override + public double apply(double... doubles) { + return def.getValue(scope, (int) doubles[0]); + } + }; + }); } }; } @@ -149,11 +127,7 @@ public double apply(double... doubles) { @Nullable @Override public VariablesMatchModule createMatchModule(Match match) throws ModuleLoadException { - for (VariableDefinition varDef : this.variables) { - match.getFeatureContext().add(varDef.buildInstance()); - } - - return new VariablesMatchModule(match); + return new VariablesMatchModule(match, context); } public static class Factory implements MapModuleFactory { @@ -166,16 +140,26 @@ public static class Factory implements MapModuleFactory { public VariablesModule parse(MapFactory factory, Logger logger, Document doc) throws InvalidXMLException { - ImmutableList.Builder> variables = ImmutableList.builder(); + boolean featureIds = factory.getProto().isNoOlderThan(MapProtos.FEATURE_SINGLETON_IDS); VariableParser parser = new VariableParser(factory); + var features = factory.getFeatures(); + if (featureIds) { + features.addFeature(null, "lives", LivesVariable.INSTANCE); + features.addFeature(null, "score", ScoreVariable.INSTANCE); + features.addFeature(null, "timelimit", TimeLimitVariable.INSTANCE); + features.addFeature(null, "maxbuildheight", MaxBuildVariable.INSTANCE); + for (var entry : PlayerLocationVariable.INSTANCES.entrySet()) { + String key = "player." + entry.getKey().name().toLowerCase(Locale.ROOT); + features.addFeature(null, key, entry.getValue()); + } + } + for (Element variable : XMLUtils.flattenElements(doc.getRootElement(), "variables", null)) { - VariableDefinition varDef = parser.parse(variable); - factory.getFeatures().addFeature(variable, varDef); - variables.add(varDef); + features.addFeature(variable, parser.parse(variable)); } - return new VariablesModule(variables.build()); + return new VariablesModule(factory.getFeatures()); } } } diff --git a/core/src/main/java/tc/oc/pgm/variables/types/AbstractVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/AbstractVariable.java index a2dfbf2046..5f28d7d119 100644 --- a/core/src/main/java/tc/oc/pgm/variables/types/AbstractVariable.java +++ b/core/src/main/java/tc/oc/pgm/variables/types/AbstractVariable.java @@ -2,24 +2,18 @@ import tc.oc.pgm.filters.Filterable; import tc.oc.pgm.variables.Variable; -import tc.oc.pgm.variables.VariableDefinition; -public abstract class AbstractVariable> implements Variable { - protected final VariableDefinition definition; +abstract class AbstractVariable> implements Variable { + private final Class scope; - public AbstractVariable(VariableDefinition definition) { - this.definition = definition; + public AbstractVariable(Class scope) { + this.scope = scope; } - @Override - public VariableDefinition getDefinition() { - return definition; + public Class getScope() { + return scope; } - protected abstract double getValueImpl(T obj); - - protected abstract void setValueImpl(T obj, double value); - @Override public double getValue(Filterable context) { return getValueImpl(getAncestor(context)); @@ -30,16 +24,19 @@ public void setValue(Filterable context, double value) { setValueImpl(getAncestor(context), value); } + protected abstract double getValueImpl(T obj); + + protected abstract void setValueImpl(T obj, double value); + protected T getAncestor(Filterable context) { - T filterable = context.getFilterableAncestor(definition.getScope()); + T filterable = context.getFilterableAncestor(getScope()); if (filterable != null) return filterable; - throw new IllegalStateException( - "Wrong variable scope for '" - + getId() - + "', expected " - + definition.getScope().getSimpleName() - + " which cannot be found in " - + context.getClass().getSimpleName()); + throw new IllegalStateException("Wrong variable scope for '" + + this + + "', expected " + + getScope().getSimpleName() + + " which cannot be found in " + + context.getClass().getSimpleName()); } } diff --git a/core/src/main/java/tc/oc/pgm/variables/types/ArrayVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/ArrayVariable.java index 3efb520f68..d59de9f4b7 100644 --- a/core/src/main/java/tc/oc/pgm/variables/types/ArrayVariable.java +++ b/core/src/main/java/tc/oc/pgm/variables/types/ArrayVariable.java @@ -5,32 +5,35 @@ import java.util.Map; import java.util.logging.Level; import tc.oc.pgm.api.PGM; +import tc.oc.pgm.api.match.Match; +import tc.oc.pgm.features.StateHolder; import tc.oc.pgm.filters.FilterMatchModule; import tc.oc.pgm.filters.Filterable; -import tc.oc.pgm.variables.VariableDefinition; +import tc.oc.pgm.variables.Variable; +import tc.oc.pgm.variables.VariablesMatchModule; public class ArrayVariable> extends AbstractVariable - implements IndexedVariable { + implements Variable.Indexed, StateHolder> { private static final String OOB_ERR = "Index %d out of bounds for array '%s' (length %d)"; private final int size; private final double def; - private final Map values; - public ArrayVariable(VariableDefinition definition, int size, double def) { - super(definition); + public ArrayVariable(Class scope, int size, double def) { + super(scope); this.size = size; this.def = def; - this.values = new HashMap<>(); } - public int checkBounds(int idx) { - if (idx < 0 || idx >= size) { - PGM.get().getGameLogger().log(Level.SEVERE, String.format(OOB_ERR, idx, getId(), size)); - return 0; - } - return idx; + @Override + public boolean isDynamic() { + return true; + } + + @Override + public void load(Match match) { + match.getFeatureContext().registerState(this, new HashMap<>()); } @Override @@ -40,27 +43,35 @@ public int size() { @Override public double getValue(Filterable obj, int idx) { - double[] val = values.get(getAncestor(obj)); - return val == null ? def : val[checkBounds(idx)]; + var scope = getAncestor(obj); + double[] val = obj.state(this).get(scope); + return val == null ? def : val[checkBounds(idx, scope)]; } @Override public void setValue(Filterable obj, int idx, double value) { - values.compute( - getAncestor(obj), - (k, arr) -> { - if (arr == null) { - if (value == def) return null; - arr = buildArray(); - } - arr[checkBounds(idx)] = value; - return arr; - }); + obj.state(this).compute(getAncestor(obj), (scope, arr) -> { + if (arr == null) { + if (value == def) return null; + arr = buildArray(); + } + arr[checkBounds(idx, scope)] = value; + return arr; + }); // For performance reasons, let's avoid launching an event for every variable change obj.moduleRequire(FilterMatchModule.class).invalidate(obj); } + public int checkBounds(int idx, T obj) { + if (idx < 0 || idx >= size) { + String id = obj.moduleRequire(VariablesMatchModule.class).getId(this); + PGM.get().getGameLogger().log(Level.SEVERE, String.format(OOB_ERR, idx, id, size)); + return 0; + } + return idx; + } + private double[] buildArray() { double[] result = new double[size]; if (def != 0) Arrays.fill(result, def); diff --git a/core/src/main/java/tc/oc/pgm/variables/types/BlitzVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/BlitzVariable.java deleted file mode 100644 index bc9a200c2b..0000000000 --- a/core/src/main/java/tc/oc/pgm/variables/types/BlitzVariable.java +++ /dev/null @@ -1,30 +0,0 @@ -package tc.oc.pgm.variables.types; - -import tc.oc.pgm.api.match.Match; -import tc.oc.pgm.api.player.MatchPlayer; -import tc.oc.pgm.blitz.BlitzMatchModule; -import tc.oc.pgm.variables.VariableDefinition; - -public class BlitzVariable extends AbstractVariable { - - private BlitzMatchModule bmm; - - public BlitzVariable(VariableDefinition definition) { - super(definition); - } - - @Override - public void postLoad(Match match) { - bmm = match.moduleRequire(BlitzMatchModule.class); - } - - @Override - protected double getValueImpl(MatchPlayer player) { - return bmm.getNumOfLives(player.getId()); - } - - @Override - protected void setValueImpl(MatchPlayer player, double value) { - bmm.setLives(player, Math.max((int) value, 0)); - } -} diff --git a/core/src/main/java/tc/oc/pgm/variables/types/DummyVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/DummyVariable.java index 074a4916d0..338b1acac5 100644 --- a/core/src/main/java/tc/oc/pgm/variables/types/DummyVariable.java +++ b/core/src/main/java/tc/oc/pgm/variables/types/DummyVariable.java @@ -4,50 +4,80 @@ import java.util.HashMap; import java.util.Map; import org.jetbrains.annotations.Nullable; +import tc.oc.pgm.api.match.Match; +import tc.oc.pgm.features.StateHolder; import tc.oc.pgm.filters.FilterMatchModule; import tc.oc.pgm.filters.Filterable; -import tc.oc.pgm.variables.VariableDefinition; -public class DummyVariable> extends AbstractVariable { +public class DummyVariable> extends AbstractVariable + implements StateHolder.Values> { private final double def; - private final Map values; + private final Integer exclusive; - // Circular buffer of last additions, head marks next location to replace - private @Nullable final T[] additions; - private int head = 0; - - public DummyVariable(VariableDefinition definition, double def, Integer exclusive) { - super(definition); + public DummyVariable(Class scope, double def, Integer exclusive) { + super(scope); this.def = def; - this.values = new HashMap<>(); - //noinspection unchecked - this.additions = - exclusive == null ? null : (T[]) Array.newInstance(definition.getScope(), exclusive); + this.exclusive = exclusive; + } + + @Override + public boolean isDynamic() { + return true; + } + + @Override + public void load(Match match) { + match + .getFeatureContext() + .registerState(this, exclusive == null ? new Values() : new LimitedValues()); } @Override protected double getValueImpl(T obj) { - return values.getOrDefault(obj, def); + return obj.state(this).values.getOrDefault(obj, def); } @Override protected void setValueImpl(T obj, double value) { - Double oldVal = values.put(obj, value); - - // Limit is enabled, and we're not replacing a pre-existing key - if (additions != null && oldVal == null) { - T toRemove = additions[head]; - if (toRemove != null) { - values.remove(toRemove); - toRemove.moduleRequire(FilterMatchModule.class).invalidate(toRemove); - } - - additions[head] = obj; - head = (head + 1) % additions.length; - } + obj.state(this).setValue(obj, value); // For performance reasons, let's avoid launching an event for every variable change obj.moduleRequire(FilterMatchModule.class).invalidate(obj); } + + class Values { + protected final Map values = new HashMap<>(); + + protected void setValue(T obj, double value) { + values.put(obj, value); + } + } + + class LimitedValues extends Values { + // Circular buffer of last additions, head marks next location to replace + private final @Nullable T[] additions; + private int head = 0; + + public LimitedValues() { + //noinspection unchecked + this.additions = (T[]) Array.newInstance(getScope(), exclusive); + } + + protected void setValue(T obj, double value) { + Double oldVal = values.put(obj, value); + + // Limit is enabled, and we're not replacing a pre-existing key + if (additions != null && oldVal == null) { + T toRemove = additions[head]; + if (toRemove != null) { + values.remove(toRemove); + toRemove.moduleRequire(FilterMatchModule.class).invalidate(toRemove); + } + + additions[head] = obj; + head = (head + 1) % additions.length; + } + } + } } diff --git a/core/src/main/java/tc/oc/pgm/variables/types/IndexedVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/IndexedVariable.java deleted file mode 100644 index c9a1ac98f5..0000000000 --- a/core/src/main/java/tc/oc/pgm/variables/types/IndexedVariable.java +++ /dev/null @@ -1,13 +0,0 @@ -package tc.oc.pgm.variables.types; - -import tc.oc.pgm.filters.Filterable; -import tc.oc.pgm.variables.Variable; - -public interface IndexedVariable> extends Variable { - - double getValue(Filterable context, int idx); - - void setValue(Filterable context, int idx, double value); - - int size(); -} diff --git a/core/src/main/java/tc/oc/pgm/variables/types/LivesVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/LivesVariable.java new file mode 100644 index 0000000000..462feb5a57 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/variables/types/LivesVariable.java @@ -0,0 +1,22 @@ +package tc.oc.pgm.variables.types; + +import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.blitz.BlitzMatchModule; + +public class LivesVariable extends AbstractVariable { + public static final LivesVariable INSTANCE = new LivesVariable(); + + public LivesVariable() { + super(MatchPlayer.class); + } + + @Override + protected double getValueImpl(MatchPlayer player) { + return player.moduleRequire(BlitzMatchModule.class).getNumOfLives(player.getId()); + } + + @Override + protected void setValueImpl(MatchPlayer player, double value) { + player.moduleRequire(BlitzMatchModule.class).setLives(player, Math.max((int) value, 0)); + } +} diff --git a/core/src/main/java/tc/oc/pgm/variables/types/MaxBuildVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/MaxBuildVariable.java index f58846db51..582d312cf9 100644 --- a/core/src/main/java/tc/oc/pgm/variables/types/MaxBuildVariable.java +++ b/core/src/main/java/tc/oc/pgm/variables/types/MaxBuildVariable.java @@ -2,30 +2,24 @@ import tc.oc.pgm.api.match.Match; import tc.oc.pgm.regions.RegionMatchModule; -import tc.oc.pgm.variables.VariableDefinition; public class MaxBuildVariable extends AbstractVariable { - private RegionMatchModule rmm; + public static final MaxBuildVariable INSTANCE = new MaxBuildVariable(); - public MaxBuildVariable(VariableDefinition definition) { - super(definition); + public MaxBuildVariable() { + super(Match.class); } @Override - public void postLoad(Match match) { - rmm = match.moduleRequire(RegionMatchModule.class); - } - - @Override - protected double getValueImpl(Match player) { - Integer val = rmm.getMaxBuildHeight(); + protected double getValueImpl(Match match) { + Integer val = match.moduleRequire(RegionMatchModule.class).getMaxBuildHeight(); return val == null ? -1 : val; } @Override - protected void setValueImpl(Match player, double value) { + protected void setValueImpl(Match match, double value) { int val = (int) value; - rmm.setMaxBuildHeight(val <= -1 ? null : val); + match.moduleRequire(RegionMatchModule.class).setMaxBuildHeight(val <= -1 ? null : val); } } diff --git a/core/src/main/java/tc/oc/pgm/variables/types/PlayerLocationVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/PlayerLocationVariable.java index d4717c54b2..4804b93590 100644 --- a/core/src/main/java/tc/oc/pgm/variables/types/PlayerLocationVariable.java +++ b/core/src/main/java/tc/oc/pgm/variables/types/PlayerLocationVariable.java @@ -3,63 +3,46 @@ import static java.lang.Math.toRadians; import static tc.oc.pgm.util.nms.PlayerUtils.PLAYER_UTILS; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Map; import java.util.function.ToDoubleFunction; import org.bukkit.Location; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.util.block.RayBlockIntersection; -import tc.oc.pgm.variables.VariableDefinition; public class PlayerLocationVariable extends AbstractVariable { + public static final Map INSTANCES; + + static { + var values = new EnumMap(Component.class); + for (Component component : Component.values()) { + values.put(component, new PlayerLocationVariable(component)); + } + INSTANCES = Collections.unmodifiableMap(values); + } + private static final double NULL_VALUE = -1; + private static RayCastCache lastRaytrace; + + record RayCastCache(Location location, RayBlockIntersection rayCast) {} private final Component component; - private Location lastLocation; - private RayBlockIntersection lastRayCast; - public PlayerLocationVariable(VariableDefinition definition, Component component) { - super(definition); + public PlayerLocationVariable(Component component) { + super(MatchPlayer.class); this.component = component; } - private RayBlockIntersection intersection(MatchPlayer player) { - if (player.getLocation().equals(lastLocation)) { - return lastRayCast; - } - lastLocation = player.getLocation().clone(); - lastRayCast = PLAYER_UTILS.getTargetedBlock(player.getBukkit()); - return lastRayCast; + @Override + public boolean isReadonly() { + return true; } @Override protected double getValueImpl(MatchPlayer player) { - return switch (component) { - case X -> player.getLocation().getX(); - case Y -> player.getLocation().getY(); - case Z -> player.getLocation().getZ(); - case PITCH -> player.getLocation().getPitch(); - case YAW -> player.getLocation().getYaw(); - case DIR_X -> -Math.cos(toRadians(player.getLocation().getPitch())) - * Math.sin(toRadians(player.getLocation().getYaw())); - case DIR_Y -> -Math.sin(toRadians(player.getLocation().getPitch())); - case DIR_Z -> Math.cos(toRadians(player.getLocation().getPitch())) - * Math.cos(toRadians(player.getLocation().getYaw())); - case VEL_X -> player.getBukkit().getVelocity().getX(); - case VEL_Y -> player.getBukkit().getVelocity().getY(); - case VEL_Z -> player.getBukkit().getVelocity().getZ(); - case TARGET_X -> getOrDefault(intersection(player), (i) -> i.getBlock().getX()); - case TARGET_Y -> getOrDefault(intersection(player), (i) -> i.getBlock().getY()); - case TARGET_Z -> getOrDefault(intersection(player), (i) -> i.getBlock().getZ()); - case PLACE_X -> getOrDefault(intersection(player), (i) -> i.getPlaceAt().getX()); - case PLACE_Y -> getOrDefault(intersection(player), (i) -> i.getPlaceAt().getY()); - case PLACE_Z -> getOrDefault(intersection(player), (i) -> i.getPlaceAt().getZ()); - case HAS_TARGET -> intersection(player) == null ? 0 : 1; - }; - } - - private double getOrDefault( - RayBlockIntersection intersection, ToDoubleFunction function) { - return intersection == null ? NULL_VALUE : function.applyAsDouble(intersection); + return component.getter.applyAsDouble(player); } @Override @@ -68,23 +51,47 @@ protected void setValueImpl(MatchPlayer obj, double value) { } public enum Component { - X, - Y, - Z, - PITCH, - YAW, - DIR_X, - DIR_Y, - DIR_Z, - VEL_X, - VEL_Y, - VEL_Z, - TARGET_X, - TARGET_Y, - TARGET_Z, - PLACE_X, - PLACE_Y, - PLACE_Z, - HAS_TARGET, + X(p -> p.getLocation().getX()), + Y(p -> p.getLocation().getY()), + Z(p -> p.getLocation().getZ()), + PITCH(p -> p.getLocation().getPitch()), + YAW(p -> p.getLocation().getYaw()), + DIR_X(p -> -Math.cos(toRadians(p.getLocation().getPitch())) + * Math.sin(toRadians(p.getLocation().getYaw()))), + DIR_Y(p -> -Math.sin(toRadians(p.getLocation().getPitch()))), + DIR_Z(p -> Math.cos(toRadians(p.getLocation().getPitch())) + * Math.cos(toRadians(p.getLocation().getYaw()))), + VEL_X(p -> p.getBukkit().getVelocity().getX()), + VEL_Y(p -> p.getBukkit().getVelocity().getY()), + VEL_Z(p -> p.getBukkit().getVelocity().getZ()), + TARGET_X(p -> intersection(p, i -> i.getBlock().getX())), + TARGET_Y(p -> intersection(p, i -> i.getBlock().getY())), + TARGET_Z(p -> intersection(p, i -> i.getBlock().getZ())), + PLACE_X(p -> intersection(p, i -> i.getPlaceAt().getX())), + PLACE_Y(p -> intersection(p, i -> i.getPlaceAt().getY())), + PLACE_Z(p -> intersection(p, i -> i.getPlaceAt().getZ())), + HAS_TARGET(p -> intersection(p) == null ? 0 : 1); + + private final ToDoubleFunction getter; + + Component(ToDoubleFunction getter) { + this.getter = getter; + } + } + + private static RayBlockIntersection intersection(MatchPlayer player) { + RayCastCache cache = lastRaytrace; + if (player.getLocation().equals(cache.location)) { + return cache.rayCast; + } + lastRaytrace = cache = new RayCastCache( + player.getLocation().clone(), PLAYER_UTILS.getTargetedBlock(player.getBukkit())); + return cache.rayCast; + } + + private static double intersection( + MatchPlayer player, ToDoubleFunction toDouble) { + var intersection = intersection(player); + return intersection == null ? NULL_VALUE : toDouble.applyAsDouble(intersection); } } diff --git a/core/src/main/java/tc/oc/pgm/variables/types/ScoreVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/ScoreVariable.java index 9cbb44065c..666b09ed21 100644 --- a/core/src/main/java/tc/oc/pgm/variables/types/ScoreVariable.java +++ b/core/src/main/java/tc/oc/pgm/variables/types/ScoreVariable.java @@ -1,32 +1,26 @@ package tc.oc.pgm.variables.types; -import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.party.Competitor; import tc.oc.pgm.api.party.Party; import tc.oc.pgm.score.ScoreMatchModule; -import tc.oc.pgm.variables.VariableDefinition; public class ScoreVariable extends AbstractVariable { + public static final ScoreVariable INSTANCE = new ScoreVariable(); - private ScoreMatchModule smm; - - public ScoreVariable(VariableDefinition definition) { - super(definition); - } - - @Override - public void postLoad(Match match) { - smm = match.moduleRequire(ScoreMatchModule.class); + public ScoreVariable() { + super(Party.class); } @Override protected double getValueImpl(Party party) { - if (party instanceof Competitor) return smm.getScore((Competitor) party); + if (party instanceof Competitor) + return party.moduleRequire(ScoreMatchModule.class).getScore((Competitor) party); return 0; } @Override protected void setValueImpl(Party party, double value) { - if (party instanceof Competitor) smm.setScore((Competitor) party, value); + if (party instanceof Competitor) + party.moduleRequire(ScoreMatchModule.class).setScore((Competitor) party, value); } } diff --git a/core/src/main/java/tc/oc/pgm/variables/types/TeamVariableAdapter.java b/core/src/main/java/tc/oc/pgm/variables/types/TeamVariableAdapter.java index 6521cbaf9b..88c57c45ce 100644 --- a/core/src/main/java/tc/oc/pgm/variables/types/TeamVariableAdapter.java +++ b/core/src/main/java/tc/oc/pgm/variables/types/TeamVariableAdapter.java @@ -3,57 +3,68 @@ import tc.oc.pgm.api.feature.FeatureReference; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.party.Party; +import tc.oc.pgm.features.StateHolder; import tc.oc.pgm.filters.Filterable; import tc.oc.pgm.teams.Team; import tc.oc.pgm.teams.TeamFactory; import tc.oc.pgm.teams.TeamMatchModule; import tc.oc.pgm.variables.Variable; -import tc.oc.pgm.variables.VariableDefinition; -public class TeamVariableAdapter extends AbstractVariable implements IndexedVariable { +public class TeamVariableAdapter extends AbstractVariable + implements Variable.Indexed, StateHolder { - private final VariableDefinition childRef; + private final Variable child; private final FeatureReference teamRef; - private Variable child; - private Team team; - - public TeamVariableAdapter( - VariableDefinition definition, - VariableDefinition childRef, - FeatureReference teamRef) { - super(definition); - this.childRef = childRef; + public TeamVariableAdapter(Variable child, FeatureReference teamRef) { + super(Match.class); + this.child = child; this.teamRef = teamRef; } - public void postLoad(Match match) { - team = match.needModule(TeamMatchModule.class).getTeam(teamRef.get()); - child = childRef.getVariable(match); + @Override + public boolean isDynamic() { + return child.isDynamic(); + } + + @Override + public boolean isReadonly() { + return child.isReadonly(); + } + + @Override + public boolean isIndexed() { + return child.isIndexed(); + } + + public void load(Match match) { + match + .getFeatureContext() + .registerState(this, match.needModule(TeamMatchModule.class).getTeam(teamRef.get())); } @Override protected double getValueImpl(Match match) { - return child.getValue(team); + return child.getValue(match.state(this)); } @Override protected void setValueImpl(Match match, double value) { - child.setValue(team, value); + child.setValue(match.state(this), value); } @Override public double getValue(Filterable context, int idx) { - return ((IndexedVariable) child).getValue(team, idx); + return ((Variable.Indexed) child).getValue(context.state(this), idx); } @Override public void setValue(Filterable context, int idx, double value) { - ((IndexedVariable) child).setValue(team, idx, value); + ((Variable.Indexed) child).setValue(context.state(this), idx, value); } @Override public int size() { - return ((IndexedVariable) child).size(); + return ((Variable.Indexed) child).size(); } } diff --git a/core/src/main/java/tc/oc/pgm/variables/types/TimeLimitVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/TimeLimitVariable.java index 195f6113bd..386eb9106b 100644 --- a/core/src/main/java/tc/oc/pgm/variables/types/TimeLimitVariable.java +++ b/core/src/main/java/tc/oc/pgm/variables/types/TimeLimitVariable.java @@ -3,49 +3,64 @@ import java.time.Duration; import java.time.temporal.ChronoUnit; import tc.oc.pgm.api.match.Match; +import tc.oc.pgm.features.StateHolder; import tc.oc.pgm.timelimit.TimeLimit; import tc.oc.pgm.timelimit.TimeLimitMatchModule; -import tc.oc.pgm.variables.VariableDefinition; -public class TimeLimitVariable extends AbstractVariable { +public class TimeLimitVariable extends AbstractVariable + implements StateHolder { - private TimeLimit oldTimeLimit; - private TimeLimitMatchModule tlmm; + public static final TimeLimitVariable INSTANCE = new TimeLimitVariable(); - public TimeLimitVariable(VariableDefinition definition) { - super(definition); - oldTimeLimit = - new TimeLimit(null, Duration.of(0, ChronoUnit.SECONDS), null, null, null, null, true); + public TimeLimitVariable() { + super(Match.class); } @Override - public void postLoad(Match match) { - tlmm = match.moduleRequire(TimeLimitMatchModule.class); + public void load(Match match) { + match + .getFeatureContext() + .registerState( + this, + new State( + match.moduleRequire(TimeLimitMatchModule.class), + new TimeLimit( + null, Duration.of(0, ChronoUnit.SECONDS), null, null, null, null, true))); } @Override protected double getValueImpl(Match obj) { - Duration remaining = tlmm.getFinalRemaining(); + Duration remaining = obj.state(this).tlmm.getFinalRemaining(); return remaining == null ? -1 : remaining.getSeconds(); } @Override protected void setValueImpl(Match obj, double value) { - TimeLimit existingTimeLimit = tlmm.getTimeLimit(); + var state = obj.state(this); + TimeLimit existingTimeLimit = state.tlmm.getTimeLimit(); if (value < 0) { if (existingTimeLimit != null) { - oldTimeLimit = existingTimeLimit; + state.oldTimeLimit = existingTimeLimit; } - tlmm.cancel(); + state.tlmm.cancel(); return; } - TimeLimit newTimeLimit = - new TimeLimit( - existingTimeLimit != null ? existingTimeLimit : oldTimeLimit, - Duration.of((long) value, ChronoUnit.SECONDS)); - tlmm.setTimeLimit(newTimeLimit); - tlmm.start(); + TimeLimit newTimeLimit = new TimeLimit( + existingTimeLimit != null ? existingTimeLimit : state.oldTimeLimit, + Duration.of((long) value, ChronoUnit.SECONDS)); + state.tlmm.setTimeLimit(newTimeLimit); + state.tlmm.start(); + } + + public static class State { + private final TimeLimitMatchModule tlmm; + private TimeLimit oldTimeLimit; + + public State(TimeLimitMatchModule tlmm, TimeLimit oldTimeLimit) { + this.tlmm = tlmm; + this.oldTimeLimit = oldTimeLimit; + } } } diff --git a/util/src/main/java/tc/oc/pgm/util/collection/ContextStore.java b/util/src/main/java/tc/oc/pgm/util/collection/ContextStore.java index fe0ce4ca21..9bb0ba62e4 100644 --- a/util/src/main/java/tc/oc/pgm/util/collection/ContextStore.java +++ b/util/src/main/java/tc/oc/pgm/util/collection/ContextStore.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.stream.Stream; public class ContextStore implements Iterable> { protected final Map store = Maps.newTreeMap(); @@ -16,6 +17,10 @@ public Iterator> iterator() { return this.store.entrySet().iterator(); } + public Stream> stream() { + return this.store.entrySet().stream(); + } + public boolean contains(String name) { return store.containsKey(name); }