From 20491e11367e5ba30dae0a3a261f804e14de4ace Mon Sep 17 00:00:00 2001 From: Lyu Zong-Hong Date: Sun, 9 May 2021 20:17:26 +0900 Subject: [PATCH] Refactor fog and protection to use replacement effect --- .../main/java/forge/ai/ability/EffectAi.java | 2 +- .../src/main/java/forge/ai/ability/FogAi.java | 2 +- .../java/forge/ai/ability/PumpAiBase.java | 2 +- .../main/java/forge/ai/ability/PumpAllAi.java | 2 +- .../src/main/java/forge/game/GameEntity.java | 3 - .../forge/game/ability/effects/FogEffect.java | 32 ++++++++++- .../src/main/java/forge/game/card/Card.java | 2 +- .../java/forge/game/card/CardFactoryUtil.java | 57 +++++++++++++++++++ .../java/forge/game/phase/PhaseHandler.java | 10 ---- .../main/java/forge/game/player/Player.java | 4 +- .../forge/game/player/PlayerFactoryUtil.java | 55 ++++++++++++++++++ .../game/replacement/ReplacementHandler.java | 24 ++++++++ 12 files changed, 173 insertions(+), 22 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java index 8614c4eb2d2..e643da47300 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java @@ -79,7 +79,7 @@ public class EffectAi extends SpellAbilityAi { if (!game.getStack().isEmpty()) { return false; } - if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) { + if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) { return false; } if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) { diff --git a/forge-ai/src/main/java/forge/ai/ability/FogAi.java b/forge-ai/src/main/java/forge/ai/ability/FogAi.java index 6dc63330369..2fe66864e3f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/FogAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/FogAi.java @@ -29,7 +29,7 @@ public class FogAi extends SpellAbilityAi { final Card hostCard = sa.getHostCard(); // Don't cast it, if the effect is already in place - if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) { + if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java index 0fc65c64997..e2728f4cb70 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java @@ -480,7 +480,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { } }); // leaves all creatures that will be destroyed } // -X/-X end - else if (attack < 0 && !game.getPhaseHandler().isPreventCombatDamageThisTurn()) { + else if (attack < 0 && !game.getReplacementHandler().isPreventCombatDamageThisTurn()) { // spells that give -X/0 boolean isMyTurn = game.getPhaseHandler().isPlayerTurn(ai); if (isMyTurn) { diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java index 386d043770d..d6fc76bad15 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java @@ -113,7 +113,7 @@ public class PumpAllAi extends PumpAiBase { if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) || phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer()) - || game.getPhaseHandler().isPreventCombatDamageThisTurn()) { + || game.getReplacementHandler().isPreventCombatDamageThisTurn()) { return false; } int totalPower = 0; diff --git a/forge-game/src/main/java/forge/game/GameEntity.java b/forge-game/src/main/java/forge/game/GameEntity.java index df87537339f..ea04d363432 100644 --- a/forge-game/src/main/java/forge/game/GameEntity.java +++ b/forge-game/src/main/java/forge/game/GameEntity.java @@ -187,9 +187,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable { restDamage = 0; } - // then apply static Damage Prevention effects - restDamage = staticDamagePrevention(restDamage, source, isCombat, false); - // if damage is greater than restDamage, damage was prevented if (damage > restDamage) { int prevent = damage - restDamage; diff --git a/forge-game/src/main/java/forge/game/ability/effects/FogEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FogEffect.java index f6d0b775e5d..a9b47f5140b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/FogEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/FogEffect.java @@ -1,7 +1,14 @@ package forge.game.ability.effects; +import forge.GameCommand; +import forge.game.Game; import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementHandler; import forge.game.spellability.SpellAbility; +import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; public class FogEffect extends SpellAbilityEffect { @@ -13,7 +20,28 @@ public class FogEffect extends SpellAbilityEffect { @Override public void resolve(SpellAbility sa) { - // Expand Fog keyword here depending on what we need out of it. - sa.getActivatingPlayer().getGame().getPhaseHandler().setPreventCombatDamageThisTurn(); + final Card hostCard = sa.getHostCard(); + final Game game = hostCard.getGame(); + final String name = hostCard.getName() + "'s Effect"; + final String image = hostCard.getImageKey(); + StringBuilder sb = new StringBuilder("Event$ DamageDone | ActiveZones$ Command | IsCombat$ True"); + sb.append(" | Prevent$ True | Description$ Prevent all combat damage this turn."); + String repeffstr = sb.toString(); + + final Card eff = createEffect(sa, hostCard.getController(), name, image); + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true); + eff.addReplacementEffect(re); + eff.updateStateForView(); + + game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); + game.getAction().moveTo(ZoneType.Command, eff, sa); + game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + + game.getEndOfTurn().addUntil(new GameCommand() { + @Override + public void run() { + game.getAction().exile(eff, null); + } + }); } } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 18a2a974032..a61eb9a6e90 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -5212,7 +5212,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return damageIn; } - if (isCombat && getGame().getPhaseHandler().isPreventCombatDamageThisTurn()) { + if (isCombat && getGame().getReplacementHandler().isPreventCombatDamageThisTurn()) { return 0; } diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 36a6e7d845f..f3415b96429 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -2228,6 +2228,59 @@ public class CardFactoryUtil { return re; } + // Create damage prevention replacement effect for protection keyword + private static ReplacementEffect createProtectionReplacement(final CardState card, final String kw, final boolean intrinsic) { + Card host = card.getCard(); + String validSource = "Card."; + + if (kw.startsWith("Protection:")) { + final String[] kws = kw.split(":"); + String characteristic = kws[1]; + if (characteristic.startsWith("Player")) { + validSource += "ControlledBy " + characteristic; + } else { + if (characteristic.endsWith("White") || characteristic.endsWith("Blue") + || characteristic.endsWith("Black") || characteristic.endsWith("Red") + || characteristic.endsWith("Green") || characteristic.endsWith("Colorless") + || characteristic.endsWith("MonoColor") || characteristic.endsWith("MultiColor")) { + characteristic += "Source"; + } + validSource = characteristic; + } + } else if (kw.startsWith("Protection from ")) { + String protectType = kw.substring("Protection from ".length()); + if (protectType.equals("white")) { + validSource += "WhiteSource"; + } else if (protectType.equals("blue")) { + validSource += "BlueSource"; + } else if (protectType.equals("black")) { + validSource += "BlackSource"; + } else if (protectType.equals("red")) { + validSource += "RedSource"; + } else if (protectType.equals("green")) { + validSource += "GreenSource"; + } else if (protectType.equals("all colors")) { + validSource += "nonColorless"; + } else if (protectType.equals("everything")) { + validSource = ""; + } else if (protectType.startsWith("opponent of ")) { + final String playerName = protectType.substring("opponent of ".length()); + validSource += "ControlledBy Player.OpponentOf PlayerNamed_" + playerName; + } else { + validSource = CardType.getSingularType(protectType); + } + } + + String rep = "Event$ DamageDone | Prevent$ True | ActiveZones$ Battlefield | ValidTarget$ Card.Self"; + if (!validSource.isEmpty()) { + rep += " | ValidSource$ " + validSource; + } + rep += " | Secondary$ True | TiedToKeyword$ " + kw + " | Description$ " + kw; + + ReplacementEffect re = ReplacementHandler.parseReplacement(rep, host, intrinsic, card); + return re; + } + public static ReplacementEffect makeEtbCounter(final String kw, final CardState card, final boolean intrinsic) { String parse = kw; @@ -3915,6 +3968,10 @@ public class CardFactoryUtil { inst.addReplacement(re); } } + else if (keyword.startsWith("Protection")) { + ReplacementEffect re = createProtectionReplacement(card, keyword, intrinsic); + inst.addReplacement(re); + } else if (keyword.startsWith("If CARDNAME would be put into a graveyard " + "from anywhere, reveal CARDNAME and shuffle it into its owner's library instead.")) { 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 a8ddf9a1ea7..0048b35ecd7 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -92,7 +92,6 @@ public class PhaseHandler implements java.io.Serializable { private int nUpkeepsThisGame = 0; private int nCombatsThisTurn = 0; private int nMain2sThisTurn = 0; - private boolean bPreventCombatDamageThisTurn = false; private int planarDiceRolledthisTurn = 0; private transient Player playerTurn = null; @@ -520,7 +519,6 @@ public class PhaseHandler implements java.io.Serializable { break; case CLEANUP: - bPreventCombatDamageThisTurn = false; if (!bRepeatCleanup) { // only call onCleanupPhase when Cleanup is not repeated game.onCleanupPhase(); @@ -834,10 +832,6 @@ public class PhaseHandler implements java.io.Serializable { return noCost || blocker.getController().getController().payManaOptional(blocker, blockCost, fakeSA, "Pay cost to declare " + blocker + " a blocker. ", ManaPaymentPurpose.DeclareBlocker); } - public final boolean isPreventCombatDamageThisTurn() { - return bPreventCombatDamageThisTurn; - } - private Player handleNextTurn() { game.getStack().onNextTurn(); // reset mustAttackEntity @@ -1222,10 +1216,6 @@ public class PhaseHandler implements java.io.Serializable { onPhaseBegin(); } - public final void setPreventCombatDamageThisTurn() { - bPreventCombatDamageThisTurn = true; - } - public int getPlanarDiceRolledthisTurn() { return planarDiceRolledthisTurn; } diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index a838ab70dbe..b4991d00f0c 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -719,7 +719,7 @@ public class Player extends GameEntity implements Comparable { return damage; } - if (isCombat && game.getPhaseHandler().isPreventCombatDamageThisTurn()) { + if (isCombat && game.getReplacementHandler().isPreventCombatDamageThisTurn()) { return 0; } @@ -1195,7 +1195,7 @@ public class Player extends GameEntity implements Comparable { public boolean hasProtectionFromDamage(final Card source) { - return hasProtectionFrom(source, false, false); + return hasProtectionFrom(source, false, true); } @Override diff --git a/forge-game/src/main/java/forge/game/player/PlayerFactoryUtil.java b/forge-game/src/main/java/forge/game/player/PlayerFactoryUtil.java index 4eb3eeac0c7..07e7eca19aa 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/player/PlayerFactoryUtil.java @@ -1,7 +1,10 @@ package forge.game.player; +import forge.card.CardType; import forge.game.card.Card; import forge.game.keyword.KeywordInterface; +import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementHandler; import forge.game.staticability.StaticAbility; public class PlayerFactoryUtil { @@ -39,6 +42,58 @@ public class PlayerFactoryUtil { } public static void addReplacementEffect(final KeywordInterface inst, Player player) { + String keyword = inst.getOriginal(); + String effect = null; + + if (keyword.startsWith("Protection")) { + String validSource = "Card."; + if (keyword.startsWith("Protection:")) { + final String[] kws = keyword.split(":"); + String characteristic = kws[1]; + if (characteristic.startsWith("Player")) { + validSource += "ControlledBy " + characteristic; + } else { + if (characteristic.endsWith("White") || characteristic.endsWith("Blue") + || characteristic.endsWith("Black") || characteristic.endsWith("Red") + || characteristic.endsWith("Green") || characteristic.endsWith("Colorless") + || characteristic.endsWith("MonoColor") || characteristic.endsWith("MultiColor")) { + characteristic += "Source"; + } + validSource = characteristic; + } + } else if (keyword.startsWith("Protection from ")) { + String protectType = keyword.substring("Protection from ".length()); + if (protectType.equals("white")) { + validSource += "WhiteSource"; + } else if (protectType.equals("blue")) { + validSource += "BlueSource"; + } else if (protectType.equals("black")) { + validSource += "BlackSource"; + } else if (protectType.equals("red")) { + validSource += "RedSource"; + } else if (protectType.equals("green")) { + validSource += "GreenSource"; + } else if (protectType.equals("all colors")) { + validSource += "nonColorless"; + } else if (protectType.equals("everything")) { + validSource = ""; + } else { + validSource = CardType.getSingularType(protectType); + } + } + + effect = "Event$ DamageDone | Prevent$ True | ActiveZones$ Command | ValidTarget$ You"; + if (!validSource.isEmpty()) { + effect += " | ValidSource$ " + validSource; + } + effect += " | Secondary$ True | Description$ " + keyword; + } + + if (effect != null) { + final Card card = player.getKeywordCard(); + ReplacementEffect re = ReplacementHandler.parseReplacement(effect, card, false, card.getCurrentState()); + inst.addReplacement(re); + } } public static void addSpellAbility(final KeywordInterface inst, Player player) { 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 fb728d47cdd..1e7f83677d8 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -482,4 +482,28 @@ public class ReplacementHandler { } return totalAmount; } + + /** + * Helper function to check if combat damage is prevented this turn (fog effect) + * @return true if there is some resolved fog effect + */ + public final boolean isPreventCombatDamageThisTurn() { + final List list = Lists.newArrayList(); + game.forEachCardInGame(new Visitor() { + @Override + public boolean visit(Card c) { + for (final ReplacementEffect re : c.getReplacementEffects()) { + if (re.getMode() == ReplacementType.DamageDone + && re.getLayer() == ReplacementLayer.Other + && re.hasParam("Prevent") && re.getParam("Prevent").equals("True") + && re.hasParam("IsCombat") && re.getParam("IsCombat").equals("True") + && re.zonesCheck(game.getZoneOf(c))) { + list.add(re); + } + } + return true; + } + }); + return !list.isEmpty(); + } }