From c5ad28a65e2bd759ec741f85c86552e3e592c040 Mon Sep 17 00:00:00 2001 From: Alumi Date: Sun, 14 Mar 2021 04:53:55 +0000 Subject: [PATCH] Re-implement skip turn using replacement effect --- .../main/java/forge/ai/ability/UntapAi.java | 2 + .../java/forge/game/ability/AbilityKey.java | 1 + .../game/ability/effects/SkipTurnEffect.java | 37 +++++++++++- .../java/forge/game/phase/PhaseHandler.java | 56 +++++-------------- .../game/replacement/ReplaceBeginTurn.java | 34 +++++++++++ .../game/replacement/ReplacementHandler.java | 6 ++ .../game/replacement/ReplacementResult.java | 3 +- .../game/replacement/ReplacementType.java | 1 + forge-gui/res/cardsfolder/s/stranglehold.txt | 4 +- forge-gui/res/cardsfolder/t/time_vault.txt | 6 +- forge-gui/res/cardsfolder/u/ugins_nexus.txt | 3 +- 11 files changed, 101 insertions(+), 52 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/replacement/ReplaceBeginTurn.java diff --git a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java index ecb45dd2ec8..2e557996182 100644 --- a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java @@ -71,6 +71,8 @@ public class UntapAi extends SpellAbilityAi { if (!sa.usesTargeting()) { if (mandatory) { return true; + } else if ("Never".equals(sa.getParam("AILogic"))) { + return false; } // TODO: use Defined to determine, if this is an unfavorable result diff --git a/forge-game/src/main/java/forge/game/ability/AbilityKey.java b/forge-game/src/main/java/forge/game/ability/AbilityKey.java index b0f526793b0..64ebf3d1fb9 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityKey.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityKey.java @@ -61,6 +61,7 @@ public enum AbilityKey { EffectOnly("EffectOnly"), Exploited("Exploited"), Explorer("Explorer"), + ExtraTurn("ExtraTurn"), Event("Event"), Fighter("Fighter"), Fighters("Fighters"), diff --git a/forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java index 9c100db4b34..f8c854c0993 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java @@ -1,9 +1,19 @@ package forge.game.ability.effects; +import forge.game.Game; +import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; import forge.game.player.Player; +import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementHandler; +import forge.game.replacement.ReplacementLayer; +import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; +import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; +import forge.util.Lang; import java.util.List; @@ -25,12 +35,33 @@ public class SkipTurnEffect extends SpellAbilityEffect { @Override public void resolve(SpellAbility sa) { + final Card hostCard = sa.getHostCard(); + final Game game = hostCard.getGame(); + final String name = hostCard.getName() + "'s Effect"; + final String image = hostCard.getImageKey(); final int numTurns = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumTurns"), sa); + String repeffstr = "Event$ BeginTurn | ActiveZones$ Command | ValidPlayer$ You " + + "| Description$ Skip your next " + (numTurns > 1 ? Lang.getNumeral(numTurns) + " turns." : "turn."); + String effect = "DB$ StoreSVar | SVar$ NumTurns | Type$ CountSVar | Expression$ NumTurns/Minus.1"; + String exile = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile " + + "| ConditionCheckSVar$ NumTurns | ConditionSVarCompare$ EQ0"; + List tgtPlayers = getTargetPlayers(sa); for (final Player player : tgtPlayers) { - for(int i = 0; i < numTurns; i++) { - player.addKeyword("Skip your next turn."); - } + final Card eff = createEffect(sa, player, name, image); + eff.setSVar("NumTurns", "Number$" + numTurns); + SpellAbility calcTurn = AbilityFactory.getAbility(effect, eff); + calcTurn.setSubAbility((AbilitySub) AbilityFactory.getAbility(exile, eff)); + + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true); + re.setLayer(ReplacementLayer.Other); + re.setOverridingAbility(calcTurn); + eff.addReplacementEffect(re); + eff.updateStateForView(); + + game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); + game.getAction().moveTo(ZoneType.Command, eff, sa); + game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); } } } diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index 4cfb6dfd91a..7f3c7e8bc5b 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -36,8 +36,9 @@ import forge.game.cost.Cost; import forge.game.event.*; import forge.game.keyword.Keyword; import forge.game.player.Player; -import forge.game.player.PlayerController.BinaryChoiceType; import forge.game.player.PlayerController.ManaPaymentPurpose; +import forge.game.replacement.ReplacementResult; +import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; import forge.game.spellability.LandAbility; import forge.game.staticability.StaticAbility; @@ -837,55 +838,28 @@ public class PhaseHandler implements java.io.Serializable { private Player getNextActivePlayer() { ExtraTurn extraTurn = !extraTurns.isEmpty() ? extraTurns.pop() : null; Player nextPlayer = extraTurn != null ? extraTurn.getPlayer() : game.getNextPlayerAfter(playerTurn); + // The bottom of the extra turn stack is the normal turn + boolean isExtraTurn = !extraTurns.isEmpty(); - // update ExtraTurn Count for all players - for (final Player p : game.getPlayers()) { - p.setExtraTurnCount(getExtraTurnForPlayer(p)); - } + // update ExtraTurn Count + nextPlayer.setExtraTurnCount(getExtraTurnForPlayer(nextPlayer)); - if (extraTurn != null) { - // The bottom of the extra turn stack is the normal turn - nextPlayer.setExtraTurn(!extraTurns.isEmpty()); - if (nextPlayer.hasKeyword("If you would begin an extra turn, skip that turn instead.")) { - return getNextActivePlayer(); - } - for (Trigger deltrig : extraTurn.getDelayedTriggers()) { - game.getTriggerHandler().registerThisTurnDelayedTrigger(deltrig); - } - } - else { - nextPlayer.setExtraTurn(false); - } - - if (nextPlayer.hasKeyword("Skip your next turn.")) { - nextPlayer.removeKeyword("Skip your next turn.", false); + // Replacement effects + final Map repRunParams = AbilityKey.mapFromAffected(nextPlayer); + repRunParams.put(AbilityKey.ExtraTurn, isExtraTurn); + ReplacementResult repres = game.getReplacementHandler().run(ReplacementType.BeginTurn, repRunParams); + if (repres != ReplacementResult.NotReplaced) { if (extraTurn == null) { setPlayerTurn(nextPlayer); } return getNextActivePlayer(); } - // TODO: This shouldn't filter by Time Vault, just in case Time Vault doesn't have it's normal ability. - CardCollection vaults = CardLists.filter(nextPlayer.getCardsIn(ZoneType.Battlefield, "Time Vault"), Presets.TAPPED); - if (!vaults.isEmpty()) { - Card crd = vaults.getFirst(); - SpellAbility fakeSA = new SpellAbility.EmptySa(crd, nextPlayer); - boolean untapTimeVault = nextPlayer.getController().chooseBinary(fakeSA, "Skip a turn to untap a Time Vault?", BinaryChoiceType.UntapTimeVault, false); - if (untapTimeVault) { - if (vaults.size() > 1) { - Card c = nextPlayer.getController().chooseSingleEntityForEffect(vaults, fakeSA, "Which Time Vault do you want to Untap?", null); - if (c != null) - crd = c; - } - crd.untap(); - if (extraTurn == null) { - setPlayerTurn(nextPlayer); - } - return getNextActivePlayer(); - } - } - + nextPlayer.setExtraTurn(isExtraTurn); if (extraTurn != null) { + for (Trigger deltrig : extraTurn.getDelayedTriggers()) { + game.getTriggerHandler().registerThisTurnDelayedTrigger(deltrig); + } if (extraTurn.isSkipUntap()) { nextPlayer.addKeyword("Skip the untap step of this turn."); } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceBeginTurn.java b/forge-game/src/main/java/forge/game/replacement/ReplaceBeginTurn.java new file mode 100644 index 00000000000..ce01a8e1453 --- /dev/null +++ b/forge-game/src/main/java/forge/game/replacement/ReplaceBeginTurn.java @@ -0,0 +1,34 @@ +package forge.game.replacement; + +import forge.game.ability.AbilityKey; +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +import java.util.Map; + +public class ReplaceBeginTurn extends ReplacementEffect { + + public ReplaceBeginTurn(final Map mapParams, final Card host, final boolean intrinsic) { + super(mapParams, host, intrinsic); + } + + @Override + public boolean canReplace(Map runParams) { + if (hasParam("ValidPlayer")) { + if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidPlayer").split(","), getHostCard())) { + return false; + } + } + if (hasParam("ExtraTurn")) { + if (!(boolean) runParams.get(AbilityKey.ExtraTurn)) { + return false; + } + } + return true; + } + + @Override + public void setReplacingObjects(Map runParams, SpellAbility sa) { + sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected)); + } +} diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java index d23d1f933dc..a8f8f7c06ec 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -349,6 +349,12 @@ public class ReplacementHandler { } } + if (mapParams.containsKey("Skip")) { + if (mapParams.get("Skip").equals("True")) { + return ReplacementResult.Skipped; // Event is skipped. + } + } + Player player = host.getController(); player.getController().playSpellAbilityNoStack(effectSA, true); diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementResult.java b/forge-game/src/main/java/forge/game/replacement/ReplacementResult.java index a60f85d12ce..86c771dda91 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementResult.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementResult.java @@ -8,5 +8,6 @@ public enum ReplacementResult { Replaced, NotReplaced, Prevented, - Updated + Updated, + Skipped } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementType.java b/forge-game/src/main/java/forge/game/replacement/ReplacementType.java index f218fb5e1f9..43c603fc5b4 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementType.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementType.java @@ -14,6 +14,7 @@ public enum ReplacementType { AddCounter(ReplaceAddCounter.class), AssignDealDamage(ReplaceAssignDealDamage.class), Attached(ReplaceAttached.class), + BeginTurn(ReplaceBeginTurn.class), Counter(ReplaceCounter.class), CopySpell(ReplaceCopySpell.class), CreateToken(ReplaceToken.class), diff --git a/forge-gui/res/cardsfolder/s/stranglehold.txt b/forge-gui/res/cardsfolder/s/stranglehold.txt index 78db97549dd..e40280c030d 100644 --- a/forge-gui/res/cardsfolder/s/stranglehold.txt +++ b/forge-gui/res/cardsfolder/s/stranglehold.txt @@ -2,7 +2,7 @@ Name:Stranglehold ManaCost:3 R Types:Enchantment S:Mode$ Continuous | Affected$ Opponent | AddKeyword$ CantSearchLibrary | Description$ Your opponents can't search libraries. -S:Mode$ Continuous | Affected$ Opponent | AddKeyword$ If you would begin an extra turn, skip that turn instead. | Description$ If an opponent would begin an extra turn, that player skips that turn instead. +R:Event$ BeginTurn | ActiveZones$ Battlefield | ValidPlayer$ Opponent | ExtraTurn$ True | Skip$ True | Description$ If an opponent would begin an extra turn, that player skips that turn instead. +SVar:NonStackingEffect:True AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/stranglehold.jpg Oracle:Your opponents can't search libraries.\nIf an opponent would begin an extra turn, that player skips that turn instead. diff --git a/forge-gui/res/cardsfolder/t/time_vault.txt b/forge-gui/res/cardsfolder/t/time_vault.txt index 30e898d2bfc..2a427e1289f 100644 --- a/forge-gui/res/cardsfolder/t/time_vault.txt +++ b/forge-gui/res/cardsfolder/t/time_vault.txt @@ -1,11 +1,11 @@ Name:Time Vault ManaCost:2 Types:Artifact -Text:If you would begin your turn while CARDNAME is tapped, you may skip that turn instead. If you do, untap CARDNAME. -K:CARDNAME doesn't untap during your untap step. K:CARDNAME enters the battlefield tapped. +K:CARDNAME doesn't untap during your untap step. +R:Event$ BeginTurn | ActiveZones$ Battlefield | ValidPlayer$ You | IsPresent$ Card.Self+tapped | Optional$ True | ReplaceWith$ DBUntap | Description$ If you would begin your turn while CARDNAME is tapped, you may skip that turn instead. If you do, untap CARDNAME. +SVar:DBUntap:DB$ Untap | Defined$ Self | AILogic$ Never A:AB$ AddTurn | Cost$ T | NumTurns$ 1 | SpellDescription$ Take an extra turn after this one. SVar:PlayMain1:ALWAYS AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/time_vault.jpg Oracle:Time Vault enters the battlefield tapped.\nTime Vault doesn't untap during your untap step.\nIf you would begin your turn while Time Vault is tapped, you may skip that turn instead. If you do, untap Time Vault.\n{T}: Take an extra turn after this one. diff --git a/forge-gui/res/cardsfolder/u/ugins_nexus.txt b/forge-gui/res/cardsfolder/u/ugins_nexus.txt index 95f442e2327..c074d831794 100644 --- a/forge-gui/res/cardsfolder/u/ugins_nexus.txt +++ b/forge-gui/res/cardsfolder/u/ugins_nexus.txt @@ -1,11 +1,10 @@ Name:Ugin's Nexus ManaCost:5 Types:Legendary Artifact -S:Mode$ Continuous | Affected$ Player | AddKeyword$ If you would begin an extra turn, skip that turn instead. | Description$ If a player would begin an extra turn, that player skips that turn instead. +R:Event$ BeginTurn | ActiveZones$ Battlefield | ExtraTurn$ True | Skip$ True | Description$ If a player would begin an extra turn, that player skips that turn instead. R:Event$ Moved | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | ReplaceWith$ DBExile | Description$ If CARDNAME would be put into a graveyard from the battlefield, instead exile it and take an extra turn after this one. SVar:DBExile:DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBAddTurn SVar:DBAddTurn:DB$ AddTurn | Defined$ ReplacedCardLKIController | NumTurns$ 1 AI:RemoveDeck:Random SVar:SacMe:5 -SVar:Picture:http://www.wizards.com/global/images/magic/general/ugins_nexus.jpg Oracle:If a player would begin an extra turn, that player skips that turn instead.\nIf Ugin's Nexus would be put into a graveyard from the battlefield, instead exile it and take an extra turn after this one.