From cb21a3aaad97d4878e517f46823ed9b5c5ecd528 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Tue, 25 Aug 2026 07:33:13 +0200 Subject: [PATCH 1/8] Card: move Adventure and Omen to getAllPossibleAbilities --- .../src/main/java/forge/game/card/Card.java | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) 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 b874a1e9a96..fd68e4389f6 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -70,6 +70,7 @@ import java.util.*; import java.util.Map.Entry; +import java.util.function.Consumer; import java.util.function.Predicate; import static java.lang.Math.max; @@ -3457,11 +3458,6 @@ public void updateSpellAbilities(List list, CardState state) { ck.applySpellAbility(list); } - if (!isInPlay() && hasState(CardStateName.Secondary) && state.getStateName() == CardStateName.Original) { - // Adventure and Omen may only be cast not from Battlefield - list.addAll(getState(CardStateName.Secondary).getSpellAbilities()); - } - // keywords should already been cleanup by layers getUnhiddenKeywords(state).applySpellAbility(list); } @@ -7392,10 +7388,7 @@ public List getAllPossibleAbilities(final Player player, final boo public List getAllPossibleAbilities(final Player player, final boolean removeUnplayable, final Multimap unhiddenAltCost) { CardState oState = getOriginalState(CardStateName.Original); final List abilities = Lists.newArrayList(); - for (SpellAbility sa : getSpellAbilities()) { - if (sa.isAdventure() && isOnAdventure()) { - continue; // skip since it's already on adventure - } + Consumer consumer = sa -> { abilities.add(sa); //add alternative costs as additional spell abilities List altCost = GameActionUtil.getAlternativeCosts(sa, player, false); @@ -7403,6 +7396,12 @@ public List getAllPossibleAbilities(final Player player, final boo if (unhiddenAltCost != null) { unhiddenAltCost.putAll(sa, altCost); } + }; + for (SpellAbility sa : getSpellAbilities()) { + if (sa.isAdventure() && isOnAdventure()) { + continue; // skip since it's already on adventure + } + consumer.accept(sa); } if (isFaceDown() && isInZone(ZoneType.Exile)) { @@ -7423,12 +7422,19 @@ public List getAllPossibleAbilities(final Player player, final boo for (SpellAbility sa : getState(CardStateName.Backside).getSpellAbilities()) { // only add Spells there if (sa.isSpell() || sa.isLandAbility()) { - abilities.add(sa); - List altCost = GameActionUtil.getAlternativeCosts(sa, player, false); - abilities.addAll(altCost); - if (unhiddenAltCost != null) { - unhiddenAltCost.putAll(sa, altCost); - } + consumer.accept(sa); + } + } + } + + if (!isInPlay() && hasState(CardStateName.Secondary) && getCurrentStateName() == CardStateName.Original) { + for (SpellAbility sa : getState(CardStateName.Secondary).getSpellAbilities()) { + if (sa.isAdventure() && isOnAdventure()) { + continue; // skip since it's already on adventure + } + // only add Spells there + if (sa.isSpell() || sa.isLandAbility()) { + consumer.accept(sa); } } } From 6c115b1cb3db011b3f4490cdec30ea3f70640f44 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Tue, 25 Aug 2026 11:05:30 +0200 Subject: [PATCH 2/8] cleanup hasNoAbilities --- forge-game/src/main/java/forge/game/card/Card.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) 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 fd68e4389f6..1442376d40c 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -3439,16 +3439,10 @@ public boolean hasNoAbilities() { return false; } for (SpellAbility sa : getSpellAbilities()) { - // while Adventure and Omen are part of Secondary - if ((sa.isAdventure() || sa.isOmen()) && !getCurrentStateName().equals(sa.getCardStateName())) { + if (sa.isLandAbility() || sa.isBasicSpell()) { continue; } - if (sa.isLandAbility()) { - continue; - } - if (!(sa instanceof SpellPermanent && sa.isBasicSpell())) { - return false; - } + return false; } return true; } From 7865adb70c3b105f4ce731d16f3af260e42f7dff Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Wed, 26 Aug 2026 11:48:06 +0200 Subject: [PATCH 3/8] Update Card.java If BasicSpell has something other than ManaCost, it's a Static in disguise --- forge-game/src/main/java/forge/game/card/Card.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 1442376d40c..15cfcddbea2 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -3439,7 +3439,10 @@ public boolean hasNoAbilities() { return false; } for (SpellAbility sa : getSpellAbilities()) { - if (sa.isLandAbility() || sa.isBasicSpell()) { + if (sa.isLandAbility()) { + continue; + } + if (sa.isBasicSpell() && sa.getPayCosts().isOnlyManaCost()) { continue; } return false; From 873738ab91e52652b2786ad36830e47fa5d46960 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Wed, 26 Aug 2026 21:12:04 +0200 Subject: [PATCH 4/8] fix Adventure/Omen from PlayEffect --- forge-game/src/main/java/forge/game/ability/AbilityUtils.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index aa35c2ffd71..c40f4d195e2 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -2920,6 +2920,9 @@ public static final List getSpellsFromPlayEffect(final Card tgtCar if (tgtCard.isModal() && tgtCard.hasState(CardStateName.Backside)) { collectSpellsForPlayEffect(list, tgtCard.getState(CardStateName.Backside), controller, withAltCost); } + if (tgtCard.hasState(CardStateName.Secondary)) { + collectSpellsForPlayEffect(list, tgtCard.getState(CardStateName.Secondary), controller, withAltCost); + } for (SpellAbility s : list) { if (s.isLandAbility()) { From 4103b9396c5e2864be52852c925701f15804589d Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 5 Sep 2026 14:48:32 +0200 Subject: [PATCH 5/8] ~ update desc --- .../src/main/java/forge/game/card/Card.java | 19 +-------------- .../java/forge/gui/card/CardDetailUtil.java | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 23 deletions(-) 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 15cfcddbea2..62a97460175 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -3000,24 +3000,7 @@ public String getAbilityText(final CardState state) { String sAbility = formatSpellAbility(sa); - // add Adventure to AbilityText - if (sa.isAdventure() && state.getStateName().equals(CardStateName.Original)) { - StringBuilder sbSA = new StringBuilder(); - sbSA.append(Localizer.getInstance().getMessage("lblAdventure")); - sbSA.append(" — ").append(CardTranslation.getTranslatedName(getState(CardStateName.Secondary))); - sbSA.append(" ").append(sa.getPayCosts().toSimpleString()); - sbSA.append(": "); - sbSA.append(sAbility); - sAbility = sbSA.toString(); - } else if (sa.isOmen() && state.getStateName().equals(CardStateName.Original)) { - StringBuilder sbSA = new StringBuilder(); - sbSA.append(Localizer.getInstance().getMessage("lblOmen")); - sbSA.append(" — ").append(CardTranslation.getTranslatedName(getState(CardStateName.Secondary))); - sbSA.append(" ").append(sa.getPayCosts().toSimpleString()); - sbSA.append(": "); - sbSA.append(sAbility); - sAbility = sbSA.toString(); - } else if (sa.isSpell() && sa.isBasicSpell()) { + if (sa.isSpell() && sa.isBasicSpell()) { continue; } else if (sa.hasParam("DescriptionFromChosenName") && !getNamedCard().isEmpty()) { String name = getNamedCard(); diff --git a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java index 995b4d657f4..009e50c3efa 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java +++ b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java @@ -657,14 +657,27 @@ public static String composeCardText(final CardStateView state, final GameView g area.append("Owner: ").append(card.getOwner().toString()); } + if (card.hasAlternateState() && card.getAlternateState().getType().hasSubtype("Adventure")) { + area.append("\n\n"); + area.append(Localizer.getInstance().getMessage("lblAdventure") + " — " + getAlternateStateDesc(card)); + } + + if (card.hasAlternateState() && card.getAlternateState().getType().hasSubtype("Omen")) { + area.append("\n\n"); + area.append(Localizer.getInstance().getMessage("lblOmen") + " — " + getAlternateStateDesc(card)); + } + if (card.hasPreparedSpell()) { area.append("\n\n"); - area.append(Localizer.getInstance().getMessage("lblPrepared") + " — " - + card.getAlternateState().getName() + " " - + card.getAlternateState().getManaCost().toString() + ": " - + card.getAlternateState().getAbilityText() - ); + area.append(Localizer.getInstance().getMessage("lblPrepared") + " — " + getAlternateStateDesc(card)); } return area.toString().trim(); } + + protected static String getAlternateStateDesc(CardView card) { + CardStateView state = card.getAlternateState(); + return state.getName() + " " + + state.getManaCost().toString() + ": " + + state.getAbilityText(); + } } From 44adb0ae23c99fd885e0bbbae26ff076d597e292 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 6 Sep 2026 09:48:49 +0200 Subject: [PATCH 6/8] getAlternateState only on Original --- .../java/forge/gui/card/CardDetailUtil.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java index 009e50c3efa..c468f995dd6 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java +++ b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java @@ -657,20 +657,23 @@ public static String composeCardText(final CardStateView state, final GameView g area.append("Owner: ").append(card.getOwner().toString()); } - if (card.hasAlternateState() && card.getAlternateState().getType().hasSubtype("Adventure")) { - area.append("\n\n"); - area.append(Localizer.getInstance().getMessage("lblAdventure") + " — " + getAlternateStateDesc(card)); - } + if (card.hasAlternateState() && state.getState() == CardStateName.Original) { + if (card.getAlternateState().getType().hasSubtype("Adventure")) { + area.append("\n\n"); + area.append(Localizer.getInstance().getMessage("lblAdventure") + " — " + getAlternateStateDesc(card)); + } - if (card.hasAlternateState() && card.getAlternateState().getType().hasSubtype("Omen")) { - area.append("\n\n"); - area.append(Localizer.getInstance().getMessage("lblOmen") + " — " + getAlternateStateDesc(card)); - } + if (card.getAlternateState().getType().hasSubtype("Omen")) { + area.append("\n\n"); + area.append(Localizer.getInstance().getMessage("lblOmen") + " — " + getAlternateStateDesc(card)); + } - if (card.hasPreparedSpell()) { - area.append("\n\n"); - area.append(Localizer.getInstance().getMessage("lblPrepared") + " — " + getAlternateStateDesc(card)); + if (card.hasPreparedSpell()) { + area.append("\n\n"); + area.append(Localizer.getInstance().getMessage("lblPrepared") + " — " + getAlternateStateDesc(card)); + } } + return area.toString().trim(); } From 841378ddd43d13684c2a11cf557085eb609523ce Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sun, 6 Sep 2026 11:09:58 +0200 Subject: [PATCH 7/8] Fix deck editor display attempt --- forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java index c468f995dd6..209c4603193 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java +++ b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java @@ -681,6 +681,6 @@ protected static String getAlternateStateDesc(CardView card) { CardStateView state = card.getAlternateState(); return state.getName() + " " + state.getManaCost().toString() + ": " - + state.getAbilityText(); + + (card.getId() > 0 ? state.getAbilityText() : state.getOracleText()); } } From 01075322b7360df6062e0d467d31f98e31666f1e Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 6 Sep 2026 20:50:06 +0200 Subject: [PATCH 8/8] ~ fix PlayerControllerHuman DevCastSpell --- .../src/main/java/forge/player/PlayerControllerHuman.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 6c06053862b..9babe9fe530 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -3420,7 +3420,9 @@ private void addCardToZone(ZoneType zone, final boolean repeatLast, final boolea // ensure triggered abilities fire getGame().getTriggerHandler().runWaitingTriggers(); } else { - final FCollectionView choices1 = forgeCard.getBasicSpells(); + // this is really needed (for rollbacks at least) + getGame().getAction().moveToHand(forgeCard, null); + final List choices1 = forgeCard.getAllPossibleAbilities(p, false); if (choices1.isEmpty()) { return; // when would it happen? } @@ -3429,7 +3431,7 @@ private void addCardToZone(ZoneType zone, final boolean repeatLast, final boolea if (choices1.size() == 1) { sa = choices1.iterator().next(); } else { - sa = repeatLast ? lastAddedSA : getGui().oneOrNone(localizer.getMessage("lblChoose"), (FCollection) choices1); + sa = repeatLast ? lastAddedSA : getGui().oneOrNone(localizer.getMessage("lblChoose"), choices1); } if (sa == null) { return; // happens if cancelled @@ -3437,8 +3439,6 @@ private void addCardToZone(ZoneType zone, final boolean repeatLast, final boolea lastAddedSA = sa; - // this is really needed (for rollbacks at least) - getGame().getAction().moveToHand(forgeCard, null); // Human player is choosing targets for an ability // controlled by chosen player. sa.setActivatingPlayer(p);