mirror of
https://github.com/magefree/mage.git
synced 2025-12-25 04:52:07 -08:00
Convert Kicker to costs tag system
This commit is contained in:
parent
1e76b59f4e
commit
77dd1711b5
6 changed files with 44 additions and 113 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<String, Integer> activations = new ConcurrentHashMap<>(); // zoneChangeCounter, activations
|
||||
|
||||
protected String keywordText;
|
||||
protected String reminderText;
|
||||
protected List<OptionalAdditionalCost> 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<String, Object> costsTags = CardUtil.getSourceCostsTags(game, source);
|
||||
if (costsTags == null) {
|
||||
return 0;
|
||||
}
|
||||
return totalActivations;
|
||||
String finalActivationKey = getActivationKey(needKickerCost);
|
||||
Stream<Map.Entry<String, Object>> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, Object> 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<String, Object> getSourceCostsTags(Game game, Ability source) {
|
||||
Map<String, Object> 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<String, Object> costTags = getCostsTags(game, source);
|
||||
Map<String, Object> 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<String, Object> costTags = getCostsTags(game, source);
|
||||
Map<String, Object> costTags = getSourceCostsTags(game, source);
|
||||
if (costTags != null) {
|
||||
return costTags.getOrDefault(tag, defaultValue);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue