From b96365e6da0e6dbdefa8febf4e9b087ee3ad7cc7 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 8 Aug 2022 12:43:23 +0200 Subject: [PATCH 01/21] Rework DelayedTrigger controller --- .../ability/effects/DelayedTriggerEffect.java | 12 +++--------- .../ability/effects/ImmediateTriggerEffect.java | 15 +++------------ .../java/forge/game/trigger/TriggerHandler.java | 3 ++- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java index f39011e5ceb..316885c6234 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java @@ -11,7 +11,6 @@ import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; -import forge.game.card.CardUtil; import forge.game.player.Player; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; @@ -46,13 +45,8 @@ public class DelayedTriggerEffect extends SpellAbilityEffect { mapParams.remove("SpellDescription"); } - // in case the card moved before the delayed trigger can be created, need to check the latest card state for right timestamp - Card gameCard = game.getCardState(host); - Card lki = CardUtil.getLKICopy(gameCard); - lki.clearControllers(); - lki.setOwner(sa.getActivatingPlayer()); - final Trigger delTrig = TriggerHandler.parseTrigger(mapParams, lki, sa.isIntrinsic(), null); - delTrig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true)); + final Trigger delTrig = TriggerHandler.parseTrigger(mapParams, host, sa.isIntrinsic(), null); + delTrig.setSpawningAbility(sa.copy(host, sa.getActivatingPlayer(), true)); delTrig.setActiveZone(null); if (sa.hasParam("RememberObjects")) { @@ -76,7 +70,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect { } if (sa.hasAdditionalAbility("Execute")) { - SpellAbility overridingSA = sa.getAdditionalAbility("Execute").copy(lki, sa.getActivatingPlayer(), false); + SpellAbility overridingSA = sa.getAdditionalAbility("Execute").copy(host, sa.getActivatingPlayer(), false); // need to reset the parent, additionalAbility does set it to this if (overridingSA instanceof AbilitySub) { ((AbilitySub)overridingSA).setParent(null); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java index 0c5b98cbd78..cf2c22ca2b7 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java @@ -8,8 +8,6 @@ import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; -import forge.game.card.CardUtil; -import forge.game.replacement.ReplacementType; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; import forge.game.trigger.Trigger; @@ -45,15 +43,8 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect { mapParams.put("Mode", TriggerType.Immediate.name()); - // in case the card moved before the delayed trigger can be created, need to check the latest card state for right timestamp - Card gameCard = game.getCardState(host); - Card lki = CardUtil.getLKICopy(gameCard); - lki.clearControllers(); - lki.setOwner(sa.getActivatingPlayer()); - // if this trigger is part of ETBReplacement it shouldn't run with LKI from incomplete zone change (Mimic Vat + Wall of Stolen Identity) - final Card trigHost = sa.getRootAbility().getReplacementEffect() != null && sa.getRootAbility().getReplacementEffect().getMode().equals(ReplacementType.Moved) && gameCard.getZone() == null ? gameCard : lki; - final Trigger immediateTrig = TriggerHandler.parseTrigger(mapParams, trigHost, sa.isIntrinsic(), null); - immediateTrig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true)); + final Trigger immediateTrig = TriggerHandler.parseTrigger(mapParams, host, sa.isIntrinsic(), null); + immediateTrig.setSpawningAbility(sa.copy(host, sa.getActivatingPlayer(), true)); // Need to copy paid costs @@ -71,7 +62,7 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect { } if (sa.hasAdditionalAbility("Execute")) { - SpellAbility overridingSA = sa.getAdditionalAbility("Execute").copy(lki, sa.getActivatingPlayer(), false); + SpellAbility overridingSA = sa.getAdditionalAbility("Execute").copy(host, sa.getActivatingPlayer(), false); // need to set Parent to null, otherwise it might have wrong root ability if (overridingSA instanceof AbilitySub) { ((AbilitySub)overridingSA).setParent(null); diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java index 4ae8797484f..3614e541825 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java @@ -541,8 +541,9 @@ public class TriggerHandler { sa.changeText(); } } else { + Player controller = delayedTriggers.contains(regtrig) ? sa.getActivatingPlayer() : host.getController(); // need to copy the SA because of TriggeringObjects - sa = sa.copy(host, host.getController(), false); + sa = sa.copy(host, controller, false); } sa.setLastStateBattlefield(game.getLastStateBattlefield()); From fe6ccc0f31a3f5beef606a92adb16104d779eaa6 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 8 Aug 2022 12:44:04 +0200 Subject: [PATCH 02/21] Fix Epic + Stifle --- .../main/java/forge/game/spellability/SpellAbility.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 1f12c72bd4b..da3dd1b2383 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1942,7 +1942,13 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } } - return topSA.getHostCard().isValid(tgt.getValidTgts(), getActivatingPlayer(), getHostCard(), this); + Card host = topSA.getHostCard(); + // if from an effect it's always Delayed Trigger + if (host.isImmutable() && !host.isEmblem()) { + host = host.getEffectSource(); + } + + return host.isValid(tgt.getValidTgts(), getActivatingPlayer(), getHostCard(), this); } public boolean isTargeting(GameObject o) { From ed48bc027eaa1bfc8dcc43d71c291d1a171243ab Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 8 Aug 2022 12:48:38 +0200 Subject: [PATCH 03/21] Fix Emblem targeting --- forge-gui/res/cardsfolder/b/brokers_confluence.txt | 2 +- forge-gui/res/cardsfolder/d/disallow.txt | 2 +- forge-gui/res/cardsfolder/l/lithoform_engine.txt | 2 +- forge-gui/res/cardsfolder/n/nimble_obstructionist.txt | 2 +- forge-gui/res/cardsfolder/s/stifle.txt | 2 +- forge-gui/res/cardsfolder/s/strionic_resonator.txt | 2 +- forge-gui/res/cardsfolder/s/sublime_epiphany.txt | 2 +- forge-gui/res/cardsfolder/t/tales_end.txt | 2 +- forge-gui/res/cardsfolder/t/trickbind.txt | 2 +- .../res/cardsfolder/upcoming/emerald_dragon_dissonant_wave.txt | 2 +- forge-gui/res/cardsfolder/v/voidslime.txt | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/forge-gui/res/cardsfolder/b/brokers_confluence.txt b/forge-gui/res/cardsfolder/b/brokers_confluence.txt index 9403ec36c53..eb6139b78a9 100644 --- a/forge-gui/res/cardsfolder/b/brokers_confluence.txt +++ b/forge-gui/res/cardsfolder/b/brokers_confluence.txt @@ -4,7 +4,7 @@ Types:Instant A:SP$ Charm | Choices$ Proliferate,PhaseOut,CounterAbility | CharmNum$ 3 | CanRepeatModes$ True SVar:Proliferate:DB$ Proliferate | SpellDescription$ Proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) SVar:PhaseOut:DB$ Phases | ValidTgts$ Creature | SpellDescription$ Target creature phases out. (Treat it and anything attached to it as though they don't exist until its controller's next turn.) -SVar:CounterAbility:DB$ Counter | TargetType$ Activated,Triggered | ValidTgts$ Card | TgtPrompt$ Select target activated or triggered ability | SpellDescription$ Counter target activated or triggered ability. +SVar:CounterAbility:DB$ Counter | TargetType$ Activated,Triggered | ValidTgts$ Card,Emblem | TgtPrompt$ Select target activated or triggered ability | SpellDescription$ Counter target activated or triggered ability. DeckHas:Ability$Proliferate DeckHints:Ability$Counters & Type$Planeswalker Oracle:Choose three. You may choose the same mode more than once.\n• Proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.)\n• Target creature phases out. (Treat it and anything attached to it as though they don't exist until its controller's next turn.)\n• Counter target activated or triggered ability. diff --git a/forge-gui/res/cardsfolder/d/disallow.txt b/forge-gui/res/cardsfolder/d/disallow.txt index e9849637477..b16f256938d 100644 --- a/forge-gui/res/cardsfolder/d/disallow.txt +++ b/forge-gui/res/cardsfolder/d/disallow.txt @@ -1,6 +1,6 @@ Name:Disallow ManaCost:1 U U Types:Instant -A:SP$ Counter | Cost$ 1 U U | TargetType$ Spell,Activated,Triggered | TgtPrompt$ Select target spell or ability | ValidTgts$ Card | SpellDescription$ Counter target spell, activated ability, or triggered ability. +A:SP$ Counter | Cost$ 1 U U | TargetType$ Spell,Activated,Triggered | TgtPrompt$ Select target spell or ability | ValidTgts$ Card,Emblem | SpellDescription$ Counter target spell, activated ability, or triggered ability. AI:RemoveDeck:All Oracle:Counter target spell, activated ability, or triggered ability. (Mana abilities can't be targeted.) diff --git a/forge-gui/res/cardsfolder/l/lithoform_engine.txt b/forge-gui/res/cardsfolder/l/lithoform_engine.txt index 90024364827..867b36d9a33 100644 --- a/forge-gui/res/cardsfolder/l/lithoform_engine.txt +++ b/forge-gui/res/cardsfolder/l/lithoform_engine.txt @@ -1,7 +1,7 @@ Name:Lithoform Engine ManaCost:4 Types:Legendary Artifact -A:AB$ CopySpellAbility | Cost$ 2 T | TgtPrompt$ Select target activated or triggered ability you control | TargetType$ Activated.YouCtrl,Triggered.YouCtrl | ValidTgts$ Card | MayChooseTarget$ True | StackDescription$ SpellDescription | SpellDescription$ Copy target activated or triggered ability you control. You may choose new targets for the copy. +A:AB$ CopySpellAbility | Cost$ 2 T | TgtPrompt$ Select target activated or triggered ability you control | TargetType$ Activated.YouCtrl,Triggered.YouCtrl | ValidTgts$ Card,Emblem | MayChooseTarget$ True | StackDescription$ SpellDescription | SpellDescription$ Copy target activated or triggered ability you control. You may choose new targets for the copy. A:AB$ CopySpellAbility | Cost$ 3 T | TgtPrompt$ Select target instant or sorcery spell you control | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TargetType$ Spell | MayChooseTarget$ True | SpellDescription$ Copy target instant or sorcery spell you control. You may choose new targets for the copy. A:AB$ CopySpellAbility | Cost$ 4 T | TgtPrompt$ Select target permanent spell you control | ValidTgts$ Permanent.YouCtrl | TargetType$ Spell | SpellDescription$ Copy target permanent spell you control. (The copy becomes a token.) Oracle:{2}, {T}: Copy target activated or triggered ability you control. You may choose new targets for the copy.\n{3}, {T}: Copy target instant or sorcery spell you control. You may choose new targets for the copy.\n{4}, {T}: Copy target permanent spell you control. (The copy becomes a token.) diff --git a/forge-gui/res/cardsfolder/n/nimble_obstructionist.txt b/forge-gui/res/cardsfolder/n/nimble_obstructionist.txt index 3d0375d5168..d6c919952ca 100644 --- a/forge-gui/res/cardsfolder/n/nimble_obstructionist.txt +++ b/forge-gui/res/cardsfolder/n/nimble_obstructionist.txt @@ -6,5 +6,5 @@ K:Flash K:Flying K:Cycling:2 U T:Mode$ Cycled | ValidCard$ Card.Self | Execute$ TrigCounter | TriggerDescription$ When you cycle CARDNAME, counter target activated or triggered ability you don't control. -SVar:TrigCounter:DB$ Counter | TargetType$ Activated,Triggered | ValidTgts$ Card.YouDontCtrl +SVar:TrigCounter:DB$ Counter | TargetType$ Activated.YouDontCtrl,Triggered.YouDontCtrl | ValidTgts$ Card,Emblem Oracle:Flash\nFlying\nCycling {2}{U} ({2}{U}, Discard this card: Draw a card.)\nWhen you cycle Nimble Obstructionist, counter target activated or triggered ability you don't control. diff --git a/forge-gui/res/cardsfolder/s/stifle.txt b/forge-gui/res/cardsfolder/s/stifle.txt index 99c3cb0f5f8..48743b8a728 100644 --- a/forge-gui/res/cardsfolder/s/stifle.txt +++ b/forge-gui/res/cardsfolder/s/stifle.txt @@ -1,6 +1,6 @@ Name:Stifle ManaCost:U Types:Instant -A:SP$ Counter | Cost$ U | TgtPrompt$ Select target Activated or Triggered Ability | ValidTgts$ Card | TargetType$ Activated,Triggered | SpellDescription$ Counter target activated or triggered ability. +A:SP$ Counter | Cost$ U | TgtPrompt$ Select target Activated or Triggered Ability | ValidTgts$ Card,Emblem | TargetType$ Activated,Triggered | SpellDescription$ Counter target activated or triggered ability. AI:RemoveDeck:All Oracle:Counter target activated or triggered ability. (Mana abilities can't be targeted.) diff --git a/forge-gui/res/cardsfolder/s/strionic_resonator.txt b/forge-gui/res/cardsfolder/s/strionic_resonator.txt index b043b300c75..75a527332c0 100644 --- a/forge-gui/res/cardsfolder/s/strionic_resonator.txt +++ b/forge-gui/res/cardsfolder/s/strionic_resonator.txt @@ -1,7 +1,7 @@ Name:Strionic Resonator ManaCost:2 Types:Artifact -A:AB$ CopySpellAbility | Cost$ 2 T | TargetType$ Triggered.YouCtrl | ValidTgts$ Card | MayChooseTarget$ True | SpellDescription$ Copy target triggered ability you control. You may choose new targets for the copy. +A:AB$ CopySpellAbility | Cost$ 2 T | TargetType$ Triggered.YouCtrl | ValidTgts$ Card,Emblem | MayChooseTarget$ True | SpellDescription$ Copy target triggered ability you control. You may choose new targets for the copy. AI:RemoveDeck:All AI:RemoveDeck:Random Oracle:{2}, {T}: Copy target triggered ability you control. You may choose new targets for the copy. (A triggered ability uses the words "when," "whenever," or "at.") diff --git a/forge-gui/res/cardsfolder/s/sublime_epiphany.txt b/forge-gui/res/cardsfolder/s/sublime_epiphany.txt index 4b0f8db5915..7b667c50282 100644 --- a/forge-gui/res/cardsfolder/s/sublime_epiphany.txt +++ b/forge-gui/res/cardsfolder/s/sublime_epiphany.txt @@ -3,7 +3,7 @@ ManaCost:4 U U Types:Instant A:SP$ Charm | MinCharmNum$ 1 | CharmNum$ 5 | Choices$ DBCounterSpell,DBCounterAbility,DBReturn,DBCopy,DBDraw SVar:DBCounterSpell:DB$ Counter | TargetType$ Spell | ValidTgts$ Card | TgtPrompt$ Select target spell | SpellDescription$ Counter target spell. -SVar:DBCounterAbility:DB$ Counter | TargetType$ Activated,Triggered | ValidTgts$ Card | TgtPrompt$ Select target activated or triggered ability | SpellDescription$ Counter target activated or triggered ability. +SVar:DBCounterAbility:DB$ Counter | TargetType$ Activated,Triggered | ValidTgts$ Card,Emblem | TgtPrompt$ Select target activated or triggered ability | SpellDescription$ Counter target activated or triggered ability. SVar:DBReturn:DB$ ChangeZone | Origin$ Battlefield | Destination$ Hand | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | AILogic$ Good | SpellDescription$ Return target nonland permanent to its owner's hand. SVar:DBCopy:DB$ CopyPermanent | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SpellDescription$ Create a token that's a copy of target creature you control. SVar:DBDraw:DB$ Draw | NumCards$ 1 | ValidTgts$ Player | TgtPrompt$ Select target player | SpellDescription$ Target player draws a card. diff --git a/forge-gui/res/cardsfolder/t/tales_end.txt b/forge-gui/res/cardsfolder/t/tales_end.txt index 777a32f0f9e..86dcad5ccd9 100644 --- a/forge-gui/res/cardsfolder/t/tales_end.txt +++ b/forge-gui/res/cardsfolder/t/tales_end.txt @@ -1,6 +1,6 @@ Name:Tale's End ManaCost:1 U Types:Instant -A:SP$ Counter | Cost$ 1 U | TargetType$ Activated,Triggered,Spell.Legendary | TgtPrompt$ Select target activated ability, triggered ability, or legendary spell | ValidTgts$ Card | SpellDescription$ Counter target activated ability, triggered ability, or legendary spell. +A:SP$ Counter | Cost$ 1 U | TargetType$ Activated,Triggered,Spell.Legendary | TgtPrompt$ Select target activated ability, triggered ability, or legendary spell | ValidTgts$ Card,Emblem | SpellDescription$ Counter target activated ability, triggered ability, or legendary spell. AI:RemoveDeck:Random Oracle:Counter target activated ability, triggered ability, or legendary spell. diff --git a/forge-gui/res/cardsfolder/t/trickbind.txt b/forge-gui/res/cardsfolder/t/trickbind.txt index b5dea757921..c8f12275bf8 100644 --- a/forge-gui/res/cardsfolder/t/trickbind.txt +++ b/forge-gui/res/cardsfolder/t/trickbind.txt @@ -2,7 +2,7 @@ Name:Trickbind ManaCost:1 U Types:Instant K:Split second -A:SP$ Counter | Cost$ 1 U | TargetType$ Activated,Triggered | TgtPrompt$ Select target Activated or Triggered Ability | RememberCountered$ True | ValidTgts$ Card | SubAbility$ DBEffect | SpellDescription$ Counter target activated or triggered ability. If a permanent's ability is countered this way, activated abilities of that permanent can't be activated this turn. (Mana abilities can't be targeted.) +A:SP$ Counter | Cost$ 1 U | TargetType$ Activated,Triggered | TgtPrompt$ Select target Activated or Triggered Ability | RememberCountered$ True | ValidTgts$ Card,Emblem | SubAbility$ DBEffect | SpellDescription$ Counter target activated or triggered ability. If a permanent's ability is countered this way, activated abilities of that permanent can't be activated this turn. (Mana abilities can't be targeted.) SVar:DBEffect:DB$ Effect | StaticAbilities$ STCantBeActivated | RememberObjects$ Remembered | SubAbility$ DBCleanup | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ1 SVar:STCantBeActivated:Mode$ CantBeActivated | EffectZone$ Command | ValidCard$ Permanent.IsRemembered SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/upcoming/emerald_dragon_dissonant_wave.txt b/forge-gui/res/cardsfolder/upcoming/emerald_dragon_dissonant_wave.txt index 573276475f5..a061dbb2380 100644 --- a/forge-gui/res/cardsfolder/upcoming/emerald_dragon_dissonant_wave.txt +++ b/forge-gui/res/cardsfolder/upcoming/emerald_dragon_dissonant_wave.txt @@ -12,5 +12,5 @@ ALTERNATE Name:Dissonant Wave ManaCost:2 G Types:Instant Adventure -A:SP$ Counter | TargetType$ Activated,Triggered | TgtPrompt$ Select target activated or triggered ability from a noncreature source | ValidTgts$ Card.nonCreature | SpellDescription$ Counter target activated or triggered ability from a noncreature source. (Then exile this card. You may cast the creature later from exile.) +A:SP$ Counter | TargetType$ Activated,Triggered | TgtPrompt$ Select target activated or triggered ability from a noncreature source | ValidTgts$ Card.nonCreature,Emblem | SpellDescription$ Counter target activated or triggered ability from a noncreature source. (Then exile this card. You may cast the creature later from exile.) Oracle:Counter target activated or triggered ability from a noncreature source. (Then exile this card. You may cast the creature later from exile.) diff --git a/forge-gui/res/cardsfolder/v/voidslime.txt b/forge-gui/res/cardsfolder/v/voidslime.txt index 4aab2439325..36078b63ff7 100644 --- a/forge-gui/res/cardsfolder/v/voidslime.txt +++ b/forge-gui/res/cardsfolder/v/voidslime.txt @@ -1,6 +1,6 @@ Name:Voidslime ManaCost:G U U Types:Instant -A:SP$ Counter | Cost$ G U U | TargetType$ Spell,Activated,Triggered | TgtPrompt$ Select target spell or ability | ValidTgts$ Card | SpellDescription$ Counter target spell, activated ability, or triggered ability. +A:SP$ Counter | Cost$ G U U | TargetType$ Spell,Activated,Triggered | TgtPrompt$ Select target spell or ability | ValidTgts$ Card,Emblem | SpellDescription$ Counter target spell, activated ability, or triggered ability. AI:RemoveDeck:All Oracle:Counter target spell, activated ability, or triggered ability. (Mana abilities can't be targeted.) From a06367dfc6dcde2f4cf4a79da14646ec81e075ac Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 8 Aug 2022 14:53:03 +0200 Subject: [PATCH 04/21] Switch to SpawningAbility --- .../forge/game/ability/SpellAbilityEffect.java | 18 ++++++++++++------ .../game/ability/effects/AddPhaseEffect.java | 1 + .../game/ability/effects/AddTurnEffect.java | 1 + .../ability/effects/CountersPutEffect.java | 1 + .../main/java/forge/game/trigger/Trigger.java | 1 - .../forge/game/trigger/TriggerHandler.java | 2 +- 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index c6fa06282c7..9a32c699239 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -326,6 +326,7 @@ public abstract class SpellAbilityEffect { final SpellAbility newSa = AbilityFactory.getAbility(trigSA, sa.getHostCard()); newSa.setIntrinsic(intrinsic); trig.setOverridingAbility(newSa); + trig.setSpawningAbility(sa.copy(sa.getHostCard(), sa.getActivatingPlayer(), true)); sa.getActivatingPlayer().getGame().getTriggerHandler().registerDelayedTrigger(trig); } @@ -641,10 +642,16 @@ public abstract class SpellAbilityEffect { final Card hostCard = sa.getHostCard(); final Game game = hostCard.getGame(); hostCard.addUntilLeavesBattlefield(triggerList.allCards()); - final TriggerHandler trigHandler = game.getTriggerHandler(); - final Card lki = CardUtil.getLKICopy(hostCard); - lki.clearControllers(); - lki.setOwner(sa.getActivatingPlayer()); + final TriggerHandler trigHandler = game.getTriggerHandler(); + + final Card lki; + if (sa.hasParam("ReturnAbility")) { + lki = CardUtil.getLKICopy(hostCard); + lki.clearControllers(); + lki.setOwner(sa.getActivatingPlayer()); + } else { + lki = null; + } return new GameCommand() { @@ -671,14 +678,13 @@ public abstract class SpellAbilityEffect { if (newCard == null || !newCard.equalsWithTimestamp(c)) { continue; } - Trigger trig = null; if (sa.hasAdditionalAbility("ReturnAbility")) { String valid = sa.getParamOrDefault("ReturnValid", "Card.IsTriggerRemembered"); String trigSA = "Mode$ ChangesZone | Origin$ " + cell.getColumnKey() + " | Destination$ " + cell.getRowKey() + " | ValidCard$ " + valid + " | TriggerDescription$ " + sa.getAdditionalAbility("ReturnAbility").getParam("SpellDescription"); - trig = TriggerHandler.parseTrigger(trigSA, hostCard, sa.isIntrinsic(), null); + Trigger trig = TriggerHandler.parseTrigger(trigSA, hostCard, sa.isIntrinsic(), null); trig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true)); trig.setActiveZone(null); trig.addRemembered(newCard); diff --git a/forge-game/src/main/java/forge/game/ability/effects/AddPhaseEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AddPhaseEffect.java index 3ec8a944860..3857c670c7a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AddPhaseEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AddPhaseEffect.java @@ -67,6 +67,7 @@ public class AddPhaseEffect extends SpellAbilityEffect { SpellAbility overridingSA = AbilityFactory.getAbility(sa.getSVar(sa.getParam("ExtraPhaseDelayedTriggerExcute")), sa.getHostCard()); overridingSA.setActivatingPlayer(sa.getActivatingPlayer()); delTrig.setOverridingAbility(overridingSA); + delTrig.setSpawningAbility(sa.copy(sa.getHostCard(), sa.getActivatingPlayer(), true)); extraPhase.addTrigger(delTrig); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java index 34861a8e750..45a6b70afa0 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java @@ -56,6 +56,7 @@ public class AddTurnEffect extends SpellAbilityEffect { SpellAbility overridingSA = AbilityFactory.getAbility(sa.getSVar(sa.getParam("ExtraTurnDelayedTriggerExcute")), sa.getHostCard()); overridingSA.setActivatingPlayer(sa.getActivatingPlayer()); delTrig.setOverridingAbility(overridingSA); + delTrig.setSpawningAbility(sa.copy(sa.getHostCard(), sa.getActivatingPlayer(), true)); extra.addTrigger(delTrig); } if (sa.hasParam("SkipUntap")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java index 1725a655fdc..fa835122984 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java @@ -665,6 +665,7 @@ public class CountersPutEffect extends SpellAbilityEffect { final SpellAbility newSa = AbilityFactory.getAbility(trigSA, host); newSa.setIntrinsic(intrinsic); trig.setOverridingAbility(newSa); + trig.setSpawningAbility(sa.copy(host, sa.getActivatingPlayer(), true)); sa.getActivatingPlayer().getGame().getTriggerHandler().registerDelayedTrigger(trig); } } diff --git a/forge-game/src/main/java/forge/game/trigger/Trigger.java b/forge-game/src/main/java/forge/game/trigger/Trigger.java index e3a5cb0ed0e..83b717b98d4 100644 --- a/forge-game/src/main/java/forge/game/trigger/Trigger.java +++ b/forge-game/src/main/java/forge/game/trigger/Trigger.java @@ -531,7 +531,6 @@ public abstract class Trigger extends TriggerReplacementBase { public SpellAbility getSpawningAbility() { return spawningAbility; } - public void setSpawningAbility(SpellAbility ability) { spawningAbility = ability; } diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java index 3614e541825..5cb79ab4e2c 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java @@ -541,7 +541,7 @@ public class TriggerHandler { sa.changeText(); } } else { - Player controller = delayedTriggers.contains(regtrig) ? sa.getActivatingPlayer() : host.getController(); + Player controller = regtrig.getSpawningAbility() != null ? regtrig.getSpawningAbility().getActivatingPlayer() : host.getController(); // need to copy the SA because of TriggeringObjects sa = sa.copy(host, controller, false); } From 3d33b955c08d5e49d8e05efd0ad1136913a172c4 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 8 Aug 2022 14:56:48 +0200 Subject: [PATCH 05/21] matchesValid delayed trigger support --- .../src/main/java/forge/game/CardTraitBase.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index b393a4d389c..4ca5241c7dd 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -23,6 +23,7 @@ import forge.game.card.IHasCardView; import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.spellability.SpellAbility; +import forge.game.trigger.Trigger; import forge.game.zone.ZoneType; import forge.util.Expressions; @@ -187,7 +188,15 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView, if (srcCard == null) { return false; } - return matchesValid(o, valids, srcCard, srcCard.getController()); + + Player controller = srcCard.getController(); + if (this instanceof Trigger) { + // check for delayed trigger + if (((Trigger) this).getSpawningAbility() != null) { + controller = ((Trigger) this).getSpawningAbility().getActivatingPlayer(); + } + } + return matchesValid(o, valids, srcCard, controller); } public boolean matchesValid(final Object o, final String[] valids, final Card srcCard, final Player srcPlayer) { From 3abd5322160cf1ee73072f1cadaa9a431f3ebe61 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Tue, 9 Aug 2022 20:18:24 +0200 Subject: [PATCH 06/21] Update TgtPrompt --- forge-gui/res/cardsfolder/n/nimble_obstructionist.txt | 2 +- forge-gui/res/cardsfolder/s/stifle.txt | 2 +- forge-gui/res/cardsfolder/s/strionic_resonator.txt | 2 +- forge-gui/res/cardsfolder/t/trickbind.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/cardsfolder/n/nimble_obstructionist.txt b/forge-gui/res/cardsfolder/n/nimble_obstructionist.txt index d6c919952ca..2466268c486 100644 --- a/forge-gui/res/cardsfolder/n/nimble_obstructionist.txt +++ b/forge-gui/res/cardsfolder/n/nimble_obstructionist.txt @@ -6,5 +6,5 @@ K:Flash K:Flying K:Cycling:2 U T:Mode$ Cycled | ValidCard$ Card.Self | Execute$ TrigCounter | TriggerDescription$ When you cycle CARDNAME, counter target activated or triggered ability you don't control. -SVar:TrigCounter:DB$ Counter | TargetType$ Activated.YouDontCtrl,Triggered.YouDontCtrl | ValidTgts$ Card,Emblem +SVar:TrigCounter:DB$ Counter | TargetType$ Activated.YouDontCtrl,Triggered.YouDontCtrl | TgtPrompt$ Choose target ability | ValidTgts$ Card,Emblem Oracle:Flash\nFlying\nCycling {2}{U} ({2}{U}, Discard this card: Draw a card.)\nWhen you cycle Nimble Obstructionist, counter target activated or triggered ability you don't control. diff --git a/forge-gui/res/cardsfolder/s/stifle.txt b/forge-gui/res/cardsfolder/s/stifle.txt index 48743b8a728..33fb35721e5 100644 --- a/forge-gui/res/cardsfolder/s/stifle.txt +++ b/forge-gui/res/cardsfolder/s/stifle.txt @@ -1,6 +1,6 @@ Name:Stifle ManaCost:U Types:Instant -A:SP$ Counter | Cost$ U | TgtPrompt$ Select target Activated or Triggered Ability | ValidTgts$ Card,Emblem | TargetType$ Activated,Triggered | SpellDescription$ Counter target activated or triggered ability. +A:SP$ Counter | Cost$ U | TgtPrompt$ Choose target ability | ValidTgts$ Card,Emblem | TargetType$ Activated,Triggered | SpellDescription$ Counter target activated or triggered ability. AI:RemoveDeck:All Oracle:Counter target activated or triggered ability. (Mana abilities can't be targeted.) diff --git a/forge-gui/res/cardsfolder/s/strionic_resonator.txt b/forge-gui/res/cardsfolder/s/strionic_resonator.txt index 75a527332c0..da9def82a88 100644 --- a/forge-gui/res/cardsfolder/s/strionic_resonator.txt +++ b/forge-gui/res/cardsfolder/s/strionic_resonator.txt @@ -1,7 +1,7 @@ Name:Strionic Resonator ManaCost:2 Types:Artifact -A:AB$ CopySpellAbility | Cost$ 2 T | TargetType$ Triggered.YouCtrl | ValidTgts$ Card,Emblem | MayChooseTarget$ True | SpellDescription$ Copy target triggered ability you control. You may choose new targets for the copy. +A:AB$ CopySpellAbility | Cost$ 2 T | TargetType$ Triggered.YouCtrl | ValidTgts$ Card,Emblem | TgtPrompt$ Choose target triggered ability you control | MayChooseTarget$ True | SpellDescription$ Copy target triggered ability you control. You may choose new targets for the copy. AI:RemoveDeck:All AI:RemoveDeck:Random Oracle:{2}, {T}: Copy target triggered ability you control. You may choose new targets for the copy. (A triggered ability uses the words "when," "whenever," or "at.") diff --git a/forge-gui/res/cardsfolder/t/trickbind.txt b/forge-gui/res/cardsfolder/t/trickbind.txt index c8f12275bf8..edc3a439209 100644 --- a/forge-gui/res/cardsfolder/t/trickbind.txt +++ b/forge-gui/res/cardsfolder/t/trickbind.txt @@ -2,7 +2,7 @@ Name:Trickbind ManaCost:1 U Types:Instant K:Split second -A:SP$ Counter | Cost$ 1 U | TargetType$ Activated,Triggered | TgtPrompt$ Select target Activated or Triggered Ability | RememberCountered$ True | ValidTgts$ Card,Emblem | SubAbility$ DBEffect | SpellDescription$ Counter target activated or triggered ability. If a permanent's ability is countered this way, activated abilities of that permanent can't be activated this turn. (Mana abilities can't be targeted.) +A:SP$ Counter | Cost$ 1 U | TargetType$ Activated,Triggered | TgtPrompt$ Choose target ability | RememberCountered$ True | ValidTgts$ Card,Emblem | SubAbility$ DBEffect | SpellDescription$ Counter target activated or triggered ability. If a permanent's ability is countered this way, activated abilities of that permanent can't be activated this turn. (Mana abilities can't be targeted.) SVar:DBEffect:DB$ Effect | StaticAbilities$ STCantBeActivated | RememberObjects$ Remembered | SubAbility$ DBCleanup | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ1 SVar:STCantBeActivated:Mode$ CantBeActivated | EffectZone$ Command | ValidCard$ Permanent.IsRemembered SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True From b2364ccce82e7d6aba3d3783fe44ea2940fb9ca3 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Tue, 9 Aug 2022 17:46:35 -0400 Subject: [PATCH 07/21] StaticAbilityPanharmonicon.applyPanharmoniconAbility support ValidCause for Wulfgar --- .../staticability/StaticAbilityPanharmonicon.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java index 53936989b56..268ba3562a4 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java @@ -107,6 +107,19 @@ public class StaticAbilityPanharmonicon { if (table.filterCards(origin == null ? null : ImmutableList.of(ZoneType.smartValueOf(origin)), ZoneType.smartValueOf(destination), stAb.getParam("ValidCause"), card, stAb).isEmpty()) { return false; } + } else if (trigMode.equals(TriggerType.Attacks)) { + if (stAb.hasParam("ValidCause")) { + if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Attacker))) { + return false; + } + } + } else if (trigMode.equals(TriggerType.AttackersDeclared) + || trigMode.equals(TriggerType.AttackersDeclaredOneTarget)) { + if (stAb.hasParam("ValidCause")) { + if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Attackers))) { + return false; + } + } } else if (trigMode.equals(TriggerType.SpellCastOrCopy) || trigMode.equals(TriggerType.SpellCast) || trigMode.equals(TriggerType.SpellCopy)) { // Check if the spell cast and the caster match From 39db05f97a2edbd8245495bc2d6c66fa6602f44d Mon Sep 17 00:00:00 2001 From: Northmoc Date: Tue, 9 Aug 2022 19:18:32 -0400 Subject: [PATCH 08/21] spelldrain_assassin.txt add AI hints --- forge-gui/res/cardsfolder/s/spelldrain_assassin.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/forge-gui/res/cardsfolder/s/spelldrain_assassin.txt b/forge-gui/res/cardsfolder/s/spelldrain_assassin.txt index 27ab1bf9680..d087064414b 100644 --- a/forge-gui/res/cardsfolder/s/spelldrain_assassin.txt +++ b/forge-gui/res/cardsfolder/s/spelldrain_assassin.txt @@ -7,4 +7,6 @@ SVar:TrigChoose:DB$ ChooseCard | ChoiceZone$ Hand | Choices$ Sorcery.YouOwn,Inst SVar:DBEffect:DB$ Effect | StaticAbilities$ PerpetualCasualty | Name$ Spelldrain Assassin's Perpetual Effect | Duration$ Permanent | SubAbility$ DBCleanup SVar:PerpetualCasualty:Mode$ Continuous | Affected$ Card.ChosenCard | AddKeyword$ Casualty:2 | EffectZone$ Command | AffectedZone$ Battlefield,Hand,Graveyard,Exile,Stack,Library,Command | Description$ The chosen card perpetually gains casualty 2. SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True +DeckHints:Type$Instant|Sorcery +DeckHas:Ability$Sacrifice Oracle:When Spelldrain Assassin enters the battlefield, choose an instant or sorcery card in your hand. It perpetually gains casualty 2. From 0ebb717057618ed55a88da67d10e74e881bef2f5 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Tue, 9 Aug 2022 19:21:06 -0400 Subject: [PATCH 09/21] grinning_totem.txt refactor --- .../res/cardsfolder/g/grinning_totem.txt | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/forge-gui/res/cardsfolder/g/grinning_totem.txt b/forge-gui/res/cardsfolder/g/grinning_totem.txt index 63c9d4cf4a8..b391bf607be 100644 --- a/forge-gui/res/cardsfolder/g/grinning_totem.txt +++ b/forge-gui/res/cardsfolder/g/grinning_totem.txt @@ -1,21 +1,11 @@ Name:Grinning Totem ManaCost:4 Types:Artifact -A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | ValidTgts$ Player.Opponent | IsCurse$ True | Chooser$ You | Origin$ Library | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | SubAbility$ TotemEffect | SpellDescription$ Search target opponent's library for a card and exile it. Then that player shuffles. Until the beginning of your next upkeep, you may play that card. At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard. | StackDescription$ SpellDescription -SVar:TotemEffect:DB$ Effect | StaticAbilities$ STGrinning | Duration$ Permanent | RememberObjects$ Remembered | Triggers$ TrigDuration,TrigReturn,TrigLandPlayed,TrigCast | SubAbility$ DBResetSVar -# Even though the Effect is "Permanent", it's not really permanent -SVar:DBResetSVar:DB$ StoreSVar | SVar$ ActiveTotem | Type$ Number | Expression$ 1 | SubAbility$ DBCleanup +A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | ValidTgts$ Opponent | IsCurse$ True | Chooser$ You | Origin$ Library | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | SubAbility$ TotemEffect | SpellDescription$ Search target opponent's library for a card and exile it. Then that player shuffles. Until the beginning of your next upkeep, you may play that card. At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard. | StackDescription$ {p:You} searches {p:Targeted}'s library for a card and exiles it. Then {p:Targeted} shuffles. +SVar:TotemEffect:DB$ Effect | StaticAbilities$ STGrinning | Duration$ Permanent | RememberObjects$ Remembered | ExileOnMoved$ Exile | SubAbility$ DBDelayedTrigger | SpellDescription$ Until the beginning of your next upkeep, you may play that card. At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard. +SVar:STGrinning:Mode$ Continuous | Affected$ Card.IsRemembered | MayPlay$ True | EffectZone$ Command | AffectedZone$ Exile | Duration$ UntilYourNextUpkeep | Description$ Until the beginning of your next upkeep, you may play that card. +SVar:DBDelayedTrigger:DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | IsPresent$ Card.IsRemembered | PresentZone$ Exile | RememberObjects$ Remembered | Execute$ DBReturn | SubAbility$ DBCleanup | TriggerDescription$ At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard. +SVar:DBReturn:DB$ ChangeZone | Defined$ RememberedLKI | Origin$ Exile | Destination$ Graveyard SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:ActiveTotem:Number$1 -SVar:STGrinning:Mode$ Continuous | Affected$ Card.IsRemembered+OppOwn | MayPlay$ True | EffectZone$ Command | AffectedZone$ Exile | CheckSVar$ ActiveTotem | Description$ Until the beginning of your next upkeep, you may play that card. -# Turn off the duration at the beginning of the upkeep statically -SVar:TrigDuration:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Static$ True | TriggerZones$ Command | Execute$ DBDuration -SVar:DBDuration:DB$ StoreSVar | SVar$ ActiveTotem | Type$ Number | Expression$ 0 -# Return the card as a normal trigger -SVar:TrigReturn:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | Execute$ DBReturn | OneOff$ True | TriggerDescription$ At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard. -SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Graveyard -# Remove Totem Effect when the card is played -SVar:TrigLandPlayed:Mode$ LandPlayed | ValidCard$ Land.IsRemembered | Static$ True | TriggerZones$ Command | Execute$ RemoveEffect -SVar:TrigCast:Mode$ SpellCast | ValidCard$ Card.IsRemembered | Static$ True | TriggerZones$ Command | Execute$ RemoveEffect AI:RemoveDeck:All Oracle:{2}, {T}, Sacrifice Grinning Totem: Search target opponent's library for a card and exile it. Then that player shuffles. Until the beginning of your next upkeep, you may play that card. At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard. From 555257c4486274651399bdfe2c0b9a919d157bf0 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Tue, 9 Aug 2022 19:46:59 -0400 Subject: [PATCH 10/21] distant_memories.txt refactor like Sin Prodder --- forge-gui/res/cardsfolder/d/distant_memories.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/cardsfolder/d/distant_memories.txt b/forge-gui/res/cardsfolder/d/distant_memories.txt index 2f7e977b90b..d695438707e 100644 --- a/forge-gui/res/cardsfolder/d/distant_memories.txt +++ b/forge-gui/res/cardsfolder/d/distant_memories.txt @@ -1,10 +1,13 @@ Name:Distant Memories ManaCost:2 U U Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | RememberChanged$ True | AILogic$ BestCard | SubAbility$ DBReturn | StackDescription$ SpellDescription | SpellDescription$ Search your library for a card, exile it, then shuffle. Any opponent may have you put that card into your hand. If no player does, you draw three cards. -SVar:DBReturn:DB$ ChangeZone | Origin$ Exile | Destination$ Hand | ChangeType$ Card.IsRemembered | Hidden$ True | Chooser$ Opponent | SelectPrompt$ Select a card to put in the opponent's hand, or they will draw three cards. | SkipCancelPrompt$ True | ForgetChanged$ True | SubAbility$ DBDraw -SVar:DBDraw:DB$ Draw | NumCards$ 3 | ConditionDefined$ Remembered | ConditionZone$ Exile | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup | StackDescription$ None -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +A:SP$ ChangeZone | Origin$ Library | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | Imprint$ True | AILogic$ BestCard | SubAbility$ DBReturn | StackDescription$ SpellDescription | SpellDescription$ Search your library for a card, exile it, then shuffle. Any opponent may have you put that card into your hand. If no player does, you draw three cards. +SVar:DBRepeat:DB$ RepeatEach | RepeatPlayers$ Opponent | RepeatSubAbility$ DBChoose | SubAbility$ DBDraw +SVar:DBChoose:DB$ GenericChoice | Defined$ Remembered | Choices$ DBToHand,DoNothing | ChoicePrompt$ Have them put the card in their hand? | ConditionDefined$ Imprinted | ConditionZone$ Exile | ConditionPresent$ Card | ConditionCompare$ EQ1 +SVar:DBToHand:DB$ ChangeZone | Origin$ Exile | Destination$ Hand | Defined$ Imprinted | Unimprint$ True | SpellDescription$ Yes +SVar:DoNothing:DB$ Cleanup | SpellDescription$ No +SVar:DBDraw:DB$ Draw | NumCards$ 3 | ConditionDefined$ Imprinted | ConditionZone$ Exile | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup | StackDescription$ None +SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True SVar:NeedsToPlayVar:X LE4 SVar:X:Count$InYourHand Oracle:Search your library for a card, exile it, then shuffle. Any opponent may have you put that card into your hand. If no player does, you draw three cards. From 3b8d335023eaa65bd2b88b649849217da939b415 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Tue, 9 Aug 2022 19:47:15 -0400 Subject: [PATCH 11/21] sin_prodder.txt tighten up UI --- forge-gui/res/cardsfolder/s/sin_prodder.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/cardsfolder/s/sin_prodder.txt b/forge-gui/res/cardsfolder/s/sin_prodder.txt index d8d55f84714..cb5cf921d9c 100644 --- a/forge-gui/res/cardsfolder/s/sin_prodder.txt +++ b/forge-gui/res/cardsfolder/s/sin_prodder.txt @@ -6,9 +6,9 @@ K:Menace T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPeek | TriggerDescription$ At the beginning of your upkeep, reveal the top card of your library. Any opponent may have you put that card into your graveyard. If a player does, Sin Prodder deals damage to that player equal to that card's mana value. Otherwise, put that card into your hand. SVar:TrigPeek:DB$ PeekAndReveal | PeekAmount$ 1 | RevealValid$ Card | ImprintRevealed$ True | SubAbility$ DBRepeat SVar:DBRepeat:DB$ RepeatEach | RepeatPlayers$ Opponent | RepeatSubAbility$ DBProd | SubAbility$ DBPutIntoHand -SVar:DBProd:DB$ GenericChoice | Defined$ Remembered | Choices$ DBMillAndDamage,DoNothing | ConditionDefined$ Imprinted | ConditionPresent$ Card | ConditionCompare$ EQ1 | AILogic$ SinProdder -SVar:DoNothing:DB$ Cleanup | SpellDescription$ Allow -SVar:DBMillAndDamage:DB$ Mill | Defined$ You | NumCards$ 1 | SubAbility$ DBDamage | SpellDescription$ Deny +SVar:DBProd:DB$ GenericChoice | Defined$ Remembered | Choices$ DBMillAndDamage,DoNothing | ChoicePrompt$ Have them put the card into their graveyard? | ConditionDefined$ Imprinted | ConditionPresent$ Card | ConditionCompare$ EQ1 | AILogic$ SinProdder +SVar:DoNothing:DB$ Cleanup | SpellDescription$ No +SVar:DBMillAndDamage:DB$ Mill | Defined$ You | NumCards$ 1 | SubAbility$ DBDamage | SpellDescription$ Yes (take damage) SVar:DBDamage:DB$ DealDamage | Defined$ Remembered | NumDmg$ X | SubAbility$ DBCleanup SVar:X:Imprinted$CardManaCost SVar:DBPutIntoHand:DB$ Dig | DigNum$ 1 | ChangeNum$ All | ChangeValid$ Card | DestinationZone$ Hand | SubAbility$ DBCleanup | ConditionDefined$ Imprinted | ConditionPresent$ Card | ConditionCompare$ EQ1 From 836ab10c9031d48ca59f9f54396df482a69638fd Mon Sep 17 00:00:00 2001 From: Northmoc Date: Tue, 9 Aug 2022 19:47:38 -0400 Subject: [PATCH 12/21] nature_demands_an_offering.txt "UnImprint" > "Unimprint" --- .../res/cardsfolder/n/nature_demands_an_offering.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/cardsfolder/n/nature_demands_an_offering.txt b/forge-gui/res/cardsfolder/n/nature_demands_an_offering.txt index f67bd24348f..0424bb67efe 100644 --- a/forge-gui/res/cardsfolder/n/nature_demands_an_offering.txt +++ b/forge-gui/res/cardsfolder/n/nature_demands_an_offering.txt @@ -3,16 +3,16 @@ ManaCost:no cost Types:Scheme T:Mode$ SetInMotion | ValidCard$ Card.Self | Execute$ ChooseCreature | TriggerZones$ Command | TriggerDescription$ When you set this scheme in motion, target opponent chooses a creature you don't control and puts it on top of its owner's library, then repeats this process for an artifact, an enchantment, and a land. Then the owner of each permanent chosen this way shuffles. SVar:ChooseCreature:DB$ ChooseCard | ValidTgts$ Opponent | Choices$ Creature.YouDontCtrl | Mandatory$ True | SubAbility$ BounceCreature -SVar:BounceCreature:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Battlefield | Destination$ Library | Imprint$ True | UnImprint$ True | LibraryPosition$ 0 | SubAbility$ DBRemember1 +SVar:BounceCreature:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Battlefield | Destination$ Library | Imprint$ True | Unimprint$ True | LibraryPosition$ 0 | SubAbility$ DBRemember1 SVar:DBRemember1:DB$ Pump | RememberObjects$ ImprintedOwner | SubAbility$ ChooseArtifact SVar:ChooseArtifact:DB$ ChooseCard | Defined$ ParentTarget | Choices$ Artifact.YouDontCtrl | Mandatory$ True | SubAbility$ BounceArtifact -SVar:BounceArtifact:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Battlefield | Destination$ Library | Imprint$ True | UnImprint$ True | LibraryPosition$ 0 | SubAbility$ DBRemember2 +SVar:BounceArtifact:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Battlefield | Destination$ Library | Imprint$ True | Unimprint$ True | LibraryPosition$ 0 | SubAbility$ DBRemember2 SVar:DBRemember2:DB$ Pump | RememberObjects$ ImprintedOwner | SubAbility$ ChooseEnchant SVar:ChooseEnchant:DB$ ChooseCard | Defined$ ParentTarget | Choices$ Enchantment.YouDontCtrl | Mandatory$ True | SubAbility$ BounceEnchant -SVar:BounceEnchant:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Battlefield | Destination$ Library | Imprint$ True | UnImprint$ True | LibraryPosition$ 0 | SubAbility$ DBRemember3 +SVar:BounceEnchant:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Battlefield | Destination$ Library | Imprint$ True | Unimprint$ True | LibraryPosition$ 0 | SubAbility$ DBRemember3 SVar:DBRemember3:DB$ Pump | RememberObjects$ ImprintedOwner | SubAbility$ ChooseLand SVar:ChooseLand:DB$ ChooseCard | Defined$ ParentTarget | Choices$ Land.YouDontCtrl | Mandatory$ True | SubAbility$ BounceLand -SVar:BounceLand:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Battlefield | Destination$ Library | Imprint$ True | UnImprint$ True | LibraryPosition$ 0 | SubAbility$ DBRemember4 +SVar:BounceLand:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Battlefield | Destination$ Library | Imprint$ True | Unimprint$ True | LibraryPosition$ 0 | SubAbility$ DBRemember4 SVar:DBRemember4:DB$ Pump | RememberObjects$ ImprintedOwner | SubAbility$ DBShuffle SVar:DBShuffle:DB$ Shuffle | Defined$ Remembered | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True From b04b1b89d3321efc72ee8635d0cc79f3e7d81cf6 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Tue, 9 Aug 2022 19:51:40 -0400 Subject: [PATCH 13/21] sin_prodder.txt shouldn't use Mill --- forge-gui/res/cardsfolder/s/sin_prodder.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-gui/res/cardsfolder/s/sin_prodder.txt b/forge-gui/res/cardsfolder/s/sin_prodder.txt index cb5cf921d9c..a8d7fd3843e 100644 --- a/forge-gui/res/cardsfolder/s/sin_prodder.txt +++ b/forge-gui/res/cardsfolder/s/sin_prodder.txt @@ -6,9 +6,9 @@ K:Menace T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPeek | TriggerDescription$ At the beginning of your upkeep, reveal the top card of your library. Any opponent may have you put that card into your graveyard. If a player does, Sin Prodder deals damage to that player equal to that card's mana value. Otherwise, put that card into your hand. SVar:TrigPeek:DB$ PeekAndReveal | PeekAmount$ 1 | RevealValid$ Card | ImprintRevealed$ True | SubAbility$ DBRepeat SVar:DBRepeat:DB$ RepeatEach | RepeatPlayers$ Opponent | RepeatSubAbility$ DBProd | SubAbility$ DBPutIntoHand -SVar:DBProd:DB$ GenericChoice | Defined$ Remembered | Choices$ DBMillAndDamage,DoNothing | ChoicePrompt$ Have them put the card into their graveyard? | ConditionDefined$ Imprinted | ConditionPresent$ Card | ConditionCompare$ EQ1 | AILogic$ SinProdder +SVar:DBProd:DB$ GenericChoice | Defined$ Remembered | Choices$ DBGraveAndDamage,DoNothing | ChoicePrompt$ Have them put the card into their graveyard? | ConditionDefined$ Imprinted | ConditionPresent$ Card | ConditionCompare$ EQ1 | AILogic$ SinProdder SVar:DoNothing:DB$ Cleanup | SpellDescription$ No -SVar:DBMillAndDamage:DB$ Mill | Defined$ You | NumCards$ 1 | SubAbility$ DBDamage | SpellDescription$ Yes (take damage) +SVar:DBGraveAndDamage:DB$ ChangeZone | Origin$ Library | Destination$ Graveyard | Defined$ Imprinted | SubAbility$ DBDamage | SpellDescription$ Yes (take damage) SVar:DBDamage:DB$ DealDamage | Defined$ Remembered | NumDmg$ X | SubAbility$ DBCleanup SVar:X:Imprinted$CardManaCost SVar:DBPutIntoHand:DB$ Dig | DigNum$ 1 | ChangeNum$ All | ChangeValid$ Card | DestinationZone$ Hand | SubAbility$ DBCleanup | ConditionDefined$ Imprinted | ConditionPresent$ Card | ConditionCompare$ EQ1 From cb8c9694b53e192656d24da64f8cfb35bc9ce3e3 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Tue, 9 Aug 2022 19:55:27 -0400 Subject: [PATCH 14/21] ChooseGenericEffectAi match Sin Prodder logic to new UI --- .../src/main/java/forge/ai/ability/ChooseGenericEffectAi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java index 596ee8f6671..fdb9634414e 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java @@ -198,7 +198,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi { } else if ("SinProdder".equals(logic)) { SpellAbility allow = null, deny = null; for (final SpellAbility sp : spells) { - if (sp.getDescription().equals("Allow")) { + if (sp.getDescription().equals("No")) { allow = sp; } else { deny = sp; From 152418976a8646909dfd36ad34b954a87534a6fd Mon Sep 17 00:00:00 2001 From: Northmoc Date: Wed, 10 Aug 2022 13:05:50 -0400 Subject: [PATCH 15/21] grinning_totem.txt fix the bad stuff --- forge-gui/res/cardsfolder/g/grinning_totem.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/forge-gui/res/cardsfolder/g/grinning_totem.txt b/forge-gui/res/cardsfolder/g/grinning_totem.txt index b391bf607be..5c4bf0ef0fd 100644 --- a/forge-gui/res/cardsfolder/g/grinning_totem.txt +++ b/forge-gui/res/cardsfolder/g/grinning_totem.txt @@ -1,11 +1,11 @@ Name:Grinning Totem ManaCost:4 Types:Artifact -A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | ValidTgts$ Opponent | IsCurse$ True | Chooser$ You | Origin$ Library | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | SubAbility$ TotemEffect | SpellDescription$ Search target opponent's library for a card and exile it. Then that player shuffles. Until the beginning of your next upkeep, you may play that card. At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard. | StackDescription$ {p:You} searches {p:Targeted}'s library for a card and exiles it. Then {p:Targeted} shuffles. -SVar:TotemEffect:DB$ Effect | StaticAbilities$ STGrinning | Duration$ Permanent | RememberObjects$ Remembered | ExileOnMoved$ Exile | SubAbility$ DBDelayedTrigger | SpellDescription$ Until the beginning of your next upkeep, you may play that card. At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard. -SVar:STGrinning:Mode$ Continuous | Affected$ Card.IsRemembered | MayPlay$ True | EffectZone$ Command | AffectedZone$ Exile | Duration$ UntilYourNextUpkeep | Description$ Until the beginning of your next upkeep, you may play that card. -SVar:DBDelayedTrigger:DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | IsPresent$ Card.IsRemembered | PresentZone$ Exile | RememberObjects$ Remembered | Execute$ DBReturn | SubAbility$ DBCleanup | TriggerDescription$ At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard. -SVar:DBReturn:DB$ ChangeZone | Defined$ RememberedLKI | Origin$ Exile | Destination$ Graveyard +A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | ValidTgts$ Opponent | IsCurse$ True | Chooser$ You | Origin$ Library | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | SubAbility$ TotemEffect | SpellDescription$ Search target opponent's library for a card and exile it. Then that player shuffles. | StackDescription$ {p:You} searches {p:Targeted}'s library for a card and exiles it. Then {p:Targeted} shuffles. +SVar:TotemEffect:DB$ Effect | StaticAbilities$ STGrinning | Duration$ UntilYourNextUpkeep | RememberObjects$ Remembered | ExileOnMoved$ Exile | SubAbility$ DBDelayedTrigger | SpellDescription$ Until the beginning of your next upkeep, you may play that card. +SVar:STGrinning:Mode$ Continuous | Affected$ Card.IsRemembered | MayPlay$ True | EffectZone$ Command | AffectedZone$ Exile | Description$ Until the beginning of your next upkeep, you may play that card. +SVar:DBDelayedTrigger:DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | RememberObjects$ Remembered | Execute$ DBReturn | IsPresent$ Card.IsTriggerRemembered | PresentZone$ Exile | SubAbility$ DBCleanup | TriggerDescription$ At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard. +SVar:DBReturn:DB$ ChangeZone | Defined$ DelayTriggerRememberedLKI | Origin$ Exile | Destination$ Graveyard SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:All Oracle:{2}, {T}, Sacrifice Grinning Totem: Search target opponent's library for a card and exile it. Then that player shuffles. Until the beginning of your next upkeep, you may play that card. At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard. From c26d51e2fed3c182b507a4e38b5bbe4ea3601a0c Mon Sep 17 00:00:00 2001 From: Northmoc Date: Wed, 10 Aug 2022 14:53:29 -0400 Subject: [PATCH 16/21] StaticAbilityPanharmonicon remove unneeded checks --- .../staticability/StaticAbilityPanharmonicon.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java index 268ba3562a4..e71134638b5 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java @@ -108,17 +108,13 @@ public class StaticAbilityPanharmonicon { return false; } } else if (trigMode.equals(TriggerType.Attacks)) { - if (stAb.hasParam("ValidCause")) { - if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Attacker))) { - return false; - } + if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Attacker))) { + return false; } } else if (trigMode.equals(TriggerType.AttackersDeclared) || trigMode.equals(TriggerType.AttackersDeclaredOneTarget)) { - if (stAb.hasParam("ValidCause")) { - if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Attackers))) { - return false; - } + if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Attackers))) { + return false; } } else if (trigMode.equals(TriggerType.SpellCastOrCopy) || trigMode.equals(TriggerType.SpellCast) || trigMode.equals(TriggerType.SpellCopy)) { From a7cd7926268eeb09d7691910a31e523c8f42dfe8 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Wed, 10 Aug 2022 15:43:11 -0400 Subject: [PATCH 17/21] waylay.txt fixup --- forge-gui/res/cardsfolder/w/waylay.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/cardsfolder/w/waylay.txt b/forge-gui/res/cardsfolder/w/waylay.txt index f0bb1767abe..b79c13b39c8 100644 --- a/forge-gui/res/cardsfolder/w/waylay.txt +++ b/forge-gui/res/cardsfolder/w/waylay.txt @@ -1,8 +1,9 @@ Name:Waylay ManaCost:2 W Types:Instant -A:SP$ Token | Cost$ 2 W | TokenAmount$ 3 | TokenScript$ w_2_2_knight | TokenOwner$ You | AtEOT$ Exile | SpellDescription$ Create three 2/2 white Knight creature tokens. Exile them at the beginning of the next cleanup step. -DeckHas:Ability$Token -AI:RemoveDeck:All -DeckHints:Type$Knight +A:SP$ Token | TokenAmount$ 3 | TokenScript$ w_2_2_knight | RememberTokens$ True | SubAbility$ DBDelayedTrigger | SpellDescription$ Create three 2/2 white Knight creature tokens. +SVar:DBDelayedTrigger:DB$ DelayedTrigger | RememberObjects$ Remembered | Mode$ Phase | Phase$ Cleanup | Execute$ TrigExileAll | SubAbility$ DBCleanup | TriggerDescription$ Exile them at the beginning of the next cleanup step. +SVar:TrigExileAll:DB$ ChangeZone | Defined$ DelayTriggerRememberedLKI | Origin$ Battlefield | Destination$ Exile +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +DeckHas:Ability$Token & Type$Knight Oracle:Create three 2/2 white Knight creature tokens. Exile them at the beginning of the next cleanup step. From 93e18656c1415cf6493f283f6271556b333eeb31 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Thu, 11 Aug 2022 16:57:41 +0800 Subject: [PATCH 18/21] Update AiController.java (#1314) - closes #1313 --- forge-ai/src/main/java/forge/ai/AiController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index b2b2ce2ea96..3fc6b92661f 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -1682,8 +1682,8 @@ public class AiController { Iterables.removeIf(saList, new Predicate() { @Override - public boolean apply(final SpellAbility spellAbility) { - return spellAbility instanceof LandAbility; + public boolean apply(final SpellAbility spellAbility) { //don't include removedAI cards if somehow the AI can play the ability or gain control of unsupported card + return spellAbility instanceof LandAbility || (spellAbility.getHostCard() != null && spellAbility.getHostCard().getRules().getAiHints().getRemAIDecks()); } }); From 05475a8339e2d82a1acbda10f9419fb4acc043e7 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Thu, 11 Aug 2022 18:30:55 +0800 Subject: [PATCH 19/21] revert multiplayer battle - should fix crash when encountering doppelganger/copyplayerdeck AI --- .../src/forge/adventure/scene/DuelScene.java | 299 ++++++++---------- 1 file changed, 135 insertions(+), 164 deletions(-) diff --git a/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java b/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java index 3535f7451d1..658afc68926 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java @@ -52,46 +52,48 @@ public class DuelScene extends ForgeScene { PlayerSprite player; RegisteredPlayer humanPlayer; private EffectData dungeonEffect; - Deck playerDeck; + Deck playerDeck, enemyDeck; boolean chaosBattle = false; boolean callbackExit = false; List playerExtras = new ArrayList<>(); List AIExtras = new ArrayList<>(); - public DuelScene() {} + public DuelScene() { + } @Override - public void dispose() {} + public void dispose() { + } public boolean hasCallbackExit() { return callbackExit; } public void GameEnd() { - boolean winner=humanPlayer == hostedMatch.getGame().getMatch().getWinner(); - String enemyName=(enemy.nameOverride.isEmpty() ? enemy.getData().name : enemy.nameOverride); + boolean winner = humanPlayer == hostedMatch.getGame().getMatch().getWinner(); + String enemyName = (enemy.nameOverride.isEmpty() ? enemy.getData().name : enemy.nameOverride); Current.player().clearBlessing(); if (chaosBattle && !winner) { callbackExit = true; - List insult = Lists.newArrayList("I'm sorry...","... ...." ,"Learn from your defeat.", - "I haven't begun to use my full power.","No matter how much you try, you still won't beat me.", - "Your technique need work.","Rookie.","That's your best?","Hah ha ha ha ha ha ha!","?!......... (Seriously?!)", - "Forget about a rematch. Practice more instead." ,"That was a 100% effort on my part! Well, actually, no... That was more like 50%.", - "If you expected me to lose out of generosity, I'm truly sorry!" ,"You'll appreciate that I held back during the match!", - "That's the best you can do?","Don't waste my time with your skills!","Ha-ha-ha! What's the matter?", - "I hope I didn't hurt your ego too badly... Oops!","This match... I think I've learned something from this.", - "Hey! Don't worry about it!","You are not worthy!","Hm! You should go back to playing puzzle games!", - "Thought you could beat me? Whew, talk about conceited.","*Yawn* ... Huh? It's over already? But I just woke up!", - "Next time bring an army. It might give you a chance." ,"The reason you lost is quite simple...", - "Is that all you can do?","You need to learn more to stand a chance.","You weren't that bad.","You made an effort at least.", - "From today, you can call me teacher.", "Hmph, predictable!", "I haven't used a fraction of my REAL power!" ); + List insult = Lists.newArrayList("I'm sorry...", "... ....", "Learn from your defeat.", + "I haven't begun to use my full power.", "No matter how much you try, you still won't beat me.", + "Your technique need work.", "Rookie.", "That's your best?", "Hah ha ha ha ha ha ha!", "?!......... (Seriously?!)", + "Forget about a rematch. Practice more instead.", "That was a 100% effort on my part! Well, actually, no... That was more like 50%.", + "If you expected me to lose out of generosity, I'm truly sorry!", "You'll appreciate that I held back during the match!", + "That's the best you can do?", "Don't waste my time with your skills!", "Ha-ha-ha! What's the matter?", + "I hope I didn't hurt your ego too badly... Oops!", "This match... I think I've learned something from this.", + "Hey! Don't worry about it!", "You are not worthy!", "Hm! You should go back to playing puzzle games!", + "Thought you could beat me? Whew, talk about conceited.", "*Yawn* ... Huh? It's over already? But I just woke up!", + "Next time bring an army. It might give you a chance.", "The reason you lost is quite simple...", + "Is that all you can do?", "You need to learn more to stand a chance.", "You weren't that bad.", "You made an effort at least.", + "From today, you can call me teacher.", "Hmph, predictable!", "I haven't used a fraction of my REAL power!"); String message = Aggregates.random(insult); FThreads.invokeInEdtNowOrLater(() -> FOptionPane.showMessageDialog(message, enemyName, new FBufferedImage(120, 120) { @Override protected void draw(Graphics g, float w, float h) { - if (FSkin.getAvatars().get(90000) != null) - g.drawImage(FSkin.getAvatars().get(90000), 0, 0, w, h); + if (FSkin.getAvatars().get(90001) != null) + g.drawImage(FSkin.getAvatars().get(90001), 0, 0, w, h); } }, new Callback() { @Override @@ -105,39 +107,38 @@ public class DuelScene extends ForgeScene { afterGameEnd(enemyName, winner); } } - void afterGameEnd(String enemyName, boolean winner) { - Gdx.app.postRunnable(new Runnable() { - @Override - public void run() { - SoundSystem.instance.setBackgroundMusic(MusicPlaylist.MENUS); //start background music - dungeonEffect = null; - callbackExit = false; - Forge.clearTransitionScreen(); - Forge.clearCurrentScreen(); - Scene last = Forge.switchToLast(); - if (last instanceof HudScene) { - Current.player().getStatistic().setResult(enemyName, winner); - ((HudScene) last).stage.setWinner(winner); - } + void afterGameEnd(String enemyName, boolean winner) { + Gdx.app.postRunnable(() -> { + SoundSystem.instance.setBackgroundMusic(MusicPlaylist.MENUS); //start background music + dungeonEffect = null; + callbackExit = false; + Forge.clearTransitionScreen(); + Forge.clearCurrentScreen(); + Scene last = Forge.switchToLast(); + + if (last instanceof HudScene) { + Current.player().getStatistic().setResult(enemyName, winner); + ((HudScene) last).stage.setWinner(winner); } }); } - void addEffects(RegisteredPlayer player,Array effects) { - if( effects == null ) return; - //Apply various combat effects. - int lifeMod=0; - int changeStartCards=0; - Array startCards=new Array<>(); - for(EffectData data:effects) { - lifeMod+=data.lifeModifier; - changeStartCards+= data.changeStartCards; + void addEffects(RegisteredPlayer player, Array effects) { + if (effects == null) return; + //Apply various combat effects. + int lifeMod = 0; + int changeStartCards = 0; + Array startCards = new Array<>(); + + for (EffectData data : effects) { + lifeMod += data.lifeModifier; + changeStartCards += data.changeStartCards; startCards.addAll(data.startBattleWithCards()); } - player.addExtraCardsOnBattlefield(startCards); - player.setStartingLife(Math.max(1,lifeMod+player.getStartingLife())); - player.setStartingHand(player.getStartingHand()+changeStartCards); + player.setCardsOnBattlefield(startCards); + player.setStartingLife(Math.max(1, lifeMod + player.getStartingLife())); + player.setStartingHand(player.getStartingHand() + changeStartCards); } public void setDungeonEffect(EffectData E) { @@ -152,132 +153,78 @@ public class DuelScene extends ForgeScene { AdventurePlayer advPlayer = Current.player(); List players = new ArrayList<>(); - int missingCards= Config.instance().getConfigData().minDeckSize-playerDeck.getMain().countAll(); - if( missingCards > 0 ) //Replace unknown cards for a Wastes. - playerDeck.getMain().add("Wastes",missingCards); - int playerCount=1; - EnemyData currentEnemy=enemy.getData(); - for(int i=0;i<8&¤tEnemy!=null;i++) - { - playerCount++; - currentEnemy=currentEnemy.nextEnemy; - } - - humanPlayer = RegisteredPlayer.forVariants(playerCount, appliedVariants,playerDeck, null, false, null, null); + int missingCards = Config.instance().getConfigData().minDeckSize - playerDeck.getMain().countAll(); + if (missingCards > 0) //Replace unknown cards for a Wastes. + playerDeck.getMain().add("Wastes", missingCards); + humanPlayer = RegisteredPlayer.forVariants(2, appliedVariants, playerDeck, null, false, null, null); LobbyPlayer playerObject = GamePlayerUtil.getGuiPlayer(); FSkin.getAvatars().put(90000, advPlayer.avatar()); playerObject.setAvatarIndex(90000); humanPlayer.setPlayer(playerObject); - humanPlayer.setTeamNumber(0); humanPlayer.setStartingLife(advPlayer.getLife()); + Current.setLatestDeck(enemyDeck); + RegisteredPlayer aiPlayer = RegisteredPlayer.forVariants(2, appliedVariants, Current.latestDeck(), null, false, null, null); + LobbyPlayer enemyPlayer = GamePlayerUtil.createAiPlayer(this.enemy.getData().name, selectAI(this.enemy.getData().ai)); + if (!enemy.nameOverride.isEmpty()) + enemyPlayer.setName(enemy.nameOverride); //Override name if defined in the map. + FSkin.getAvatars().put(90001, this.enemy.getAvatar()); + enemyPlayer.setAvatarIndex(90001); + + aiPlayer.setPlayer(enemyPlayer); + aiPlayer.setStartingLife(Math.round((float) enemy.getData().life * advPlayer.getDifficulty().enemyLifeFactor)); Array playerEffects = new Array<>(); - Array oppEffects = new Array<>(); - - - Map, List>> deckProxyMapMap = null; - DeckProxy deckProxy =null; - if(chaosBattle) - { - deckProxyMapMap = DeckProxy.getAllQuestChallenges(); - List decks = new ArrayList<>(deckProxyMapMap.keySet()); - deckProxy = Aggregates.random(decks); - //playerextras - List playerCards = new ArrayList<>(); - for (String s : deckProxyMapMap.get(deckProxy).getLeft()) { - playerCards.add(QuestUtil.readExtraCard(s)); - } - humanPlayer.addExtraCardsOnBattlefield(playerCards); - } + Array oppEffects = new Array<>(); //Collect and add items effects first. - for(String playerItem:advPlayer.getEquippedItems()) { - ItemData item=ItemData.getItem(playerItem); - if(item != null) { + for (String playerItem : advPlayer.getEquippedItems()) { + ItemData item = ItemData.getItem(playerItem); + if (item != null) { playerEffects.add(item.effect); if (item.effect.opponent != null) oppEffects.add(item.effect.opponent); } else { System.err.printf("Item %s not found.", playerItem); } } + if (enemy.getData().equipment != null) { + for (String oppItem : enemy.getData().equipment) { + ItemData item = ItemData.getItem(oppItem); + oppEffects.add(item.effect); + if (item.effect.opponent != null) playerEffects.add(item.effect.opponent); + } + } //Collect and add player blessings. - if(advPlayer.getBlessing() != null){ + if (advPlayer.getBlessing() != null) { playerEffects.add(advPlayer.getBlessing()); - if(advPlayer.getBlessing().opponent != null) oppEffects.add(advPlayer.getBlessing().opponent); + if (advPlayer.getBlessing().opponent != null) oppEffects.add(advPlayer.getBlessing().opponent); } //Collect and add enemy effects (same as blessings but for individual enemies). - if(enemy.effect != null){ + if (enemy.effect != null) { oppEffects.add(enemy.effect); - if(enemy.effect.opponent != null) + if (enemy.effect.opponent != null) playerEffects.add(enemy.effect.opponent); } //Collect and add dungeon-wide effects. - if(dungeonEffect != null) { + if (dungeonEffect != null) { oppEffects.add(dungeonEffect); - if(dungeonEffect.opponent != null) + if (dungeonEffect.opponent != null) playerEffects.add(dungeonEffect.opponent); } - addEffects(humanPlayer,playerEffects); + addEffects(humanPlayer, playerEffects); + addEffects(aiPlayer, oppEffects); - currentEnemy=enemy.getData(); - for(int i=0;i<8&¤tEnemy!=null;i++) - { - Deck deck=null; - - if (this.chaosBattle) { //random challenge for chaos mode - //aiextras - List aiCards = new ArrayList<>(); - for (String s : deckProxyMapMap.get(deck).getRight()) { - aiCards.add(QuestUtil.readExtraCard(s)); - } - this.AIExtras = aiCards; - deck = deckProxy.getDeck(); - } else { - deck=currentEnemy.copyPlayerDeck ? this.playerDeck : currentEnemy.generateDeck(Current.player().isFantasyMode(), Current.player().getDifficulty().name.equalsIgnoreCase("Hard")); - } - RegisteredPlayer aiPlayer = RegisteredPlayer.forVariants(playerCount, appliedVariants, deck, null, false, null, null); - - LobbyPlayer enemyPlayer = GamePlayerUtil.createAiPlayer(currentEnemy.name, selectAI(currentEnemy.ai)); - if(!enemy.nameOverride.isEmpty()) enemyPlayer.setName(enemy.nameOverride); //Override name if defined in the map.(only supported for 1 enemy atm) - FSkin.getAvatars().put(90001+i, enemy.getAvatar(i)); - enemyPlayer.setAvatarIndex(90001+i); - aiPlayer.setPlayer(enemyPlayer); - aiPlayer.setTeamNumber(currentEnemy.teamNumber); - aiPlayer.setStartingLife(Math.round((float)currentEnemy.life*advPlayer.getDifficulty().enemyLifeFactor)); - - Array equipmentEffects = new Array<>(); - if(currentEnemy.equipment!=null) { - for(String oppItem:currentEnemy.equipment) { - ItemData item=ItemData.getItem(oppItem); - equipmentEffects.add(item.effect); - if(item.effect.opponent !=null) playerEffects.add(item.effect.opponent); - } - } - addEffects(aiPlayer,oppEffects); - addEffects(aiPlayer,equipmentEffects); - - - //add extra cards for challenger mode - if (chaosBattle) { - aiPlayer.addExtraCardsOnBattlefield(AIExtras); - } - - players.add(aiPlayer); - - - - Current.setLatestDeck(deck); - - currentEnemy=currentEnemy.nextEnemy; + //add extra cards for challenger mode + if (chaosBattle) { + humanPlayer.addExtraCardsOnBattlefield(playerExtras); + aiPlayer.addExtraCardsOnBattlefield(AIExtras); } - - players.add(humanPlayer); + players.add(aiPlayer); final Map guiMap = new HashMap<>(); guiMap.put(humanPlayer, MatchController.instance); @@ -291,35 +238,29 @@ public class DuelScene extends ForgeScene { rules.setGamesPerMatch(1); rules.setManaBurn(false); - hostedMatch.setEndGameHook(new Runnable() { - @Override - public void run() { - DuelScene.this.GameEnd(); - } - }); + hostedMatch.setEndGameHook(() -> DuelScene.this.GameEnd()); hostedMatch.startMatch(rules, appliedVariants, players, guiMap); MatchController.instance.setGameView(hostedMatch.getGameView()); if (chaosBattle) { - List list = Lists.newArrayList("It all depends on your skill!","It's showtime!","Let's party!", - "You've proved yourself!","Are you ready? Go!","Prepare to strike, now!","Let's go!","What's next?", - "Yeah, I've been waitin' for this!","The stage of battle is set!","And the battle begins!","Let's get started!", - "Are you ready?","It's the battle of the century!","Let's keep it up!","How long will this go on?","I hope you're ready!", - "This could be the end.","Pull out all the stops!","It all comes down to this.","Who will emerge victorious?", - "Nowhere to run, nowhere to hide!","This battle is over!","There was no way out of that one!","Let's do this!","Let the madness begin!", - "It's all or nothing!","It's all on the line!","You can't back down now!","Do you have what it takes?","What will happen next?", - "Don't blink!","You can't lose here!","There's no turning back!","It's all or nothing now!"); + List list = Lists.newArrayList("It all depends on your skill!", "It's showtime!", "Let's party!", + "You've proved yourself!", "Are you ready? Go!", "Prepare to strike, now!", "Let's go!", "What's next?", + "Yeah, I've been waitin' for this!", "The stage of battle is set!", "And the battle begins!", "Let's get started!", + "Are you ready?", "It's the battle of the century!", "Let's keep it up!", "How long will this go on?", "I hope you're ready!", + "This could be the end.", "Pull out all the stops!", "It all comes down to this.", "Who will emerge victorious?", + "Nowhere to run, nowhere to hide!", "This battle is over!", "There was no way out of that one!", "Let's do this!", "Let the madness begin!", + "It's all or nothing!", "It's all on the line!", "You can't back down now!", "Do you have what it takes?", "What will happen next?", + "Don't blink!", "You can't lose here!", "There's no turning back!", "It's all or nothing now!"); String message = Aggregates.random(list); - FThreads.delayInEDT(600, () -> FThreads.invokeInEdtNowOrLater(() -> FOptionPane.showMessageDialog(message, enemy.getName(), new FBufferedImage(120, 120) { + FThreads.delayInEDT(600, () -> FThreads.invokeInEdtNowOrLater(() -> FOptionPane.showMessageDialog(message, aiPlayer.getPlayer().getName(), new FBufferedImage(120, 120) { @Override protected void draw(Graphics g, float w, float h) { - if (FSkin.getAvatars().get(90000) != null) - g.drawImage(FSkin.getAvatars().get(90000), 0, 0, w, h); + if (FSkin.getAvatars().get(90001) != null) + g.drawImage(FSkin.getAvatars().get(90001), 0, 0, w, h); } }))); } - for (final Player p : hostedMatch.getGame().getPlayers()) { if (p.getController() instanceof PlayerControllerHuman) { final PlayerControllerHuman humanController = (PlayerControllerHuman) p.getController(); @@ -339,28 +280,58 @@ public class DuelScene extends ForgeScene { public void initDuels(PlayerSprite playerSprite, EnemySprite enemySprite) { this.player = playerSprite; this.enemy = enemySprite; - this.playerDeck = (Deck)AdventurePlayer.current().getSelectedDeck().copyTo("PlayerDeckCopy"); + this.playerDeck = (Deck) AdventurePlayer.current().getSelectedDeck().copyTo("PlayerDeckCopy"); this.chaosBattle = this.enemy.getData().copyPlayerDeck && Current.player().isFantasyMode(); + if (this.chaosBattle) { //random challenge for chaos mode + Map, List>> deckProxyMapMap = DeckProxy.getAllQuestChallenges(); + List decks = new ArrayList<>(deckProxyMapMap.keySet()); + DeckProxy deck = Aggregates.random(decks); + //playerextras + List playerCards = new ArrayList<>(); + for (String s : deckProxyMapMap.get(deck).getLeft()) { + playerCards.add(QuestUtil.readExtraCard(s)); + } + this.playerExtras = playerCards; + //aiextras + List aiCards = new ArrayList<>(); + for (String s : deckProxyMapMap.get(deck).getRight()) { + aiCards.add(QuestUtil.readExtraCard(s)); + } + this.AIExtras = aiCards; + this.enemyDeck = deck.getDeck(); + } else { + this.AIExtras.clear(); + this.playerExtras.clear(); + this.enemyDeck = this.enemy.getData().copyPlayerDeck ? this.playerDeck : this.enemy.getData().generateDeck(Current.player().isFantasyMode(), Current.player().getDifficulty().name.equalsIgnoreCase("Hard")); + } + } + //was planning to get player and enemy deck outside the duelscene while loading to display VS screen in the future with deck colors displayed... + public Deck getPlayerDeck() { + return this.playerDeck; + } - this.AIExtras.clear(); - this.playerExtras.clear(); - + public Deck getEnemyDeck() { + return this.enemyDeck; } private String selectAI(String ai) { //Decide opponent AI. String AI = ""; //Use user settings if it's null. - if (ai != null){ + if (ai != null) { switch (ai.toLowerCase()) { //We use this way to ensure capitalization is exact. //We don't want misspellings here. case "default": - AI = "Default"; break; + AI = "Default"; + break; case "reckless": - AI = "Reckless"; break; + AI = "Reckless"; + break; case "cautious": - AI = "Cautious"; break; + AI = "Cautious"; + break; case "experimental": - AI = "Experimental"; break; + AI = "Experimental"; + break; default: AI = ""; //User settings. } From 481d00bfc59e9a3aaf920a104eb382d7534624da Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Thu, 11 Aug 2022 18:37:16 +0800 Subject: [PATCH 20/21] unused import --- forge-gui-mobile/src/forge/adventure/scene/DuelScene.java | 1 - 1 file changed, 1 deletion(-) diff --git a/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java b/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java index 658afc68926..e2b591e4d05 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java @@ -9,7 +9,6 @@ import forge.LobbyPlayer; import forge.adventure.character.EnemySprite; import forge.adventure.character.PlayerSprite; import forge.adventure.data.EffectData; -import forge.adventure.data.EnemyData; import forge.adventure.data.ItemData; import forge.adventure.player.AdventurePlayer; import forge.adventure.util.Config; From 1d8dd61f262da85cc5ec1cb7ced10751476a8573 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Thu, 11 Aug 2022 18:51:06 +0800 Subject: [PATCH 21/21] reverse check - prevent NPE --- .../forge/game/ability/effects/DigUntilEffect.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java index ab0db62815d..35a846ef8f2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java @@ -42,7 +42,7 @@ public class DigUntilEffect extends SpellAbilityEffect { } final ZoneType revealed = ZoneType.smartValueOf(sa.getParam("RevealedDestination")); - sb.append(revealed.equals(ZoneType.Exile) ? "exiles cards from their library until they exile " : + sb.append(ZoneType.Exile.equals(revealed) ? "exiles cards from their library until they exile " : "reveals cards from their library until revealing "); sb.append(Lang.nounWithNumeralExceptOne(untilAmount, desc + " card")); if (untilAmount != 1) { @@ -62,18 +62,18 @@ public class DigUntilEffect extends SpellAbilityEffect { sb.append(untilAmount > 1 ? "those cards" : "that card"); sb.append(" "); - if (found.equals(ZoneType.Hand)) { + if (ZoneType.Hand.equals(found)) { sb.append("into their hand "); } - if (revealed.equals(ZoneType.Graveyard)) { + if (ZoneType.Graveyard.equals(revealed)) { sb.append("and all other cards into their graveyard."); } - if (revealed.equals(ZoneType.Exile)) { + if (ZoneType.Exile.equals(revealed)) { sb.append("and exile all other cards revealed this way."); } } else if (revealed != null) { - if (revealed.equals(ZoneType.Hand)) { + if (ZoneType.Hand.equals(revealed)) { sb.append("all cards revealed this way into their hand"); } }