From 73afe50889ff1e75b06c82aac1cccc0de5f42e7a Mon Sep 17 00:00:00 2001 From: Pablo Herrera Date: Thu, 31 Aug 2023 08:03:51 +0200 Subject: [PATCH] Add Score & team-rescoping variables (#1220) Signed-off-by: Pablo Herrera --- .../java/tc/oc/pgm/action/ActionParser.java | 3 +- .../tc/oc/pgm/broadcast/BroadcastModule.java | 3 +- .../oc/pgm/consumable/ConsumableModule.java | 5 +- .../pgm/controlpoint/ControlPointParser.java | 1 - .../oc/pgm/enderchest/EnderChestModule.java | 1 - .../filters/matcher/match/VariableFilter.java | 3 +- .../filters/parse/FeatureFilterParser.java | 4 - .../tc/oc/pgm/filters/parse/FilterParser.java | 14 +-- .../java/tc/oc/pgm/goals/ProximityMetric.java | 5 +- .../main/java/tc/oc/pgm/kits/KitParser.java | 3 +- .../main/java/tc/oc/pgm/map/MapInfoImpl.java | 8 +- .../java/tc/oc/pgm/map/WorldInfoImpl.java | 1 - .../oc/pgm/projectile/ProjectileModule.java | 5 +- .../main/java/tc/oc/pgm/score/MercyRule.java | 19 ++-- .../tc/oc/pgm/score/ScoreMatchModule.java | 11 ++- .../tc/oc/pgm/variables/BlitzVariable.java | 33 ------- .../tc/oc/pgm/variables/DummyVariable.java | 48 ---------- .../java/tc/oc/pgm/variables/Variable.java | 3 + .../oc/pgm/variables/VariableDefinition.java | 29 +++--- .../tc/oc/pgm/variables/VariableParser.java | 92 +++++++++++++++++++ .../tc/oc/pgm/variables/VariableType.java | 33 ------- .../pgm/variables/VariablesMatchModule.java | 17 +++- .../tc/oc/pgm/variables/VariablesModule.java | 39 +++----- .../pgm/variables/types/AbstractVariable.java | 45 +++++++++ .../oc/pgm/variables/types/BlitzVariable.java | 30 ++++++ .../oc/pgm/variables/types/DummyVariable.java | 31 +++++++ .../oc/pgm/variables/types/ScoreVariable.java | 32 +++++++ .../variables/types/TeamVariableAdapter.java | 43 +++++++++ .../java/tc/oc/pgm/util/xml/XMLUtils.java | 28 +++--- 29 files changed, 366 insertions(+), 223 deletions(-) delete mode 100644 core/src/main/java/tc/oc/pgm/variables/BlitzVariable.java delete mode 100644 core/src/main/java/tc/oc/pgm/variables/DummyVariable.java create mode 100644 core/src/main/java/tc/oc/pgm/variables/VariableParser.java delete mode 100644 core/src/main/java/tc/oc/pgm/variables/VariableType.java create mode 100644 core/src/main/java/tc/oc/pgm/variables/types/AbstractVariable.java create mode 100644 core/src/main/java/tc/oc/pgm/variables/types/BlitzVariable.java create mode 100644 core/src/main/java/tc/oc/pgm/variables/types/DummyVariable.java create mode 100644 core/src/main/java/tc/oc/pgm/variables/types/ScoreVariable.java create mode 100644 core/src/main/java/tc/oc/pgm/variables/types/TeamVariableAdapter.java 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 1a13ee1933..55289fb311 100644 --- a/core/src/main/java/tc/oc/pgm/action/ActionParser.java +++ b/core/src/main/java/tc/oc/pgm/action/ActionParser.java @@ -246,8 +246,7 @@ public MessageAction parseChatMessage(Element el, Class scope) throws Invalid @MethodParser("sound") public SoundAction parseSoundAction(Element el, Class scope) throws InvalidXMLException { SoundType soundType = - XMLUtils.parseEnum( - Node.fromAttr(el, "preset"), SoundType.class, "preset", SoundType.CUSTOM); + XMLUtils.parseEnum(Node.fromAttr(el, "preset"), SoundType.class, SoundType.CUSTOM); Node resourceNode = Node.fromAttr(el, "key"); String resource = resourceNode == null ? soundType.getResource() : resourceNode.getValue(); diff --git a/core/src/main/java/tc/oc/pgm/broadcast/BroadcastModule.java b/core/src/main/java/tc/oc/pgm/broadcast/BroadcastModule.java index 4d1a22477d..74a410eb29 100644 --- a/core/src/main/java/tc/oc/pgm/broadcast/BroadcastModule.java +++ b/core/src/main/java/tc/oc/pgm/broadcast/BroadcastModule.java @@ -42,8 +42,7 @@ public BroadcastModule parse(MapFactory factory, Logger logger, Document doc) for (Element elBroadcast : elBroadcasts.getChildren()) { final Node nodeBroadcast = new Node(elBroadcast); Broadcast.Type type = - XMLUtils.parseEnum( - nodeBroadcast, elBroadcast.getName(), Broadcast.Type.class, "broadcast type"); + XMLUtils.parseEnum(nodeBroadcast, elBroadcast.getName(), Broadcast.Type.class); Component message = XMLUtils.parseFormattedText(nodeBroadcast); diff --git a/core/src/main/java/tc/oc/pgm/consumable/ConsumableModule.java b/core/src/main/java/tc/oc/pgm/consumable/ConsumableModule.java index 202fdc493b..9bd49f946b 100644 --- a/core/src/main/java/tc/oc/pgm/consumable/ConsumableModule.java +++ b/core/src/main/java/tc/oc/pgm/consumable/ConsumableModule.java @@ -59,10 +59,7 @@ public Collection>> getWeakDependencies() { actionParser.parseReference(actionNode, MatchPlayer.class); ConsumeCause cause = - XMLUtils.parseEnum( - Node.fromRequiredAttr(consumableElement, "on"), - ConsumeCause.class, - "consume cause"); + XMLUtils.parseEnum(Node.fromRequiredAttr(consumableElement, "on"), ConsumeCause.class); ConsumableDefinition consumableDefinition = new ConsumableDefinition(id, action, cause, override); diff --git a/core/src/main/java/tc/oc/pgm/controlpoint/ControlPointParser.java b/core/src/main/java/tc/oc/pgm/controlpoint/ControlPointParser.java index 69cdc40035..fb10205893 100644 --- a/core/src/main/java/tc/oc/pgm/controlpoint/ControlPointParser.java +++ b/core/src/main/java/tc/oc/pgm/controlpoint/ControlPointParser.java @@ -142,7 +142,6 @@ public static ControlPointDefinition parseControlPoint( XMLUtils.parseEnum( Node.fromAttr(el, "capture-rule"), ControlPointDefinition.CaptureCondition.class, - "capture rule", ControlPointDefinition.CaptureCondition.EXCLUSIVE); if (pd) { diff --git a/core/src/main/java/tc/oc/pgm/enderchest/EnderChestModule.java b/core/src/main/java/tc/oc/pgm/enderchest/EnderChestModule.java index f23cca525a..81663e35a8 100644 --- a/core/src/main/java/tc/oc/pgm/enderchest/EnderChestModule.java +++ b/core/src/main/java/tc/oc/pgm/enderchest/EnderChestModule.java @@ -51,7 +51,6 @@ public EnderChestModule parse(MapFactory factory, Logger logger, Document doc) XMLUtils.parseEnum( Node.fromAttr(enderchestEl, "fallback"), DropoffFallback.class, - "fallback", DropoffFallback.AUTO); enabled = true; } 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 af3d55f621..2dc99a1693 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 @@ -37,8 +37,7 @@ public boolean respondsTo(Class queryType) { @Override public boolean isDynamic() { - // Variables' setValue will always invalidate the filterable directly, no events required - return true; + return variable.isDynamic(); } @Override 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 a3946bd669..2f7dd98016 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 @@ -21,7 +21,6 @@ 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.VariableType; import tc.oc.pgm.variables.VariablesModule; public class FeatureFilterParser extends FilterParser { @@ -100,9 +99,6 @@ public Filter parseNot(Element el) throws InvalidXMLException { if (varMatch.matches()) { VariableDefinition variable = features.resolve(node, varMatch.group(1), VariableDefinition.class); - if (!variable.getVariableType().equals(VariableType.DUMMY)) { - throw new InvalidXMLException("Variable filters only support dummy variables!", node); - } Range range = XMLUtils.parseNumericRange(node, varMatch.group(2), Double.class); return new VariableFilter(variable, range); } 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 d99cbabb4c..a69d242807 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 @@ -93,7 +93,6 @@ 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.VariableType; public abstract class FilterParser implements XMLParser { @@ -295,7 +294,7 @@ public VoidFilter parseVoid(Element el) throws InvalidXMLException { @MethodParser("entity") public EntityTypeFilter parseEntity(Element el) throws InvalidXMLException { - return new EntityTypeFilter(XMLUtils.parseEnum(el, EntityType.class, "entity type")); + return new EntityTypeFilter(XMLUtils.parseEnum(el, EntityType.class)); } @MethodParser("mob") @@ -309,8 +308,7 @@ public EntityTypeFilter parseMob(Element el) throws InvalidXMLException { @MethodParser("spawn") public SpawnReasonFilter parseSpawnReason(Element el) throws InvalidXMLException { - return new SpawnReasonFilter( - XMLUtils.parseEnum(new Node(el), SpawnReason.class, "spawn reason")); + return new SpawnReasonFilter(XMLUtils.parseEnum(new Node(el), SpawnReason.class)); } @MethodParser("kill-streak") @@ -465,13 +463,12 @@ public CarryingFlagFilter parseCarryingFlag(Element el) throws InvalidXMLExcepti @MethodParser("cause") public CauseFilter parseCause(Element el) throws InvalidXMLException { - return new CauseFilter(XMLUtils.parseEnum(el, CauseFilter.Cause.class, "cause filter")); + return new CauseFilter(XMLUtils.parseEnum(el, CauseFilter.Cause.class)); } @MethodParser("relation") public RelationFilter parseRelation(Element el) throws InvalidXMLException { - return new RelationFilter( - XMLUtils.parseEnum(el, PlayerRelation.class, "player relation filter")); + return new RelationFilter(XMLUtils.parseEnum(el, PlayerRelation.class)); } @MethodParser("carrying") @@ -662,9 +659,6 @@ public PlayerCountFilter parsePlayerCountFilter(Element el) throws InvalidXMLExc public Filter parseVariableFilter(Element el) throws InvalidXMLException { VariableDefinition varDef = features.resolve(Node.fromRequiredAttr(el, "var"), VariableDefinition.class); - if (!varDef.getVariableType().equals(VariableType.DUMMY)) { - throw new InvalidXMLException("Variable filters only support dummy variables!", el); - } Range range = XMLUtils.parseNumericRange(new Node(el), Double.class); if (varDef.getScope() == Party.class) diff --git a/core/src/main/java/tc/oc/pgm/goals/ProximityMetric.java b/core/src/main/java/tc/oc/pgm/goals/ProximityMetric.java index ef780ebd80..d9b1fbf1a5 100644 --- a/core/src/main/java/tc/oc/pgm/goals/ProximityMetric.java +++ b/core/src/main/java/tc/oc/pgm/goals/ProximityMetric.java @@ -70,10 +70,7 @@ public int hashCode() { ProximityMetric.Type type = XMLUtils.parseEnum( - Node.fromAttr(el, prefix + "proximity-metric"), - ProximityMetric.Type.class, - "proximity metric", - def.type); + Node.fromAttr(el, prefix + "proximity-metric"), ProximityMetric.Type.class, def.type); // If proximity metric is none, use null proximity so that it doesn't try to get tracked nor // shows in the scoreboard diff --git a/core/src/main/java/tc/oc/pgm/kits/KitParser.java b/core/src/main/java/tc/oc/pgm/kits/KitParser.java index acdcea73b9..acf477aabd 100644 --- a/core/src/main/java/tc/oc/pgm/kits/KitParser.java +++ b/core/src/main/java/tc/oc/pgm/kits/KitParser.java @@ -414,8 +414,7 @@ public ItemStack parseFirework(Element el) throws InvalidXMLException { meta.setPower(power); for (Element explosionEl : el.getChildren("explosion")) { - Type type = - XMLUtils.parseEnum(Node.fromAttr(explosionEl, "type"), Type.class, null, Type.BURST); + Type type = XMLUtils.parseEnum(Node.fromAttr(explosionEl, "type"), Type.class, Type.BURST); boolean flicker = XMLUtils.parseBoolean(Node.fromAttr(explosionEl, "flicker"), false); boolean trail = XMLUtils.parseBoolean(Node.fromAttr(explosionEl, "trail"), false); diff --git a/core/src/main/java/tc/oc/pgm/map/MapInfoImpl.java b/core/src/main/java/tc/oc/pgm/map/MapInfoImpl.java index 574c292deb..e74fed8fdc 100644 --- a/core/src/main/java/tc/oc/pgm/map/MapInfoImpl.java +++ b/core/src/main/java/tc/oc/pgm/map/MapInfoImpl.java @@ -115,17 +115,13 @@ public MapInfoImpl(MapSource source, Element root) throws InvalidXMLException { this.rules = parseRules(root); this.difficulty = XMLUtils.parseEnum( - Node.fromLastChildOrAttr(root, "difficulty"), - Difficulty.class, - "difficulty", - Difficulty.NORMAL) + Node.fromLastChildOrAttr(root, "difficulty"), Difficulty.class, Difficulty.NORMAL) .ordinal(); this.world = parseWorld(root); this.gamemode = XMLUtils.parseFormattedText(root, "game"); this.gamemodes = parseGamemodes(root); this.phase = - XMLUtils.parseEnum( - Node.fromLastChildOrAttr(root, "phase"), Phase.class, "phase", Phase.PRODUCTION); + XMLUtils.parseEnum(Node.fromLastChildOrAttr(root, "phase"), Phase.class, Phase.PRODUCTION); this.friendlyFire = XMLUtils.parseBoolean( Node.fromLastChildOrAttr(root, "friendlyfire", "friendly-fire"), false); diff --git a/core/src/main/java/tc/oc/pgm/map/WorldInfoImpl.java b/core/src/main/java/tc/oc/pgm/map/WorldInfoImpl.java index 57780a860e..059af3c563 100644 --- a/core/src/main/java/tc/oc/pgm/map/WorldInfoImpl.java +++ b/core/src/main/java/tc/oc/pgm/map/WorldInfoImpl.java @@ -34,7 +34,6 @@ public WorldInfoImpl(Element element) throws InvalidXMLException { XMLUtils.parseEnum( Node.fromLastChildOrAttr(element, "environment"), World.Environment.class, - "environment", World.Environment.NORMAL) .ordinal()); } diff --git a/core/src/main/java/tc/oc/pgm/projectile/ProjectileModule.java b/core/src/main/java/tc/oc/pgm/projectile/ProjectileModule.java index 5fbfc4406e..80f3ddbefd 100644 --- a/core/src/main/java/tc/oc/pgm/projectile/ProjectileModule.java +++ b/core/src/main/java/tc/oc/pgm/projectile/ProjectileModule.java @@ -62,10 +62,7 @@ public ProjectileModule parse(MapFactory factory, Logger logger, Document doc) Node.fromChildOrAttr(projectileElement, "velocity"), Double.class, 1.0); ClickAction clickAction = XMLUtils.parseEnum( - Node.fromAttr(projectileElement, "click"), - ClickAction.class, - "click action", - ClickAction.BOTH); + Node.fromAttr(projectileElement, "click"), ClickAction.class, ClickAction.BOTH); Class entity = XMLUtils.parseEntityTypeAttribute(projectileElement, "projectile", Arrow.class); List potionKit = kitParser.parsePotions(projectileElement); diff --git a/core/src/main/java/tc/oc/pgm/score/MercyRule.java b/core/src/main/java/tc/oc/pgm/score/MercyRule.java index 10a377d887..63eb500057 100644 --- a/core/src/main/java/tc/oc/pgm/score/MercyRule.java +++ b/core/src/main/java/tc/oc/pgm/score/MercyRule.java @@ -4,6 +4,7 @@ import java.util.Map; import tc.oc.pgm.api.party.Competitor; import tc.oc.pgm.api.party.event.CompetitorScoreChangeEvent; +import tc.oc.pgm.util.Pair; public class MercyRule { @@ -12,8 +13,8 @@ public class MercyRule { private final int mercyLimit; private final int mercyLimitMin; - private Map.Entry leader; - private Map.Entry trailer; + private Pair leader; + private Pair trailer; public MercyRule( ScoreMatchModule scoreMatchModule, int scoreLimit, int mercyLimit, int mercyLimitMin) { @@ -26,27 +27,27 @@ public MercyRule( } private double getLeaderScore() { - return leader.getValue(); + return leader.getRight(); } private double getTrailerScore() { - return trailer.getValue(); + return trailer.getRight(); } private void setLeader(Competitor competitor, Double score) { - leader = new AbstractMap.SimpleEntry<>(competitor, score); + leader = Pair.of(competitor, score); } private void setTrailer(Competitor competitor, Double score) { - trailer = new AbstractMap.SimpleEntry<>(competitor, score); + trailer = Pair.of(competitor, score); } private boolean isLeader(Competitor competitor) { - return competitor.equals(leader.getKey()); + return competitor.equals(leader.getLeft()); } private boolean isTrailer(Competitor competitor) { - return competitor.equals(trailer.getKey()); + return competitor.equals(trailer.getLeft()); } public int getScoreLimit() { @@ -83,7 +84,7 @@ public void handleEvent(CompetitorScoreChangeEvent event) { if (event.getOldScore() > event.getNewScore()) { if (isLeader(event.getCompetitor()) || isTrailer(event.getCompetitor()) - || trailer.getKey() == null) { + || trailer.getLeft() == null) { calculateLeaders(); } } diff --git a/core/src/main/java/tc/oc/pgm/score/ScoreMatchModule.java b/core/src/main/java/tc/oc/pgm/score/ScoreMatchModule.java index 57217f7a88..9e90433c0c 100644 --- a/core/src/main/java/tc/oc/pgm/score/ScoreMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/score/ScoreMatchModule.java @@ -26,6 +26,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.filter.Filter; import tc.oc.pgm.api.match.Match; @@ -99,7 +100,7 @@ public Map getScores() { return this.scores; } - public double getScore(Competitor competitor) { + public double getScore(@NotNull Competitor competitor) { return this.scores.get(competitor); } @@ -319,10 +320,18 @@ public void incrementScore(UUID player, Competitor competitor, double amount) { } } + public void setScore(@NotNull Competitor competitor, double value) { + double curr = getScore(competitor); + if (curr != value) setScore(competitor, curr, value); + } + public void incrementScore(Competitor competitor, double amount) { double oldScore = this.scores.get(competitor); double newScore = oldScore + amount; + setScore(competitor, oldScore, newScore); + } + private void setScore(Competitor competitor, double oldScore, double newScore) { if (this.config.scoreLimit > 0 && newScore > this.config.scoreLimit) { newScore = this.config.scoreLimit; } diff --git a/core/src/main/java/tc/oc/pgm/variables/BlitzVariable.java b/core/src/main/java/tc/oc/pgm/variables/BlitzVariable.java deleted file mode 100644 index 473984691a..0000000000 --- a/core/src/main/java/tc/oc/pgm/variables/BlitzVariable.java +++ /dev/null @@ -1,33 +0,0 @@ -package tc.oc.pgm.variables; - -import tc.oc.pgm.api.player.MatchPlayer; -import tc.oc.pgm.blitz.BlitzMatchModule; -import tc.oc.pgm.filters.Filterable; - -public class BlitzVariable implements Variable { - - private final VariableDefinition definition; - - public BlitzVariable(VariableDefinition> definition) { - this.definition = (VariableDefinition) definition; - } - - @Override - public VariableDefinition getDefinition() { - return definition; - } - - @Override - public double getValue(Filterable context) { - MatchPlayer matchPlayer = context.getFilterableAncestor(MatchPlayer.class); - BlitzMatchModule blitzMatchModule = matchPlayer.moduleRequire(BlitzMatchModule.class); - return blitzMatchModule.getNumOfLives(matchPlayer.getId()); - } - - @Override - public void setValue(Filterable context, double value) { - MatchPlayer matchPlayer = context.getFilterableAncestor(MatchPlayer.class); - BlitzMatchModule blitzMatchModule = matchPlayer.moduleRequire(BlitzMatchModule.class); - blitzMatchModule.setLives(matchPlayer, Math.max((int) value, 0)); - } -} diff --git a/core/src/main/java/tc/oc/pgm/variables/DummyVariable.java b/core/src/main/java/tc/oc/pgm/variables/DummyVariable.java deleted file mode 100644 index c2c05f5a8b..0000000000 --- a/core/src/main/java/tc/oc/pgm/variables/DummyVariable.java +++ /dev/null @@ -1,48 +0,0 @@ -package tc.oc.pgm.variables; - -import java.util.HashMap; -import java.util.Map; -import tc.oc.pgm.filters.FilterMatchModule; -import tc.oc.pgm.filters.Filterable; - -public class DummyVariable> implements Variable { - - private final VariableDefinition definition; - private final Map values; - - public DummyVariable(VariableDefinition definition) { - this.definition = definition; - this.values = new HashMap<>(); - } - - @Override - public VariableDefinition getDefinition() { - return definition; - } - - @Override - public double getValue(Filterable context) { - return values.computeIfAbsent(getAncestor(context), k -> definition.getDefault()); - } - - @Override - public void setValue(Filterable context, double value) { - T ctx = getAncestor(context); - values.put(ctx, value); - // For performance reasons, let's avoid launching an event for every variable change - context.getMatch().needModule(FilterMatchModule.class).invalidate(ctx); - } - - private T getAncestor(Filterable context) { - T filterable = context.getFilterableAncestor(definition.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()); - } -} 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 356e146627..fccba688f0 100644 --- a/core/src/main/java/tc/oc/pgm/variables/Variable.java +++ b/core/src/main/java/tc/oc/pgm/variables/Variable.java @@ -1,6 +1,7 @@ package tc.oc.pgm.variables; import tc.oc.pgm.api.feature.Feature; +import tc.oc.pgm.api.match.Match; import tc.oc.pgm.filters.Filterable; public interface Variable> extends Feature> { @@ -13,4 +14,6 @@ default String getId() { double getValue(Filterable context); void setValue(Filterable context, double value); + + default void postLoad(Match match) {} } diff --git a/core/src/main/java/tc/oc/pgm/variables/VariableDefinition.java b/core/src/main/java/tc/oc/pgm/variables/VariableDefinition.java index 4e4c37a9e8..7175576efb 100644 --- a/core/src/main/java/tc/oc/pgm/variables/VariableDefinition.java +++ b/core/src/main/java/tc/oc/pgm/variables/VariableDefinition.java @@ -1,5 +1,6 @@ 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; @@ -7,30 +8,30 @@ public class VariableDefinition> extends SelfIdentifyingFeatureDefinition { private final Class scope; - private final double def; - private final VariableType variableType; - - public VariableDefinition(String id, Class scope, double def, VariableType variableType) { + private final boolean isDynamic; + private final Function, Variable> builder; + + public VariableDefinition( + String id, + Class scope, + boolean isDynamic, + Function, Variable> builder) { super(id); this.scope = scope; - this.def = def; - this.variableType = variableType; + this.isDynamic = isDynamic; + this.builder = builder; } public Class getScope() { return scope; } - public double getDefault() { - return def; - } - - public Variable buildInstance() { - return getVariableType().buildInstance(this); + public boolean isDynamic() { + return isDynamic; } - public VariableType getVariableType() { - return variableType; + public Variable buildInstance() { + return builder.apply(this); } @SuppressWarnings("unchecked") diff --git a/core/src/main/java/tc/oc/pgm/variables/VariableParser.java b/core/src/main/java/tc/oc/pgm/variables/VariableParser.java new file mode 100644 index 0000000000..6c9187e752 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/variables/VariableParser.java @@ -0,0 +1,92 @@ +package tc.oc.pgm.variables; + +import java.lang.reflect.Method; +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; +import tc.oc.pgm.api.party.Party; +import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.filters.Filterable; +import tc.oc.pgm.teams.TeamFactory; +import tc.oc.pgm.util.MethodParser; +import tc.oc.pgm.util.MethodParsers; +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.types.BlitzVariable; +import tc.oc.pgm.variables.types.DummyVariable; +import tc.oc.pgm.variables.types.ScoreVariable; +import tc.oc.pgm.variables.types.TeamVariableAdapter; + +public class VariableParser { + // The limitation is due to them being used in exp4j formulas for. + public static final Pattern VARIABLE_ID = Pattern.compile("[A-Za-z_]\\w*"); + + private final MapFactory factory; + private final Map methodParsers; + + public VariableParser(MapFactory factory) { + this.factory = factory; + this.methodParsers = MethodParsers.getMethodParsersForClass(getClass()); + } + + public VariableDefinition parse(Element el) throws InvalidXMLException { + String id = Node.fromRequiredAttr(el, "id").getValue(); + if (!VARIABLE_ID.matcher(id).matches()) + throw new InvalidXMLException( + "Variable IDs must start with a letter or underscore and can only include letters, digits or underscores.", + el); + + Method parser = methodParsers.get(el.getName().toLowerCase()); + if (parser != null) { + try { + return (VariableDefinition) parser.invoke(this, el, id); + } catch (Exception e) { + throw InvalidXMLException.coerce(e, new Node(el)); + } + } else { + throw new InvalidXMLException("Unknown variable type: " + el.getName(), el); + } + } + + @MethodParser("variable") + public VariableDefinition parseDummy(Element el, String id) throws InvalidXMLException { + Class> scope = Filterables.parse(Node.fromRequiredAttr(el, "scope")); + double def = XMLUtils.parseNumber(Node.fromAttr(el, "default"), Double.class, 0d); + return new VariableDefinition<>(id, scope, true, vd -> new DummyVariable<>(vd, def)); + } + + @MethodParser("lives") + public VariableDefinition parseBlitzLives(Element el, String id) + throws InvalidXMLException { + return new VariableDefinition<>(id, MatchPlayer.class, false, BlitzVariable::new); + } + + @MethodParser("score") + public VariableDefinition parseScore(Element el, String id) throws InvalidXMLException { + return new VariableDefinition<>(id, Party.class, false, ScoreVariable::new); + } + + @MethodParser("with-team") + public VariableDefinition parseTeamAdapter(Element el, String id) + throws InvalidXMLException { + @SuppressWarnings("unchecked") + VariableDefinition var = + factory.getFeatures().resolve(Node.fromRequiredAttr(el, "var"), VariableDefinition.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); + + return new VariableDefinition<>( + id, Match.class, var.isDynamic(), vd -> new TeamVariableAdapter(vd, var, team)); + } +} diff --git a/core/src/main/java/tc/oc/pgm/variables/VariableType.java b/core/src/main/java/tc/oc/pgm/variables/VariableType.java deleted file mode 100644 index 925f90421d..0000000000 --- a/core/src/main/java/tc/oc/pgm/variables/VariableType.java +++ /dev/null @@ -1,33 +0,0 @@ -package tc.oc.pgm.variables; - -import java.util.function.Function; -import tc.oc.pgm.api.player.MatchPlayer; -import tc.oc.pgm.filters.Filterable; - -public enum VariableType { - DUMMY(DummyVariable::new, Filterable.class), - LIVES(BlitzVariable::new, MatchPlayer.class); - - private final Function, Variable> supplierFunction; - private final Class[] supportedScopes; - - VariableType( - Function, Variable> supplierFunction, - Class... supportedScopes) { - this.supplierFunction = supplierFunction; - this.supportedScopes = supportedScopes; - } - - public boolean supports(Class cls) { - for (Class supportedScope : supportedScopes) { - if (supportedScope.isAssignableFrom(cls)) { - return true; - } - } - return false; - } - - public Variable buildInstance(VariableDefinition definition) { - return supplierFunction.apply(definition); - } -} 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 cba2bcc734..9d4e2cac94 100644 --- a/core/src/main/java/tc/oc/pgm/variables/VariablesMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/variables/VariablesMatchModule.java @@ -1,5 +1,20 @@ package tc.oc.pgm.variables; +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; -public class VariablesMatchModule implements MatchModule {} +public class VariablesMatchModule implements MatchModule, Listener { + + private final Match match; + + public VariablesMatchModule(Match match) { + this.match = match; + } + + @Override + public void load() throws ModuleLoadException { + match.getFeatureContext().getAll(Variable.class).forEach(v -> v.postLoad(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 bc7015888e..89e8f525e5 100644 --- a/core/src/main/java/tc/oc/pgm/variables/VariablesModule.java +++ b/core/src/main/java/tc/oc/pgm/variables/VariablesModule.java @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterators; +import java.util.Collection; import java.util.List; import java.util.logging.Logger; import java.util.regex.Pattern; @@ -16,10 +17,13 @@ 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.filters.Filterable; +import tc.oc.pgm.score.ScoreMatchModule; +import tc.oc.pgm.teams.TeamMatchModule; import tc.oc.pgm.util.xml.InvalidXMLException; -import tc.oc.pgm.util.xml.Node; import tc.oc.pgm.util.xml.XMLUtils; public class VariablesModule implements MapModule { @@ -39,6 +43,11 @@ public VariablesModule(ImmutableList> variables) { this.variablesByScope = varsBuilder.build(); } + @Override + public @Nullable Collection> getWeakDependencies() { + return ImmutableList.of(TeamMatchModule.class, BlitzMatchModule.class, ScoreMatchModule.class); + } + public ImmutableSet getVariableNames(Class> scope) { return variablesByScope.get(scope).names; } @@ -77,7 +86,7 @@ public VariablesMatchModule createMatchModule(Match match) throws ModuleLoadExce match.getFeatureContext().add(varDef.buildInstance()); } - return new VariablesMatchModule(); + return new VariablesMatchModule(match); } public static class Factory implements MapModuleFactory { @@ -91,28 +100,10 @@ public VariablesModule parse(MapFactory factory, Logger logger, Document doc) throws InvalidXMLException { ImmutableList.Builder> variables = ImmutableList.builder(); - for (Element variable : - XMLUtils.flattenElements(doc.getRootElement(), "variables", "variable")) { - - String id = Node.fromRequiredAttr(variable, "id").getValue(); - if (!VARIABLE_ID.matcher(id).matches()) - throw new InvalidXMLException( - "Variable IDs must start with a letter or the underscore _ and can only include letters, digits or underscores.", - variable); - Class> scope = - Filterables.parse(Node.fromRequiredAttr(variable, "scope")); - double def = XMLUtils.parseNumber(Node.fromAttr(variable, "default"), Double.class, 0d); - Node variableTypeNode = Node.fromAttr(variable, "type"); - VariableType variableType = - XMLUtils.parseEnum( - variableTypeNode, VariableType.class, "variable type", VariableType.DUMMY); - if (!variableType.supports(scope)) { - throw new InvalidXMLException( - "VariableType: " + variableType + " Does not support scope: " + scope, - variableTypeNode); - } - - VariableDefinition varDef = new VariableDefinition<>(id, scope, def, variableType); + VariableParser parser = new VariableParser(factory); + + for (Element variable : XMLUtils.flattenElements(doc.getRootElement(), "variables", null)) { + VariableDefinition varDef = parser.parse(variable); factory.getFeatures().addFeature(variable, varDef); variables.add(varDef); } 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 new file mode 100644 index 0000000000..a2dfbf2046 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/variables/types/AbstractVariable.java @@ -0,0 +1,45 @@ +package tc.oc.pgm.variables.types; + +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; + + public AbstractVariable(VariableDefinition definition) { + this.definition = definition; + } + + @Override + public VariableDefinition getDefinition() { + return definition; + } + + protected abstract double getValueImpl(T obj); + + protected abstract void setValueImpl(T obj, double value); + + @Override + public double getValue(Filterable context) { + return getValueImpl(getAncestor(context)); + } + + @Override + public void setValue(Filterable context, double value) { + setValueImpl(getAncestor(context), value); + } + + protected T getAncestor(Filterable context) { + T filterable = context.getFilterableAncestor(definition.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()); + } +} 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 new file mode 100644 index 0000000000..bc9a200c2b --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/variables/types/BlitzVariable.java @@ -0,0 +1,30 @@ +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 new file mode 100644 index 0000000000..d254408a42 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/variables/types/DummyVariable.java @@ -0,0 +1,31 @@ +package tc.oc.pgm.variables.types; + +import java.util.HashMap; +import java.util.Map; +import tc.oc.pgm.filters.FilterMatchModule; +import tc.oc.pgm.filters.Filterable; +import tc.oc.pgm.variables.VariableDefinition; + +public class DummyVariable> extends AbstractVariable { + + private final double def; + private final Map values; + + public DummyVariable(VariableDefinition definition, double def) { + super(definition); + this.def = def; + this.values = new HashMap<>(); + } + + @Override + protected double getValueImpl(T obj) { + return values.computeIfAbsent(obj, k -> def); + } + + @Override + protected void setValueImpl(T obj, double value) { + values.put(obj, value); + // For performance reasons, let's avoid launching an event for every variable change + obj.moduleRequire(FilterMatchModule.class).invalidate(obj); + } +} 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 new file mode 100644 index 0000000000..9cbb44065c --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/variables/types/ScoreVariable.java @@ -0,0 +1,32 @@ +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 { + + private ScoreMatchModule smm; + + public ScoreVariable(VariableDefinition definition) { + super(definition); + } + + @Override + public void postLoad(Match match) { + smm = match.moduleRequire(ScoreMatchModule.class); + } + + @Override + protected double getValueImpl(Party party) { + if (party instanceof Competitor) return smm.getScore((Competitor) party); + return 0; + } + + @Override + protected void setValueImpl(Party party, double value) { + if (party instanceof Competitor) smm.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 new file mode 100644 index 0000000000..0dc52362fb --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/variables/types/TeamVariableAdapter.java @@ -0,0 +1,43 @@ +package tc.oc.pgm.variables.types; + +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.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 { + + private final VariableDefinition childRef; + private final FeatureReference teamRef; + + private Variable child; + private Team team; + + public TeamVariableAdapter( + VariableDefinition definition, + VariableDefinition childRef, + FeatureReference teamRef) { + super(definition); + this.childRef = childRef; + this.teamRef = teamRef; + } + + public void postLoad(Match match) { + team = match.needModule(TeamMatchModule.class).getTeam(teamRef.get()); + child = childRef.getVariable(match); + } + + @Override + protected double getValueImpl(Match match) { + return child.getValue(team); + } + + @Override + protected void setValueImpl(Match match, double value) { + child.setValue(team, value); + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/xml/XMLUtils.java b/util/src/main/java/tc/oc/pgm/util/xml/XMLUtils.java index 4975d5a458..914d238225 100644 --- a/util/src/main/java/tc/oc/pgm/util/xml/XMLUtils.java +++ b/util/src/main/java/tc/oc/pgm/util/xml/XMLUtils.java @@ -894,8 +894,8 @@ public static PotionEffect parseCompactPotionEffect(Node node, String text) return createPotionEffect(type, duration, amplifier, ambient); } - public static > T parseEnum( - Node node, String text, Class type, String readableType) throws InvalidXMLException { + public static > T parseEnum(Node node, String text, Class type) + throws InvalidXMLException { try { return TextParser.parseEnum(text, type); } catch (TextException e) { @@ -903,35 +903,29 @@ public static > T parseEnum( } } - public static > T parseEnum( - @Nullable Node node, Class type, String readableType, @Nullable T def) + public static > T parseEnum(@Nullable Node node, Class type, @Nullable T def) throws InvalidXMLException { if (node == null) return def; - return parseEnum(node, node.getValueNormalize(), type, readableType); - } - - public static > T parseEnum( - @Nullable Node node, Class type, String readableType) throws InvalidXMLException { - return parseEnum(node, type, readableType, null); + return parseEnum(node, node.getValueNormalize(), type); } - public static > T parseEnum(Element el, Class type) + public static > T parseEnum(@Nullable Node node, Class type) throws InvalidXMLException { - return parseEnum(new Node(el), type, type.getSimpleName()); + return parseEnum(node, type, null); } - public static > T parseEnum(Element el, Class type, String readableType) + public static > T parseEnum(Element el, Class type) throws InvalidXMLException { - return parseEnum(new Node(el), type, readableType); + return parseEnum(new Node(el), type); } - public static > T parseEnum(Attribute attr, Class type, String readableType) + public static > T parseEnum(Attribute attr, Class type) throws InvalidXMLException { - return parseEnum(new Node(attr), type, readableType); + return parseEnum(new Node(attr), type); } public static ChatColor parseChatColor(@Nullable Node node) throws InvalidXMLException { - return parseEnum(node, ChatColor.class, "color"); + return parseEnum(node, ChatColor.class); } public static ChatColor parseChatColor(@Nullable Node node, ChatColor def)