Merge branch 'refactor_fog_protection' into 'master'

Refactor Damage Prevention - Step 3

See merge request core-developers/forge!4680
This commit is contained in:
Michael Kamensky
2021-05-09 18:15:44 +00:00
12 changed files with 173 additions and 22 deletions

View File

@@ -79,7 +79,7 @@ public class EffectAi extends SpellAbilityAi {
if (!game.getStack().isEmpty()) { if (!game.getStack().isEmpty()) {
return false; return false;
} }
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) { if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
return false; return false;
} }
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) { if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {

View File

@@ -29,7 +29,7 @@ public class FogAi extends SpellAbilityAi {
final Card hostCard = sa.getHostCard(); final Card hostCard = sa.getHostCard();
// Don't cast it, if the effect is already in place // Don't cast it, if the effect is already in place
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) { if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
return false; return false;
} }

View File

@@ -480,7 +480,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
} }
}); // leaves all creatures that will be destroyed }); // leaves all creatures that will be destroyed
} // -X/-X end } // -X/-X end
else if (attack < 0 && !game.getPhaseHandler().isPreventCombatDamageThisTurn()) { else if (attack < 0 && !game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
// spells that give -X/0 // spells that give -X/0
boolean isMyTurn = game.getPhaseHandler().isPlayerTurn(ai); boolean isMyTurn = game.getPhaseHandler().isPlayerTurn(ai);
if (isMyTurn) { if (isMyTurn) {

View File

@@ -113,7 +113,7 @@ public class PumpAllAi extends PumpAiBase {
if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer()) || game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())
|| game.getPhaseHandler().isPreventCombatDamageThisTurn()) { || game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
return false; return false;
} }
int totalPower = 0; int totalPower = 0;

View File

@@ -187,9 +187,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
restDamage = 0; 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 is greater than restDamage, damage was prevented
if (damage > restDamage) { if (damage > restDamage) {
int prevent = damage - restDamage; int prevent = damage - restDamage;

View File

@@ -1,7 +1,14 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.GameCommand;
import forge.game.Game;
import forge.game.ability.SpellAbilityEffect; 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.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
public class FogEffect extends SpellAbilityEffect { public class FogEffect extends SpellAbilityEffect {
@@ -13,7 +20,28 @@ public class FogEffect extends SpellAbilityEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
// Expand Fog keyword here depending on what we need out of it. final Card hostCard = sa.getHostCard();
sa.getActivatingPlayer().getGame().getPhaseHandler().setPreventCombatDamageThisTurn(); 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);
}
});
} }
} }

View File

@@ -5212,7 +5212,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return damageIn; return damageIn;
} }
if (isCombat && getGame().getPhaseHandler().isPreventCombatDamageThisTurn()) { if (isCombat && getGame().getReplacementHandler().isPreventCombatDamageThisTurn()) {
return 0; return 0;
} }

View File

@@ -2228,6 +2228,59 @@ public class CardFactoryUtil {
return re; 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) public static ReplacementEffect makeEtbCounter(final String kw, final CardState card, final boolean intrinsic)
{ {
String parse = kw; String parse = kw;
@@ -3915,6 +3968,10 @@ public class CardFactoryUtil {
inst.addReplacement(re); 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 " 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.")) { + "from anywhere, reveal CARDNAME and shuffle it into its owner's library instead.")) {

View File

@@ -92,7 +92,6 @@ public class PhaseHandler implements java.io.Serializable {
private int nUpkeepsThisGame = 0; private int nUpkeepsThisGame = 0;
private int nCombatsThisTurn = 0; private int nCombatsThisTurn = 0;
private int nMain2sThisTurn = 0; private int nMain2sThisTurn = 0;
private boolean bPreventCombatDamageThisTurn = false;
private int planarDiceRolledthisTurn = 0; private int planarDiceRolledthisTurn = 0;
private transient Player playerTurn = null; private transient Player playerTurn = null;
@@ -520,7 +519,6 @@ public class PhaseHandler implements java.io.Serializable {
break; break;
case CLEANUP: case CLEANUP:
bPreventCombatDamageThisTurn = false;
if (!bRepeatCleanup) { if (!bRepeatCleanup) {
// only call onCleanupPhase when Cleanup is not repeated // only call onCleanupPhase when Cleanup is not repeated
game.onCleanupPhase(); 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); 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() { private Player handleNextTurn() {
game.getStack().onNextTurn(); game.getStack().onNextTurn();
// reset mustAttackEntity // reset mustAttackEntity
@@ -1222,10 +1216,6 @@ public class PhaseHandler implements java.io.Serializable {
onPhaseBegin(); onPhaseBegin();
} }
public final void setPreventCombatDamageThisTurn() {
bPreventCombatDamageThisTurn = true;
}
public int getPlanarDiceRolledthisTurn() { public int getPlanarDiceRolledthisTurn() {
return planarDiceRolledthisTurn; return planarDiceRolledthisTurn;
} }

View File

@@ -719,7 +719,7 @@ public class Player extends GameEntity implements Comparable<Player> {
return damage; return damage;
} }
if (isCombat && game.getPhaseHandler().isPreventCombatDamageThisTurn()) { if (isCombat && game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
return 0; return 0;
} }
@@ -1195,7 +1195,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public boolean hasProtectionFromDamage(final Card source) { public boolean hasProtectionFromDamage(final Card source) {
return hasProtectionFrom(source, false, false); return hasProtectionFrom(source, false, true);
} }
@Override @Override

View File

@@ -1,7 +1,10 @@
package forge.game.player; package forge.game.player;
import forge.card.CardType;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
public class PlayerFactoryUtil { public class PlayerFactoryUtil {
@@ -39,6 +42,58 @@ public class PlayerFactoryUtil {
} }
public static void addReplacementEffect(final KeywordInterface inst, Player player) { 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) { public static void addSpellAbility(final KeywordInterface inst, Player player) {

View File

@@ -482,4 +482,28 @@ public class ReplacementHandler {
} }
return totalAmount; 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<ReplacementEffect> list = Lists.newArrayList();
game.forEachCardInGame(new Visitor<Card>() {
@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();
}
} }