forked from External/mage
Costs Tag Tracking part 2: Tag system and X values, reworked deep copy code (#11406)
* Implement Costs Tag Map system * Use Costs Tag Map system to store X value for spells, abilities, and resolving permanents * Store Bestow without target's tags Change functions for getting tags and storing the tags of a new permanent * Create and use deep copy function in CardUtil, add Copyable<T> to many classes * Fix Hall Of the Bandit Lord infinite loop * Add additional comments * Don't store null/empty costs tags maps (saves memory) * Fix two more Watchers with Ability variable * Add check for exact collection types during deep copy * Use generics instead of pure type erasure during deep copy * convert more code to using deep copy helper, everything use Object copier, add EnumMap * fix documentation * Don't need the separate null checks anymore (handled in deepCopyObject) * Minor cleanup
This commit is contained in:
parent
72e30f1574
commit
bea33c7493
29 changed files with 458 additions and 338 deletions
|
|
@ -1,10 +1,7 @@
|
|||
package mage.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObject;
|
||||
import mage.Mana;
|
||||
import mage.*;
|
||||
import mage.abilities.*;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.costs.Cost;
|
||||
|
|
@ -42,9 +39,11 @@ import mage.game.permanent.token.Token;
|
|||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.StackObject;
|
||||
import mage.players.Player;
|
||||
import mage.players.PlayerList;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.watchers.Watcher;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
|
@ -1648,6 +1647,75 @@ public final class CardUtil {
|
|||
}
|
||||
return zcc;
|
||||
}
|
||||
/**
|
||||
* Create a MageObjectReference of the ability's source
|
||||
* Subtract 1 zcc if not on the stack, referencing when it was on the stack if it's a resolved permanent.
|
||||
* works in any moment (even before source ability activated)
|
||||
*
|
||||
* @param game
|
||||
* @param ability
|
||||
* @return MageObjectReference to the ability's source stack moment
|
||||
*/
|
||||
public static MageObjectReference getSourceStackMomentReference(Game game, Ability ability) {
|
||||
// Squad/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 = ability.getSourceObject(game);
|
||||
Zone sourceObjectZone = game.getState().getZone(sourceObject.getId());
|
||||
int zcc = CardUtil.getActualSourceObjectZoneChangeCounter(game, ability);
|
||||
// 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 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) {
|
||||
Map<String, Object> costTags;
|
||||
costTags = source.getCostsTagMap();
|
||||
if (costTags == null && source.getSourcePermanentOrLKI(game) != null) {
|
||||
costTags = game.getPermanentCostsTags().get(CardUtil.getSourceStackMomentReference(game, source));
|
||||
}
|
||||
return costTags;
|
||||
}
|
||||
/**
|
||||
* Check if a specific tag exists in the cost tags of either the source ability, or the permanent source of the ability.
|
||||
* Works in any moment (even before source ability activated)
|
||||
*
|
||||
* @param game
|
||||
* @param source
|
||||
* @param tag The tag's string identifier to look up
|
||||
* @return if the tag was found
|
||||
*/
|
||||
public static boolean checkSourceCostsTagExists(Game game, Ability source, String tag) {
|
||||
Map<String, Object> costTags = getCostsTags(game, source);
|
||||
return costTags != null && costTags.containsKey(tag);
|
||||
}
|
||||
/**
|
||||
* Find a specific tag in the cost tags of either the source ability, or the permanent source of the ability.
|
||||
* Works in any moment (even before source ability activated)
|
||||
*
|
||||
* @param game
|
||||
* @param source
|
||||
* @param tag The tag's string identifier to look up
|
||||
* @param defaultValue A default value to return if the tag is not found
|
||||
* @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);
|
||||
if (costTags != null) {
|
||||
return costTags.getOrDefault(tag, defaultValue);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static String addCostVerb(String text) {
|
||||
if (costWords.stream().anyMatch(text.toLowerCase(Locale.ENGLISH)::startsWith)) {
|
||||
|
|
@ -1656,6 +1724,117 @@ public final class CardUtil {
|
|||
return "pay " + text;
|
||||
}
|
||||
|
||||
private static boolean isImmutableObject(Object o){
|
||||
return o == null
|
||||
|| o instanceof Number || o instanceof Boolean || o instanceof String
|
||||
|| o instanceof MageObjectReference || o instanceof UUID
|
||||
|| o instanceof Enum;
|
||||
}
|
||||
public static <T> T deepCopyObject(T value){
|
||||
if (isImmutableObject(value)) {
|
||||
return value;
|
||||
} else if (value instanceof Copyable) {
|
||||
return (T) ((Copyable<T>) value).copy();
|
||||
} else if (value instanceof Watcher) {
|
||||
return (T) ((Watcher) value).copy();
|
||||
} else if (value instanceof Ability) {
|
||||
return (T) ((Ability) value).copy();
|
||||
} else if (value instanceof PlayerList) {
|
||||
return (T) ((PlayerList) value).copy();
|
||||
} else if (value instanceof EnumSet) {
|
||||
return (T) ((EnumSet) value).clone();
|
||||
} else if (value instanceof EnumMap) {
|
||||
return (T) deepCopyEnumMap((EnumMap) value);
|
||||
} else if (value instanceof LinkedHashSet) {
|
||||
return (T) deepCopyLinkedHashSet((LinkedHashSet) value);
|
||||
} else if (value instanceof LinkedHashMap) {
|
||||
return (T) deepCopyLinkedHashMap((LinkedHashMap) value);
|
||||
} else if (value instanceof TreeSet) {
|
||||
return (T) deepCopyTreeSet((TreeSet) value);
|
||||
} else if (value instanceof HashSet) {
|
||||
return (T) deepCopyHashSet((HashSet) value);
|
||||
} else if (value instanceof HashMap) {
|
||||
return (T) deepCopyHashMap((HashMap) value);
|
||||
} else if (value instanceof List) {
|
||||
return (T) deepCopyList((List) value);
|
||||
} else if (value instanceof AbstractMap.SimpleImmutableEntry){ //Used by Leonin Arbiter, Vessel Of The All Consuming Wanderer as a generic Pair class
|
||||
AbstractMap.SimpleImmutableEntry entryValue = (AbstractMap.SimpleImmutableEntry) value;
|
||||
return (T) new AbstractMap.SimpleImmutableEntry(deepCopyObject(entryValue.getKey()),deepCopyObject(entryValue.getValue()));
|
||||
} else {
|
||||
throw new IllegalStateException("Unhandled object " + value.getClass().getSimpleName() + " during deep copy, must add explicit handling of all Object types");
|
||||
}
|
||||
}
|
||||
private static <T extends Comparable<T>> TreeSet<T> deepCopyTreeSet(TreeSet<T> original) {
|
||||
if (original.getClass() != TreeSet.class) {
|
||||
throw new IllegalStateException("Unhandled TreeSet type " + original.getClass().getSimpleName() + " in deep copy");
|
||||
}
|
||||
TreeSet<T> newSet = new TreeSet<>();
|
||||
for (T value : original){
|
||||
newSet.add((T) deepCopyObject(value));
|
||||
}
|
||||
return newSet;
|
||||
}
|
||||
private static <T> HashSet<T> deepCopyHashSet(Set<T> original) {
|
||||
if (original.getClass() != HashSet.class) {
|
||||
throw new IllegalStateException("Unhandled HashSet type " + original.getClass().getSimpleName() + " in deep copy");
|
||||
}
|
||||
HashSet<T> newSet = new HashSet<>(original.size());
|
||||
for (T value : original){
|
||||
newSet.add((T) deepCopyObject(value));
|
||||
}
|
||||
return newSet;
|
||||
}
|
||||
private static <T> LinkedHashSet<T> deepCopyLinkedHashSet(LinkedHashSet<T> original) {
|
||||
if (original.getClass() != LinkedHashSet.class) {
|
||||
throw new IllegalStateException("Unhandled LinkedHashSet type " + original.getClass().getSimpleName() + " in deep copy");
|
||||
}
|
||||
LinkedHashSet<T> newSet = new LinkedHashSet<>(original.size());
|
||||
for (T value : original){
|
||||
newSet.add((T) deepCopyObject(value));
|
||||
}
|
||||
return newSet;
|
||||
}
|
||||
private static <T> List<T> deepCopyList(List<T> original) { //always returns an ArrayList
|
||||
if (original.getClass() != ArrayList.class) {
|
||||
throw new IllegalStateException("Unhandled List type " + original.getClass().getSimpleName() + " in deep copy");
|
||||
}
|
||||
ArrayList<T> newList = new ArrayList<>(original.size());
|
||||
for (T value : original){
|
||||
newList.add((T) deepCopyObject(value));
|
||||
}
|
||||
return newList;
|
||||
}
|
||||
private static <K, V> HashMap<K, V> deepCopyHashMap(Map<K, V> original) {
|
||||
if (original.getClass() != HashMap.class) {
|
||||
throw new IllegalStateException("Unhandled HashMap type " + original.getClass().getSimpleName() + " in deep copy");
|
||||
}
|
||||
HashMap<K, V> newMap = new HashMap<>(original.size());
|
||||
for (Map.Entry<K, V> entry : original.entrySet()) {
|
||||
newMap.put((K) deepCopyObject(entry.getKey()), (V) deepCopyObject(entry.getValue()));
|
||||
}
|
||||
return newMap;
|
||||
}
|
||||
private static <K, V> LinkedHashMap<K, V> deepCopyLinkedHashMap(Map<K, V> original) {
|
||||
if (original.getClass() != LinkedHashMap.class) {
|
||||
throw new IllegalStateException("Unhandled LinkedHashMap type " + original.getClass().getSimpleName() + " in deep copy");
|
||||
}
|
||||
LinkedHashMap<K, V> newMap = new LinkedHashMap<>(original.size());
|
||||
for (Map.Entry<K, V> entry : original.entrySet()) {
|
||||
newMap.put((K) deepCopyObject(entry.getKey()), (V) deepCopyObject(entry.getValue()));
|
||||
}
|
||||
return newMap;
|
||||
}
|
||||
private static <K extends Enum<K>, V> EnumMap<K, V> deepCopyEnumMap(Map<K, V> original) {
|
||||
if (original.getClass() != EnumMap.class) {
|
||||
throw new IllegalStateException("Unhandled EnumMap type " + original.getClass().getSimpleName() + " in deep copy");
|
||||
}
|
||||
EnumMap<K, V> newMap = new EnumMap<>(original);
|
||||
for (Map.Entry<K, V> entry : newMap.entrySet()) {
|
||||
entry.setValue((V) deepCopyObject(entry.getValue()));
|
||||
}
|
||||
return newMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all possible object's parts (example: all sides in mdf/split cards)
|
||||
* <p>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue