diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 1803e42b3bf..a47eadf2eec 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -3659,6 +3659,8 @@ public class Card extends GameEntity implements Comparable { if (updateView) { updateKeywords(); + if (isToken()) + game.fireEvent(new GameEventTokenStateUpdate(this)); } } @@ -3713,6 +3715,8 @@ public class Card extends GameEntity implements Comparable { KeywordsChange change = changedCardKeywords.remove(timestamp); if (change != null && updateView) { updateKeywords(); + if (isToken()) + game.fireEvent(new GameEventTokenStateUpdate(this)); } return change; } @@ -5460,6 +5464,14 @@ public class Card extends GameEntity implements Comparable { } return hexproofKey; } + public String getKeywordKey() { + List ability = new ArrayList<>(); + for (final KeywordInterface inst : getKeywords()) { + ability.add(inst.getOriginal()); + } + Collections.sort(ability); + return String.join(",", ability); + } public Zone getZone() { return currentZone; } diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index cf83a240f5a..845b729ebe0 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -1017,6 +1017,7 @@ public class CardView extends GameEntityView { foilIndexOverride = index0; } + public String getKeywordKey() { return get(TrackableProperty.KeywordKey); } public String getProtectionKey() { return get(TrackableProperty.ProtectionKey); } public String getHexproofKey() { return get(TrackableProperty.HexproofKey); } public boolean hasDeathtouch() { return get(TrackableProperty.HasDeathtouch); } @@ -1076,6 +1077,8 @@ public class CardView extends GameEntityView { set(TrackableProperty.ProtectionKey, c.getProtectionKey()); //set hexproofKeys for Icons set(TrackableProperty.HexproofKey, c.getHexproofKey()); + //keywordkey + set(TrackableProperty.KeywordKey, c.getKeywordKey()); } public boolean isBasicLand() { diff --git a/forge-game/src/main/java/forge/game/event/GameEventTokenStateUpdate.java b/forge-game/src/main/java/forge/game/event/GameEventTokenStateUpdate.java new file mode 100644 index 00000000000..a0e10e652c8 --- /dev/null +++ b/forge-game/src/main/java/forge/game/event/GameEventTokenStateUpdate.java @@ -0,0 +1,24 @@ +package forge.game.event; + +import forge.game.card.Card; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +public class GameEventTokenStateUpdate extends GameEvent { + + public final Collection cards; + public GameEventTokenStateUpdate(Card affected) { + cards = Arrays.asList(affected); + } + + public GameEventTokenStateUpdate(List affected) { + cards = affected; + } + + @Override + public T visit(IGameEventVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java b/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java index 8dd56af94d4..d78e2c4fc66 100644 --- a/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java +++ b/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java @@ -37,6 +37,7 @@ public interface IGameEventVisitor { T visit(GameEventPlayerPoisoned event); T visit(GameEventPlayerPriority event); T visit(GameEventPlayerStatsChanged event); + T visit(GameEventTokenStateUpdate event); T visit(GameEventScry event); T visit(GameEventShuffle event); T visit(GameEventSpellAbilityCast gameEventSpellAbilityCast); @@ -83,6 +84,7 @@ public interface IGameEventVisitor { public T visit(GameEventPlayerPoisoned event) { return null; } public T visit(GameEventPlayerPriority event) { return null; } public T visit(GameEventPlayerStatsChanged event) { return null; } + public T visit(GameEventTokenStateUpdate event) { return null; } public T visit(GameEventScry event) { return null; } public T visit(GameEventShuffle event) { return null; } public T visit(GameEventSpellResolved event) { return null; } diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index bc7ef03f4a2..48e86dbfa72 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -189,6 +189,8 @@ public class PhaseHandler implements java.io.Serializable { final List lands = CardLists.filter(playerTurn.getLandsInPlay(), Presets.UNTAPPED); playerTurn.setNumPowerSurgeLands(lands.size()); } + //update tokens + game.fireEvent(new GameEventTokenStateUpdate(playerTurn.getTokensInPlay())); game.fireEvent(new GameEventTurnPhase(playerTurn, phase, phaseType)); } diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 02b148414e7..d707f8c174d 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -2362,6 +2362,13 @@ public class Player extends GameEntity implements Comparable { return CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.CREATURES); } + /** + * use to get a list of tokens in play for a given player. + */ + public CardCollection getTokensInPlay() { + return CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.TOKEN); + } + /** * use to get a list of all lands a given player has on the battlefield. */ diff --git a/forge-game/src/main/java/forge/trackable/TrackableProperty.java b/forge-game/src/main/java/forge/trackable/TrackableProperty.java index 4a7186362c9..bae1cc49933 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableProperty.java +++ b/forge-game/src/main/java/forge/trackable/TrackableProperty.java @@ -82,6 +82,7 @@ public enum TrackableProperty { ChangedColorWords(TrackableTypes.StringMapType), ChangedTypes(TrackableTypes.StringMapType), + KeywordKey(TrackableTypes.StringType), HasDeathtouch(TrackableTypes.BooleanType), HasDefender(TrackableTypes.BooleanType), HasDoubleStrike(TrackableTypes.BooleanType), diff --git a/forge-gui-mobile/src/forge/screens/match/views/VField.java b/forge-gui-mobile/src/forge/screens/match/views/VField.java index 583fecb2710..20ce926a90b 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VField.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VField.java @@ -110,6 +110,9 @@ public class VField extends FContainer { if (!c.hasCardAttachments() && cardName.equals(c.getCurrentState().getName()) && card.hasSameCounters(c) && + card.getCurrentState().getKeywordKey().equals(c.getCurrentState().getKeywordKey()) && + card.isTapped() == c.isTapped() && // don't stack tapped tokens on untapped tokens + card.isSick() == c.isSick() && //don't stack sick tokens on non sick card.isToken() == c.isToken()) { //don't stack tokens on top of non-tokens CardAreaPanel cPanel = CardAreaPanel.get(c); while (cPanel.getNextPanelInStack() != null) { diff --git a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java index 799364cff24..d3bdc446df1 100644 --- a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java +++ b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java @@ -357,6 +357,12 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { return processCards(cards, cardsRefreshDetails); } + @Override + public Void visit(final GameEventTokenStateUpdate event) { + processCards(event.cards, cardsRefreshDetails); + return processCards(event.cards, cardsUpdate); + } + @Override public Void visit(final GameEventShuffle event) { //pfps the change to the library has already been performed by a setCards call