mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 04:38:00 +00:00
Refactor damage can't be prevented effect
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user