From d02dd67016d54bb75ccb14da205c591699f4545e Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Fri, 28 Feb 2025 10:22:13 +0100 Subject: [PATCH] Hidden and Double Agenda better as Keyword (#7093) * Hidden and Double Agenda better as Keyword --- .../main/java/forge/ai/PlayerControllerAi.java | 4 ++-- .../main/java/forge/ai/ability/SetStateAi.java | 3 ++- .../game/ability/effects/SetStateEffect.java | 6 +++--- .../src/main/java/forge/game/card/Card.java | 8 ++++++++ .../java/forge/game/card/CardFactoryUtil.java | 10 +++++----- .../main/java/forge/game/keyword/Keyword.java | 2 ++ .../src/main/java/forge/game/player/Player.java | 16 ++++++++++++---- .../spellability/SpellAbilityRestriction.java | 5 ----- 8 files changed, 34 insertions(+), 20 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 2cddfca804c..81668086ab9 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -1389,11 +1389,11 @@ public class PlayerControllerAi extends PlayerController { oppLibrary = CardLists.getValidCards(oppLibrary, valid, source.getController(), source, sa); } - if (source != null && source.getState(CardStateName.Original).hasIntrinsicKeyword("Hidden agenda")) { + if (source != null && source.getState(CardStateName.Original).hasKeyword(Keyword.HIDDEN_AGENDA)) { // If any Conspiracies are present, try not to choose the same name twice // (otherwise the AI will spam the same name) for (Card consp : player.getCardsIn(ZoneType.Command)) { - if (consp.getState(CardStateName.Original).hasIntrinsicKeyword("Hidden agenda")) { + if (consp.getState(CardStateName.Original).hasKeyword(Keyword.HIDDEN_AGENDA)) { String chosenName = consp.getNamedCard(); if (!chosenName.isEmpty()) { aiLibrary = CardLists.filter(aiLibrary, CardPredicates.nameNotEquals(chosenName)); diff --git a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java index 8f11347cc5a..73b9f4e2daa 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java @@ -6,6 +6,7 @@ import forge.ai.SpellAbilityAi; import forge.card.CardStateName; import forge.game.ability.AbilityUtils; import forge.game.card.*; +import forge.game.keyword.Keyword; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -142,7 +143,7 @@ public class SetStateAi extends SpellAbilityAi { return false; } // hidden agenda - if (card.getState(CardStateName.Original).hasIntrinsicKeyword("Hidden agenda") + if (card.getState(CardStateName.Original).hasKeyword(Keyword.HIDDEN_AGENDA) && card.isInZone(ZoneType.Command)) { String chosenName = card.getNamedCard(); for (Card cast : ai.getGame().getStack().getSpellsCastThisTurn()) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java index 4611047be93..6e7a5bfb5ff 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java @@ -9,6 +9,7 @@ import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.*; import forge.game.event.GameEventCardStatsChanged; +import forge.game.keyword.Keyword; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.trigger.TriggerHandler; @@ -53,7 +54,6 @@ public class SetStateEffect extends SpellAbilityEffect { final Game game = host.getGame(); final boolean remChanged = sa.hasParam("RememberChanged"); - final boolean hiddenAgenda = sa.hasParam("HiddenAgenda"); final boolean optional = sa.hasParam("Optional"); final CardCollection transformedCards = new CardCollection(); @@ -194,8 +194,8 @@ public class SetStateEffect extends SpellAbilityEffect { } else if (sa.isCloakUp()) { String sb = p + " has uncloaked " + gameCard.getName(); game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb); - } else if (hiddenAgenda) { - if (gameCard.hasKeyword("Double agenda")) { + } else if (sa.isKeyword(Keyword.HIDDEN_AGENDA) || sa.isKeyword(Keyword.DOUBLE_AGENDA)) { + if (sa.isKeyword(Keyword.DOUBLE_AGENDA)) { String sb = p + " has revealed " + gameCard.getName() + " with the chosen names: " + gameCard.getNamedCards(); game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb); } else { 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 46436ecab53..8d75e0f22ec 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -7591,6 +7591,14 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player, false)); } } + if (isFaceDown() && isInZone(ZoneType.Command)) { + for (KeywordInterface k : oState.getCachedKeyword(Keyword.HIDDEN_AGENDA)) { + abilities.addAll(k.getAbilities()); + } + for (KeywordInterface k : oState.getCachedKeyword(Keyword.DOUBLE_AGENDA)) { + abilities.addAll(k.getAbilities()); + } + } // Add Modal Spells if (isModal() && hasState(CardStateName.Modal)) { for (SpellAbility sa : getState(CardStateName.Modal).getSpellAbilities()) { diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index c8a300d918f..e909c0d1bc6 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -215,7 +215,7 @@ public class CardFactoryUtil { return manifestUp; } - public static boolean handleHiddenAgenda(Player player, Card card) { + public static boolean handleHiddenAgenda(Player player, Card card, KeywordInterface ki) { SpellAbility sa = new SpellAbility.EmptySa(card); sa.putParam("AILogic", card.getSVar("AgendaLogic")); Predicate cpp = x -> true; @@ -228,7 +228,7 @@ public class CardFactoryUtil { } card.addNamedCard(name); - if (card.hasKeyword("Double agenda")) { + if (ki.getKeyword().equals(Keyword.DOUBLE_AGENDA)) { String name2 = player.getController().chooseCardName(sa, cpp, "Card.!NamedCard", "Name a second card for " + card.getName()); if (name2 == null || name2.isEmpty()) { @@ -239,14 +239,14 @@ public class CardFactoryUtil { card.turnFaceDown(); card.addMayLookAt(player.getGame().getNextTimestamp(), ImmutableList.of(player)); - card.addSpellAbility(abilityRevealHiddenAgenda(card)); + ki.addSpellAbility(abilityRevealHiddenAgenda(card)); return true; } private static SpellAbility abilityRevealHiddenAgenda(final Card sourceCard) { String ab = "ST$ SetState | Cost$ 0" - + " | ConditionDefined$ Self | ConditionPresent$ Card.faceDown+inZoneCommand" - + " | HiddenAgenda$ True" + + " | PresentDefined$ Self | IsPresent$ Card.faceDown+inZoneCommand" + + " | ActivationZone$ Command | Secondary$ True" + " | Mode$ TurnFaceUp | SpellDescription$ Reveal this Hidden Agenda at any time."; return AbilityFactory.getAbility(ab, sourceCard); } diff --git a/forge-game/src/main/java/forge/game/keyword/Keyword.java b/forge-game/src/main/java/forge/game/keyword/Keyword.java index 0eacfdde42b..95f3743bc8c 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -57,6 +57,7 @@ public enum Keyword { DISGUISE("Disguise", KeywordWithCost.class, false, "You may cast this card face down for {3} as a 2/2 creature with ward {2}. Turn it face up any time for its disguise cost."), DISTURB("Disturb", KeywordWithCost.class, false, "You may cast this card from your graveyard transformed for its disturb cost."), DOCTORS_COMPANION("Doctor's companion", Partner.class, true, "You can have two commanders if the other is the Doctor."), + DOUBLE_AGENDA("Double agenda", SimpleKeyword.class, false, "Start the game with this conspiracy face down in the command zone and secretly choose two different card names. You may turn this conspiracy face up any time and reveal those names."), DOUBLE_STRIKE("Double Strike", SimpleKeyword.class, true, "This creature deals both first-strike and regular combat damage."), DOUBLE_TEAM("Double team", SimpleKeyword.class, true, "When this creature attacks, if it's not a token, conjure a duplicate of it into your hand. Then both cards perpetually lose double team."), DREDGE("Dredge", KeywordWithAmount.class, false, "If you would draw a card, instead you may put exactly {%d:card} from the top of your library into your graveyard. If you do, return this card from your graveyard to your hand. Otherwise, draw a card."), @@ -99,6 +100,7 @@ public enum Keyword { HAUNT("Haunt", SimpleKeyword.class, false, "When this is put into a graveyard, exile it haunting target creature."), HEXPROOF("Hexproof", Hexproof.class, true, "This can't be the target of %s spells or abilities your opponents control."), HIDEAWAY("Hideaway", KeywordWithAmount.class, false, "When this permanent enters, look at the top {%d:card} of your library, exile one face down, then put the rest on the bottom of your library."), + HIDDEN_AGENDA("Hidden agenda", SimpleKeyword.class, false, "Start the game with this conspiracy face down in the command zone and secretly choose a card name. You may turn this conspiracy face up any time and reveal that name."), HORSEMANSHIP("Horsemanship", SimpleKeyword.class, true, "This creature can't be blocked except by creatures with horsemanship."), IMPENDING("Impending", KeywordWithCostAndAmount.class, false, "If you cast this spell for its impending cost, it enters with {%2$d:time counter} and isn't a creature until the last is removed. At the beginning of your end step, remove a time counter from it."), IMPROVISE("Improvise", SimpleKeyword.class, true, "Your artifacts can help cast this spell. Each artifact you tap after you're done activating mana abilities pays for {1}."), 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 106969c23af..29f77c60a35 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -3079,9 +3079,15 @@ public class Player extends GameEntity implements Comparable { // Conspiracies for (IPaperCard cp : registeredPlayer.getConspiracies()) { Card conspire = Card.fromPaperCard(cp, this); - if (conspire.hasKeyword("Hidden agenda") || conspire.hasKeyword("Double agenda")) { - if (!CardFactoryUtil.handleHiddenAgenda(this, conspire)) { - continue; + boolean addToCommand = true; + for (KeywordInterface ki : conspire.getKeywords(Keyword.HIDDEN_AGENDA)) { + if (!CardFactoryUtil.handleHiddenAgenda(this, conspire, ki)) { + addToCommand = false; + } + } + for (KeywordInterface ki : conspire.getKeywords(Keyword.DOUBLE_AGENDA)) { + if (!CardFactoryUtil.handleHiddenAgenda(this, conspire, ki)) { + addToCommand = false; } } @@ -3093,7 +3099,9 @@ public class Player extends GameEntity implements Comparable { this.extraZones.add(hand); } - com.add(conspire); + if (addToCommand) { + com.add(conspire); + } } // Attractions diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java index d3b44e71e7a..3a425af9465 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java @@ -229,11 +229,6 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { if (cardZone == null || this.getZone() == null || !cardZone.is(this.getZone())) { // If Card is not in the default activating zone, do some additional checks - - // A conspiracy with hidden agenda: reveal at any time - if (cardZone != null && cardZone.is(ZoneType.Command) && sa.hasParam("HiddenAgenda")) { - return true; - } if (sa.hasParam("AdditionalActivationZone")) { if (cardZone != null && cardZone.is(ZoneType.valueOf(sa.getParam("AdditionalActivationZone")))) { return true;