Hidden and Double Agenda better as Keyword (#7093)

* Hidden and Double Agenda better as Keyword
This commit is contained in:
Hans Mackowiak
2025-02-28 10:22:13 +01:00
committed by GitHub
parent 27d5766abb
commit d02dd67016
8 changed files with 34 additions and 20 deletions

View File

@@ -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));

View File

@@ -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()) {

View File

@@ -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 {

View File

@@ -7591,6 +7591,14 @@ public class Card extends GameEntity implements Comparable<Card>, 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()) {

View File

@@ -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<ICardFace> 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);
}

View File

@@ -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}."),

View File

@@ -3079,9 +3079,15 @@ public class Player extends GameEntity implements Comparable<Player> {
// 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<Player> {
this.extraZones.add(hand);
}
com.add(conspire);
if (addToCommand) {
com.add(conspire);
}
}
// Attractions

View File

@@ -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;