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:
ssk97 2023-11-16 11:12:32 -08:00 committed by GitHub
parent 72e30f1574
commit bea33c7493
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 458 additions and 338 deletions

View file

@ -101,6 +101,7 @@ public class GameState implements Serializable, Copyable<GameState> {
private Map<UUID, Zone> zones = new HashMap<>();
private List<GameEvent> simultaneousEvents = new ArrayList<>();
private Map<UUID, CardState> cardState = new HashMap<>();
private Map<MageObjectReference, Map<String, Object>> permanentCostsTags = new HashMap<>(); // Permanent reference -> map of (tag -> values) describing how the permanent's spell was cast
private Map<UUID, MageObjectAttribute> mageObjectAttribute = new HashMap<>();
private Map<UUID, Integer> zoneChangeCounter = new HashMap<>();
private Map<UUID, Card> copiedCards = new HashMap<>();
@ -162,36 +163,19 @@ public class GameState implements Serializable, Copyable<GameState> {
this.stepNum = state.stepNum;
this.extraTurnId = state.extraTurnId;
this.effects = state.effects.copy();
for (TriggeredAbility trigger : state.triggered) {
this.triggered.add(trigger.copy());
}
this.triggered = CardUtil.deepCopyObject(state.triggered);
this.triggers = state.triggers.copy();
this.delayed = state.delayed.copy();
this.specialActions = state.specialActions.copy();
this.combat = state.combat.copy();
this.turnMods = state.turnMods.copy();
this.watchers = state.watchers.copy();
for (Map.Entry<String, Object> entry : state.values.entrySet()) {
if (entry.getValue() instanceof HashSet) {
this.values.put(entry.getKey(), ((HashSet) entry.getValue()).clone());
} else if (entry.getValue() instanceof EnumSet) {
this.values.put(entry.getKey(), ((EnumSet) entry.getValue()).clone());
} else if (entry.getValue() instanceof HashMap) {
this.values.put(entry.getKey(), ((HashMap) entry.getValue()).clone());
} else if (entry.getValue() instanceof List) {
this.values.put(entry.getKey(), ((List) entry.getValue()).stream().collect(Collectors.toList()));
} else {
this.values.put(entry.getKey(), entry.getValue());
}
}
this.values = CardUtil.deepCopyObject(state.values);
this.zones.putAll(state.zones);
this.simultaneousEvents.addAll(state.simultaneousEvents);
for (Map.Entry<UUID, CardState> entry : state.cardState.entrySet()) {
cardState.put(entry.getKey(), entry.getValue().copy());
}
for (Map.Entry<UUID, MageObjectAttribute> entry : state.mageObjectAttribute.entrySet()) {
mageObjectAttribute.put(entry.getKey(), entry.getValue().copy());
}
this.cardState = CardUtil.deepCopyObject(state.cardState);
this.permanentCostsTags = CardUtil.deepCopyObject(state.permanentCostsTags);
this.mageObjectAttribute = CardUtil.deepCopyObject(state.mageObjectAttribute);
this.zoneChangeCounter.putAll(state.zoneChangeCounter);
this.copiedCards.putAll(state.copiedCards);
this.permanentOrderNumber = state.permanentOrderNumber;
@ -231,6 +215,7 @@ public class GameState implements Serializable, Copyable<GameState> {
gameOver = false;
specialActions.clear();
cardState.clear();
permanentCostsTags.clear();
combat.clear();
turnMods.clear();
watchers.clear();
@ -280,6 +265,7 @@ public class GameState implements Serializable, Copyable<GameState> {
this.zones = state.zones;
this.simultaneousEvents = state.simultaneousEvents;
this.cardState = state.cardState;
this.permanentCostsTags = state.permanentCostsTags;
this.mageObjectAttribute = state.mageObjectAttribute;
this.zoneChangeCounter = state.zoneChangeCounter;
this.copiedCards = state.copiedCards;
@ -1369,6 +1355,29 @@ public class GameState implements Serializable, Copyable<GameState> {
return mageObjectAtt;
}
public Map<MageObjectReference, Map<String, Object>> getPermanentCostsTags() {
return permanentCostsTags;
}
/**
* Store the tags of source ability using the MOR as a reference
*/
void storePermanentCostsTags(MageObjectReference permanentMOR, Ability source){
if (source.getCostsTagMap() != null) {
permanentCostsTags.put(permanentMOR, CardUtil.deepCopyObject(source.getCostsTagMap()));
}
}
/**
* Removes the cost tags if the corresponding permanent is no longer on the battlefield.
* Only use if the stack is empty and nothing can refer to them anymore (such as at EOT, the current behavior)
*/
public void cleanupPermanentCostsTags(Game game){
getPermanentCostsTags().entrySet().removeIf(entry ->
!(entry.getKey().zoneCounterIsCurrent(game))
);
}
public void addWatcher(Watcher watcher) {
this.watchers.add(watcher);
}