Refactor damage can't be prevented effect

This commit is contained in:
Alumi
2021-05-11 16:47:54 +00:00
committed by Michael Kamensky
parent e2fc8015b8
commit 4d6da7454c
37 changed files with 153 additions and 196 deletions

View File

@@ -39,7 +39,6 @@ import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
public abstract class GameEntity extends GameObject implements IIdentifiable {
@@ -67,31 +66,28 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
public final int addDamage(final int damage, final Card source, boolean isCombat, boolean noPrevention,
final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
if (noPrevention) {
return addDamageWithoutPrevention(damage, source, damageMap, preventMap, counterTable, cause);
} else if (isCombat) {
return addCombatDamage(damage, source, damageMap, preventMap, counterTable);
int damageToDo = replaceDamage(damage, source, isCombat, !noPrevention, damageMap, preventMap, counterTable, cause);
if (damageToDo <= 0) {
return 0;
}
if (isCombat) {
source.getDamageHistory().registerCombatDamage(this);
return addCombatDamageBase(damageToDo, source, damageMap, counterTable);
} else {
return addDamage(damage, source, damageMap, preventMap, counterTable, cause);
return addDamageAfterPrevention(damageToDo, source, false, damageMap, counterTable);
}
}
public int addDamage(final int damage, final Card source, final CardDamageMap damageMap,
final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
int damageToDo = damage;
damageToDo = replaceDamage(damageToDo, source, false, true, damageMap, preventMap, counterTable, cause);
damageToDo = preventDamage(damageToDo, source, false, preventMap, cause);
int damageToDo = replaceDamage(damage, source, false, true, damageMap, preventMap, counterTable, cause);
return addDamageAfterPrevention(damageToDo, source, false, damageMap, counterTable);
}
public final int addCombatDamage(final int damage, final Card source, final CardDamageMap damageMap,
final CardDamageMap preventMap, GameEntityCounterTable counterTable) {
int damageToDo = damage;
damageToDo = replaceDamage(damageToDo, source, true, true, damageMap, preventMap, counterTable, null);
damageToDo = preventDamage(damageToDo, source, true, preventMap, null);
int damageToDo = replaceDamage(damage, source, true, true, damageMap, preventMap, counterTable, null);
if (damageToDo > 0) {
source.getDamageHistory().registerCombatDamage(this);
@@ -104,14 +100,14 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
return addDamageAfterPrevention(damage, source, true, damageMap, counterTable);
}
public int addDamageWithoutPrevention(final int damage, final Card source, final CardDamageMap damageMap,
final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
int damageToDo = replaceDamage(damage, source, false, false, damageMap, preventMap, counterTable, cause);
return addDamageAfterPrevention(damageToDo, source, false, damageMap, counterTable);
}
public int replaceDamage(final int damage, final Card source, final boolean isCombat, final boolean prevention,
public int replaceDamage(final int damage, final Card source, final boolean isCombat, boolean prevention,
final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
if (!source.canDamagePrevented(isCombat)) {
prevention = false;
}
int restDamage = damage;
// Replacement effects
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.DamageSource, source);
@@ -127,22 +123,24 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
switch (getGame().getReplacementHandler().run(ReplacementType.DamageDone, repParams)) {
case NotReplaced:
return damage;
break;
case Updated:
int newDamage = (int) repParams.get(AbilityKey.DamageAmount);
GameEntity newTarget = (GameEntity) repParams.get(AbilityKey.Affected);
// check if this is still the affected card or player
if (this.equals(newTarget)) {
return newDamage;
restDamage = newDamage;
} else {
if (prevention) {
newDamage = newTarget.preventDamage(newDamage, source, isCombat, preventMap, cause);
}
newDamage = newTarget.replaceDamage(newDamage, source, isCombat, prevention, damageMap, preventMap, counterTable, cause);
newTarget.addDamageAfterPrevention(newDamage, source, isCombat, damageMap, counterTable);
restDamage = 0;
}
break;
default:
return 0;
restDamage = 0;
}
return restDamage;
}
// This function handles damage after replacement and prevention effects are applied
@@ -156,54 +154,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
// not change the game state)
public abstract int staticReplaceDamage(final int damage, final Card source, final boolean isCombat);
public final int preventDamage(
final int damage, final Card source, final boolean isCombat, CardDamageMap preventMap,
final SpellAbility cause) {
if (!source.canDamagePrevented(isCombat)) {
return damage;
}
int restDamage = damage;
// first try to replace the damage
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.DamageSource, source);
repParams.put(AbilityKey.DamageAmount, damage);
repParams.put(AbilityKey.IsCombat, isCombat);
repParams.put(AbilityKey.Prevention, true);
repParams.put(AbilityKey.PreventMap, preventMap);
if (cause != null) {
repParams.put(AbilityKey.Cause, cause);
}
switch (getGame().getReplacementHandler().run(ReplacementType.DamageDone, repParams)) {
case NotReplaced:
restDamage = damage;
break;
case Updated:
restDamage = (int) repParams.get(AbilityKey.DamageAmount);
break;
default:
restDamage = 0;
}
// if damage is greater than restDamage, damage was prevented
if (damage > restDamage) {
int prevent = damage - restDamage;
preventMap.put(source, this, damage - restDamage);
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.DamageTarget, this);
runParams.put(AbilityKey.DamageAmount, prevent);
runParams.put(AbilityKey.DamageSource, source);
runParams.put(AbilityKey.IsCombatDamage, isCombat);
getGame().getTriggerHandler().runTrigger(TriggerType.DamagePrevented, runParams, false);
}
return restDamage;
}
public int getPreventNextDamageTotalShields() {
return getGame().getReplacementHandler().getTotalPreventionShieldAmount(this);
}

View File

@@ -5,16 +5,24 @@ import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardDamageMap;
import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.util.TextUtil;
/**
* This class handles two kinds of prevention effect:
* - Prevent next X damages. Those will use `Amount$ <SVar>`, and the `<SVar>` will have form `Number$ X`.
* That SVar will be updated after each prevention "shield" used up.
* - Prevent X damages. Those will use `Amount$ N` or `Amount$ <SVar>`, where the `<SVar>` will have form other than
* `Number$ X`. These "shields" are not used up so won't be updated. */
public class ReplaceDamageEffect extends SpellAbilityEffect {
@Override
@@ -27,33 +35,44 @@ public class ReplaceDamageEffect extends SpellAbilityEffect {
return;
}
final ReplacementType event = sa.getReplacementEffect().getMode();
String varValue = sa.getParamOrDefault("Amount", "1");
@SuppressWarnings("unchecked")
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
Map<AbilityKey, Object> params = AbilityKey.newMap(originalParams);
Integer dmg = (Integer) sa.getReplacingObject(AbilityKey.DamageAmount);
String varValue = sa.getParamOrDefault("Amount", "1");
int prevent = AbilityUtils.calculateAmount(card, varValue, sa);
// Currently it does reduce damage by amount, need second mode for Setting Damage
if (prevent > 0) {
int n = Math.min(dmg, prevent);
dmg -= n;
prevent -= n;
if (card.getType().hasStringType("Effect") && prevent <= 0) {
game.getAction().exile(card, null);
} else if (!StringUtils.isNumeric(varValue) && card.getSVar(varValue).startsWith("Number$")) {
card.setSVar(varValue, "Number$" + prevent);
card.updateAbilityTextForView();
if (!StringUtils.isNumeric(varValue) && card.getSVar(varValue).startsWith("Number$")) {
if (card.getType().hasStringType("Effect") && prevent <= 0) {
game.getAction().exile(card, null);
} else {
card.setSVar(varValue, "Number$" + prevent);
card.updateAbilityTextForView();
}
}
// Set PreventedDamage SVar for PreventionSubAbility
// Set PreventedDamage SVar
card.setSVar("PreventedDamage", "Number$" + n);
// Set prevent map entry
CardDamageMap preventMap = (CardDamageMap) originalParams.get(AbilityKey.PreventMap);
Card source = (Card) sa.getReplacingObject(AbilityKey.Source);
GameEntity target = (GameEntity) sa.getReplacingObject(AbilityKey.Target);
preventMap.put(source, target, n);
// Following codes are commented out since DamagePrevented trigger is currently not used by any Card.
// final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
// runParams.put(AbilityKey.DamageTarget, target);
// runParams.put(AbilityKey.DamageAmount, dmg);
// runParams.put(AbilityKey.DamageSource, source);
// runParams.put(AbilityKey.IsCombatDamage, originalParams.get(AbilityKey.IsCombat));
// game.getTriggerHandler().runTrigger(TriggerType.DamagePrevented, runParams, false);
}
// no damage for original target anymore
@@ -71,6 +90,7 @@ public class ReplaceDamageEffect extends SpellAbilityEffect {
}
//try to call replacementHandler with new Params
final ReplacementType event = sa.getReplacementEffect().getMode();
ReplacementResult result = game.getReplacementHandler().run(event, params);
switch (result) {
case NotReplaced:

View File

@@ -2228,9 +2228,7 @@ 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();
public static String getProtectionReplacementValidSource(final String kw) {
String validSource = "Card.";
if (kw.startsWith("Protection:")) {
@@ -2259,8 +2257,10 @@ public class CardFactoryUtil {
validSource += "RedSource";
} else if (protectType.equals("green")) {
validSource += "GreenSource";
} else if (protectType.equals("colorless")) {
validSource += "ColorlessSource";
} else if (protectType.equals("all colors")) {
validSource += "nonColorless";
validSource += "nonColorlessSource";
} else if (protectType.equals("everything")) {
validSource = "";
} else if (protectType.startsWith("opponent of ")) {
@@ -2271,14 +2271,7 @@ public class CardFactoryUtil {
}
}
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;
return validSource;
}
public static ReplacementEffect makeEtbCounter(final String kw, final CardState card, final boolean intrinsic)
@@ -3969,7 +3962,15 @@ public class CardFactoryUtil {
}
}
else if (keyword.startsWith("Protection")) {
ReplacementEffect re = createProtectionReplacement(card, keyword, intrinsic);
String validSource = getProtectionReplacementValidSource(keyword);
String rep = "Event$ DamageDone | Prevent$ True | ActiveZones$ Battlefield | ValidTarget$ Card.Self";
if (!validSource.isEmpty()) {
rep += " | ValidSource$ " + validSource;
}
rep += " | Secondary$ True | TiedToKeyword$ " + keyword + " | Description$ " + keyword;
ReplacementEffect re = ReplacementHandler.parseReplacement(rep, host, intrinsic, card);
inst.addReplacement(re);
}

View File

@@ -1,7 +1,7 @@
package forge.game.player;
import forge.card.CardType;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.keyword.KeywordInterface;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
@@ -46,41 +46,7 @@ public class PlayerFactoryUtil {
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);
}
}
String validSource = CardFactoryUtil.getProtectionReplacementValidSource(keyword);
effect = "Event$ DamageDone | Prevent$ True | ActiveZones$ Command | ValidTarget$ You";
if (!validSource.isEmpty()) {

View File

@@ -51,9 +51,6 @@ public class ReplaceDamage extends ReplacementEffect {
public boolean canReplace(Map<AbilityKey, Object> runParams) {
final Game game = getHostCard().getGame();
if (!(runParams.containsKey(AbilityKey.Prevention) == (hasParam("PreventionEffect") || hasParam("Prevent")))) {
return false;
}
if (((Integer) runParams.get(AbilityKey.DamageAmount)) == 0) {
// If no actual damage is dealt, there is nothing to replace
return false;

View File

@@ -38,6 +38,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardDamageMap;
import forge.game.card.CardState;
import forge.game.card.CardTraitChanges;
import forge.game.card.CardUtil;
@@ -280,6 +281,23 @@ public class ReplacementHandler {
return res;
}
private void putPreventMapEntry(final Map<AbilityKey, Object> runParams) {
// Set prevent map entry
CardDamageMap preventMap = (CardDamageMap) runParams.get(AbilityKey.PreventMap);
Card source = (Card) runParams.get(AbilityKey.DamageSource);
GameEntity target = (GameEntity) runParams.get(AbilityKey.Affected);
Integer damage = (Integer) runParams.get(AbilityKey.DamageAmount);
preventMap.put(source, target, damage);
// Following codes are commented out since DamagePrevented trigger is currently not used by any Card.
// final Map<AbilityKey, Object> trigParams = AbilityKey.newMap();
// trigParams.put(AbilityKey.DamageTarget, target);
// trigParams.put(AbilityKey.DamageAmount, damage);
// trigParams.put(AbilityKey.DamageSource, source);
// trigParams.put(AbilityKey.IsCombatDamage, runParams.get(AbilityKey.IsCombat));
// game.getTriggerHandler().runTrigger(TriggerType.DamagePrevented, trigParams, false);
}
/**
*
* Runs a single replacement effect.
@@ -349,10 +367,14 @@ public class ReplacementHandler {
}
}
if (mapParams.containsKey("Prevent")) {
if (mapParams.get("Prevent").equals("True")) {
return ReplacementResult.Prevented; // Nothing should replace the event.
if (mapParams.containsKey("Prevent") && mapParams.get("Prevent").equals("True")) {
if (replacementEffect.getMode() == ReplacementType.DamageDone) {
if (Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))) {
return ReplacementResult.NotReplaced;
}
putPreventMapEntry(runParams);
}
return ReplacementResult.Prevented; // Nothing should replace the event.
}
if (mapParams.containsKey("Skip")) {
@@ -361,9 +383,25 @@ public class ReplacementHandler {
}
}
boolean cantPreventDamage = (replacementEffect.getMode() == ReplacementType.DamageDone
&& mapParams.containsKey("PreventionEffect")
&& Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage)));
Player player = host.getController();
player.getController().playSpellAbilityNoStack(effectSA, true);
if (!cantPreventDamage || mapParams.containsKey("AlwaysReplace")) {
player.getController().playSpellAbilityNoStack(effectSA, true);
if (replacementEffect.getMode() == ReplacementType.DamageDone
&& effectSA.getApi() != ApiType.ReplaceDamage && !cantPreventDamage) {
putPreventMapEntry(runParams);
}
}
// If can't prevent damage, result is not replaced
if (cantPreventDamage) {
return ReplacementResult.NotReplaced;
}
// if the spellability is a replace effect then its some new logic
// if ReplacementResult is set in run params use that instead
if (runParams.containsKey(AbilityKey.ReplacementResult)) {