From 77dd1711b5289c2d3547dfd18eca44aabe7208b2 Mon Sep 17 00:00:00 2001 From: Steven Knipe Date: Thu, 16 Nov 2023 15:07:58 -0800 Subject: [PATCH] Convert Kicker to costs tag system --- .../src/mage/cards/s/StrongholdArena.java | 2 +- .../condition/common/KickedCondition.java | 2 +- .../condition/common/KickedCostCondition.java | 12 +- .../dynamicvalue/common/MultikickerCount.java | 2 +- .../mage/abilities/keyword/KickerAbility.java | 122 ++++-------------- Mage/src/main/java/mage/util/CardUtil.java | 17 ++- 6 files changed, 44 insertions(+), 113 deletions(-) diff --git a/Mage.Sets/src/mage/cards/s/StrongholdArena.java b/Mage.Sets/src/mage/cards/s/StrongholdArena.java index db436225982..26366d48ef4 100644 --- a/Mage.Sets/src/mage/cards/s/StrongholdArena.java +++ b/Mage.Sets/src/mage/cards/s/StrongholdArena.java @@ -73,7 +73,7 @@ class StrongholdArenaGainLifeEffect extends OneShotEffect { if (controller == null) { return false; } - controller.gainLife(KickerAbility.getSourceObjectKickedCount(game, source) * 3, game, source); + controller.gainLife(KickerAbility.getKickedCounter(game, source) * 3, game, source); return true; } } diff --git a/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java b/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java index 7420d35cae3..e0b40c02e34 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java @@ -24,7 +24,7 @@ public enum KickedCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - return KickerAbility.getSourceObjectKickedCount(game, source) >= kickedCount; + return KickerAbility.getKickedCounter(game, source) >= kickedCount; } @Override diff --git a/Mage/src/main/java/mage/abilities/condition/common/KickedCostCondition.java b/Mage/src/main/java/mage/abilities/condition/common/KickedCostCondition.java index d847018644e..b3dcf1ee4d4 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/KickedCostCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/KickedCostCondition.java @@ -1,10 +1,8 @@ package mage.abilities.condition.common; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.condition.Condition; import mage.abilities.keyword.KickerAbility; -import mage.cards.Card; import mage.game.Game; /** @@ -22,14 +20,6 @@ public class KickedCostCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - MageObject sourceObject = source.getSourceObject(game); - if (sourceObject instanceof Card) { - for (Ability ability : ((Card) sourceObject).getAbilities(game)) { - if (ability instanceof KickerAbility) { - return ((KickerAbility) ability).isKicked(game, source, kickerCostText); - } - } - } - return false; + return KickerAbility.getKickedCounterStrict(game, source, kickerCostText) > 0; } } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/MultikickerCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/MultikickerCount.java index 0860d2e8475..8b1fa60d48e 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/MultikickerCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/MultikickerCount.java @@ -16,7 +16,7 @@ public enum MultikickerCount implements DynamicValue { @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - return KickerAbility.getSourceObjectKickedCount(game, sourceAbility); + return KickerAbility.getKickedCounter(game, sourceAbility); } @Override diff --git a/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java b/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java index dadd163a6f5..9dd192c5f52 100644 --- a/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java @@ -1,12 +1,10 @@ package mage.abilities.keyword; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.StaticAbility; import mage.abilities.costs.*; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.cards.Card; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; @@ -16,7 +14,7 @@ import mage.players.Player; import mage.util.CardUtil; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; /** * 20121001 702.31. Kicker 702.31a Kicker is a static ability that functions @@ -55,8 +53,6 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo protected static final String KICKER_REMINDER_COST = "You may {cost} in addition " + "to any other costs as you cast this spell."; - protected Map activations = new ConcurrentHashMap<>(); // zoneChangeCounter, activations - protected String keywordText; protected String reminderText; protected List kickerCosts = new LinkedList<>(); @@ -86,7 +82,6 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo } this.keywordText = ability.keywordText; this.reminderText = ability.reminderText; - this.activations.putAll(ability.activations); } @Override @@ -119,32 +114,30 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo for (OptionalAdditionalCost cost : kickerCosts) { cost.reset(); } - activations.clear(); } - private int getKickedCounterStrict(Game game, Ability source, String needKickerCost) { - String key; - if (needKickerCost.isEmpty()) { - // need all kickers - key = getActivationKey(source, "", game); - } else { - // need only cost related kickers - key = getActivationKey(source, needKickerCost, game); - } + private static String getActivationKey(String needKickerCost){ + return "kickerActivation"+needKickerCost; + } - int totalActivations = 0; - if (kickerCosts.size() > 1) { - for (String activationKey : activations.keySet()) { - if (activationKey.startsWith(key) && activations.get(activationKey) > 0) { - totalActivations += activations.get(activationKey); - } - } - } else { - if (activations.containsKey(key) && activations.get(key) > 0) { - totalActivations += activations.get(key); - } + /** + * Return total kicker activations with the specified Cost (blank for all kickers/multikickers) + * Checks the start of the tags, to work for that blank method, which requires direct access + * + * @param game + * @param source + * @param needKickerCost use cost.getText(true) + * @return + */ + public static int getKickedCounterStrict(Game game, Ability source, String needKickerCost) { + Map costsTags = CardUtil.getSourceCostsTags(game, source); + if (costsTags == null) { + return 0; } - return totalActivations; + String finalActivationKey = getActivationKey(needKickerCost); + Stream> tagStream = costsTags.entrySet().stream() + .filter(x -> x.getKey().startsWith(finalActivationKey)); + return tagStream.mapToInt(x -> (int)x.getValue()).sum(); } /** @@ -154,7 +147,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo * @param source * @return */ - public int getKickedCounter(Game game, Ability source) { + public static int getKickedCounter(Game game, Ability source) { return getKickedCounterStrict(game, source, ""); } @@ -186,47 +179,11 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo } private void activateKicker(OptionalAdditionalCost kickerCost, Ability source, Game game) { - int amount = 1; - String key = getActivationKey(source, kickerCost.getText(true), game); - if (activations.containsKey(key)) { - amount += activations.get(key); - } - activations.put(key, amount); game.fireEvent(GameEvent.getEvent(GameEvent.EventType.KICKED, source.getSourceId(), source, source.getControllerId())); - } - /** - * Return activation zcc key for searching spell's settings in source object - * - * @param source - * @param game - * @return - */ - public static String getActivationKey(Ability source, Game game) { - // Kicker activates in STACK zone so all zcc must be from "stack moment" - // Use cases: - // * resolving spell have same zcc (example: check kicker status in sorcery/instant); - // * copied spell have same zcc as source spell (see Spell.copySpell and zcc sync); - // * creature/token from resolved spell have +1 zcc after moved to battlefield (example: check kicker status in ETB triggers/effects); - - // find object info from the source ability (it can be a permanent or a spell on stack, on the moment of trigger/resolve) - MageObject sourceObject = source.getSourceObject(game); - Zone sourceObjectZone = game.getState().getZone(sourceObject.getId()); - int zcc = CardUtil.getActualSourceObjectZoneChangeCounter(game, source); - - // find "stack moment" zcc: - // * permanent cards enters from STACK to BATTLEFIELD (+1 zcc) - // * permanent tokens enters from OUTSIDE to BATTLEFIELD (+1 zcc, see prepare code in TokenImpl.putOntoBattlefieldHelper) - // * spells and copied spells resolves on STACK (zcc not changes) - if (sourceObjectZone != Zone.STACK) { - --zcc; - } - - return zcc + ""; - } - - private String getActivationKey(Ability source, String costText, Game game) { - return getActivationKey(source, game) + ((kickerCosts.size() > 1) ? costText : ""); + String activationKey = getActivationKey(kickerCost.getText(true)); + Integer next = (int)source.getCostsTagOrDefault(activationKey,0)+1; + source.setCostsTag(activationKey,next); } @Override @@ -330,35 +287,10 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo * @return */ public static int getSpellKickedCount(Game game, UUID spellId) { - int count = 0; Spell spell = game.getSpellOrLKIStack(spellId); if (spell != null) { - for (Ability ability : spell.getAbilities(game)) { - if (ability instanceof KickerAbility) { - count += ((KickerAbility) ability).getKickedCounter(game, spell.getSpellAbility()); - } - } + return KickerAbility.getKickedCounter(game, spell.getSpellAbility()); } - return count; - } - - /** - * Find source object's kicked stats. Can be used in any places, e.g. in ETB effects - * - * @param game - * @param abilityToCheck - * @return - */ - public static int getSourceObjectKickedCount(Game game, Ability abilityToCheck) { - MageObject sourceObject = abilityToCheck.getSourceObject(game); - int count = 0; - if (sourceObject instanceof Card) { - for (Ability ability : ((Card) sourceObject).getAbilities(game)) { - if (ability instanceof KickerAbility) { - count += ((KickerAbility) ability).getKickedCounter(game, abilityToCheck); - } - } - } - return count; + return 0; } } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 8ce419b47dc..d4a0a6a5d5c 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1677,8 +1677,17 @@ public final class CardUtil { return new MageObjectReference(ability.getSourceId(), zcc, game); } - //Use the two other functions below to access the tags, this is just the shared logic for them - private static Map getCostsTags(Game game, Ability source) { + /** + * Returns the entire cost tags map of either the source ability, or the permanent source of the ability. May be null. + * Works in any moment (even before source ability activated) + * Usually you should use one of the single tag functions instead + * + * @param game + * @param source + * @return the tag map (or null) + */ + // + public static Map getSourceCostsTags(Game game, Ability source) { Map costTags; costTags = source.getCostsTagMap(); if (costTags == null && source.getSourcePermanentOrLKI(game) != null) { @@ -1696,7 +1705,7 @@ public final class CardUtil { * @return if the tag was found */ public static boolean checkSourceCostsTagExists(Game game, Ability source, String tag) { - Map costTags = getCostsTags(game, source); + Map costTags = getSourceCostsTags(game, source); return costTags != null && costTags.containsKey(tag); } /** @@ -1710,7 +1719,7 @@ public final class CardUtil { * @return The object stored by the tag if found, the default if not */ public static Object getSourceCostsTag(Game game, Ability source, String tag, Object defaultValue){ - Map costTags = getCostsTags(game, source); + Map costTags = getSourceCostsTags(game, source); if (costTags != null) { return costTags.getOrDefault(tag, defaultValue); }