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()) {
return false;
}
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
return false;
}
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}
});
}
}

View File

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

View File

@@ -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.")) {

View File

@@ -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;
}

View File

@@ -719,7 +719,7 @@ public class Player extends GameEntity implements Comparable<Player> {
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<Player> {
public boolean hasProtectionFromDamage(final Card source) {
return hasProtectionFrom(source, false, false);
return hasProtectionFrom(source, false, true);
}
@Override

View File

@@ -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) {

View File

@@ -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<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();
}
}