diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index e63ae76c366..1df09691ee1 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -561,4 +561,12 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { // this does overwrite the original MapParams this.originalMapParams = Maps.newHashMap(this.mapParams); } + + protected void copyHelper(CardTraitBase copy, Card host) { + copy.originalMapParams = Maps.newHashMap(originalMapParams); + copy.mapParams = Maps.newHashMap(originalMapParams); + copy.sVars = Maps.newHashMap(sVars); + // dont use setHostCard to not trigger the not copied parts yet + copy.hostCard = host; + } } diff --git a/forge-game/src/main/java/forge/game/GameObjectPredicates.java b/forge-game/src/main/java/forge/game/GameObjectPredicates.java new file mode 100644 index 00000000000..9331bcb6a89 --- /dev/null +++ b/forge-game/src/main/java/forge/game/GameObjectPredicates.java @@ -0,0 +1,44 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.game; + +import com.google.common.base.Predicate; + +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; + + +/** + *

+ * Predicate interface. + *

+ * + * @author Forge + */ +public final class GameObjectPredicates { + + public static final Predicate restriction(final String[] restrictions, final Player sourceController, final Card source, final SpellAbility spellAbility) { + return new Predicate() { + @Override + public boolean apply(final GameObject c) { + return (c != null) && c.isValid(restrictions, sourceController, source, spellAbility); + } + }; + } +} diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java index c68659e0709..a4e8e6c1217 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java @@ -122,7 +122,7 @@ public class DamageAllEffect extends DamageBaseEffect { if (!usedDamageMap) { preventMap.triggerPreventDamage(false); - damageMap.triggerDamageDoneOnce(false, sa); + damageMap.triggerDamageDoneOnce(false, game, sa); preventMap.clear(); damageMap.clear(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java index 5ba5f4d8203..6092fde9af8 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java @@ -166,7 +166,7 @@ public class DamageDealEffect extends DamageBaseEffect { if (!usedDamageMap) { preventMap.triggerPreventDamage(false); // non combat damage cause lifegain there - damageMap.triggerDamageDoneOnce(false, sa); + damageMap.triggerDamageDoneOnce(false, game, sa); preventMap.clear(); damageMap.clear(); @@ -215,7 +215,7 @@ public class DamageDealEffect extends DamageBaseEffect { if (!usedDamageMap) { preventMap.triggerPreventDamage(false); // non combat damage cause lifegain there - damageMap.triggerDamageDoneOnce(false, sa); + damageMap.triggerDamageDoneOnce(false, game, sa); preventMap.clear(); damageMap.clear(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java index 8887b60bfb6..913b6e979a2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java @@ -136,7 +136,7 @@ public class DamageEachEffect extends DamageBaseEffect { if (!usedDamageMap) { preventMap.triggerPreventDamage(false); - damageMap.triggerDamageDoneOnce(false, sa); + damageMap.triggerDamageDoneOnce(false, game, sa); preventMap.clear(); damageMap.clear(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageResolveEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageResolveEffect.java index 6ed532889a6..a9d7c35a677 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamageResolveEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamageResolveEffect.java @@ -25,7 +25,7 @@ public class DamageResolveEffect extends SpellAbilityEffect { } // non combat damage cause lifegain there if (damageMap != null) { - damageMap.triggerDamageDoneOnce(false, sa); + damageMap.triggerDamageDoneOnce(false, sa.getHostCard().getGame(), sa); damageMap.clear(); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java index 1596cbea793..7ae2fc18dc7 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java @@ -154,7 +154,7 @@ public class FightEffect extends DamageBaseEffect { if (!usedDamageMap) { preventMap.triggerPreventDamage(false); - damageMap.triggerDamageDoneOnce(false, sa); + damageMap.triggerDamageDoneOnce(false, fighterA.getGame(), sa); preventMap.clear(); damageMap.clear(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java index e7ed46e2b1e..54e32d8e6d6 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java @@ -236,7 +236,7 @@ public class RepeatEachEffect extends SpellAbilityEffect { sa.getPreventMap().triggerPreventDamage(false); sa.setPreventMap(null); // non combat damage cause lifegain there - sa.getDamageMap().triggerDamageDoneOnce(false, sa); + sa.getDamageMap().triggerDamageDoneOnce(false, game, sa); sa.setDamageMap(null); } if (sa.hasParam("ChangeZoneTable")) { diff --git a/forge-game/src/main/java/forge/game/card/CardDamageMap.java b/forge-game/src/main/java/forge/game/card/CardDamageMap.java index 8a742dacf68..b5cf899f352 100644 --- a/forge-game/src/main/java/forge/game/card/CardDamageMap.java +++ b/forge-game/src/main/java/forge/game/card/CardDamageMap.java @@ -1,16 +1,20 @@ /** - * + * */ package forge.game.card; import java.util.Map; +import java.util.Set; import com.google.common.collect.ForwardingTable; import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.common.collect.Table; +import forge.game.Game; import forge.game.GameEntity; +import forge.game.GameObjectPredicates; import forge.game.ability.AbilityKey; import forge.game.keyword.Keyword; import forge.game.spellability.SpellAbility; @@ -18,7 +22,7 @@ import forge.game.trigger.TriggerType; public class CardDamageMap extends ForwardingTable { private Table dataMap = HashBasedTable.create(); - + public CardDamageMap(Table damageMap) { this.putAll(damageMap); } @@ -38,13 +42,13 @@ public class CardDamageMap extends ForwardingTable { runParams.put(AbilityKey.DamageTarget, ge); runParams.put(AbilityKey.DamageAmount, sum); runParams.put(AbilityKey.IsCombatDamage, isCombat); - + ge.getGame().getTriggerHandler().runTrigger(TriggerType.DamagePreventedOnce, runParams, false); } } } - public void triggerDamageDoneOnce(boolean isCombat, final SpellAbility sa) { + public void triggerDamageDoneOnce(boolean isCombat, final Game game, final SpellAbility sa) { // Source -> Targets for (Map.Entry> e : this.rowMap().entrySet()) { final Card sourceLKI = e.getKey(); @@ -58,9 +62,9 @@ public class CardDamageMap extends ForwardingTable { runParams.put(AbilityKey.DamageTargets, Sets.newHashSet(e.getValue().keySet())); runParams.put(AbilityKey.DamageAmount, sum); runParams.put(AbilityKey.IsCombatDamage, isCombat); - - sourceLKI.getGame().getTriggerHandler().runTrigger(TriggerType.DamageDealtOnce, runParams, false); - + + game.getTriggerHandler().runTrigger(TriggerType.DamageDealtOnce, runParams, false); + if (sourceLKI.hasKeyword(Keyword.LIFELINK)) { sourceLKI.getController().gainLife(sum, sourceLKI, sa); } @@ -79,10 +83,15 @@ public class CardDamageMap extends ForwardingTable { runParams.put(AbilityKey.DamageSources, Sets.newHashSet(e.getValue().keySet())); runParams.put(AbilityKey.DamageAmount, sum); runParams.put(AbilityKey.IsCombatDamage, isCombat); - - ge.getGame().getTriggerHandler().runTrigger(TriggerType.DamageDoneOnce, runParams, false); + + game.getTriggerHandler().runTrigger(TriggerType.DamageDoneOnce, runParams, false); } } + + final Map runParams = AbilityKey.newMap(); + runParams.put(AbilityKey.DamageMap, new CardDamageMap(this)); + runParams.put(AbilityKey.IsCombatDamage, isCombat); + game.getTriggerHandler().runTrigger(TriggerType.DamageAll, runParams, false); } /** * special put logic, sum the values @@ -98,4 +107,29 @@ public class CardDamageMap extends ForwardingTable { return dataMap; } + public int filteredAmount(String validSource, String validTarget, Card host, SpellAbility sa) { + int result = 0; + + Set filteredSource = null; + Set filteredTarget = null; + if (validSource != null) { + filteredSource = Sets.newHashSet(Iterables.filter(rowKeySet(), GameObjectPredicates.restriction(validSource.split(","), host.getController(), host, sa))); + } + if (validTarget != null) { + filteredTarget = Sets.newHashSet(Iterables.filter(columnKeySet(), GameObjectPredicates.restriction(validTarget.split(","), host.getController(), host, sa))); + } + + for (Table.Cell c : cellSet()) { + if (filteredSource != null && !filteredSource.contains(c.getRowKey())) { + continue; + } + if (filteredTarget != null && !filteredTarget.contains(c.getColumnKey())) { + continue; + } + result += c.getValue(); + } + + return result; + } + } diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java index 72099af6fb4..4fc83734d74 100644 --- a/forge-game/src/main/java/forge/game/combat/Combat.java +++ b/forge-game/src/main/java/forge/game/combat/Combat.java @@ -836,7 +836,7 @@ public class Combat { // Run the trigger to deal combat damage once // LifeLink for Combat Damage at this place - dealtDamageTo.triggerDamageDoneOnce(true, null); + dealtDamageTo.triggerDamageDoneOnce(true, game, null); dealtDamageTo.clear(); counterTable.triggerCountersPutAll(game); diff --git a/forge-game/src/main/java/forge/game/cost/CostDamage.java b/forge-game/src/main/java/forge/game/cost/CostDamage.java index cc33d7c6501..198ac700ea8 100644 --- a/forge-game/src/main/java/forge/game/cost/CostDamage.java +++ b/forge-game/src/main/java/forge/game/cost/CostDamage.java @@ -74,7 +74,7 @@ public class CostDamage extends CostPart { payer.addDamage(decision.c, source, damageMap, preventMap, table, sa); preventMap.triggerPreventDamage(false); - damageMap.triggerDamageDoneOnce(false, sa); + damageMap.triggerDamageDoneOnce(false, source.getGame(), sa); table.triggerCountersPutAll(payer.getGame()); preventMap.clear(); diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java index 34f48bcc28c..2ff6a07d9ec 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java @@ -164,9 +164,8 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { */ public final ReplacementEffect copy(final Card host, final boolean lki) { final ReplacementEffect res = (ReplacementEffect) clone(); - for (String key : getSVars()) { - res.setSVar(key, getSVar(key)); - } + + copyHelper(res, host); final SpellAbility sa = this.getOverridingAbility(); if (sa != null) { @@ -182,8 +181,6 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { res.setOtherChoices(null); } - res.setHostCard(host); - res.setActiveZone(validHostZones); res.setLayer(getLayer()); return res; diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index d467b6c0ffe..75479eaadfc 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -870,13 +870,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit clone.view = new SpellAbilityView(clone); // dont use setHostCard to not trigger the not copied parts yet - clone.hostCard = host; + + copyHelper(clone, host); if (!lki && host != null && host.getGame() != null) { host.getGame().addSpellAbility(clone); } - // need to clone the maps too so they can be changed - clone.originalMapParams = Maps.newHashMap(this.originalMapParams); - clone.mapParams = Maps.newHashMap(this.mapParams); clone.triggeringObjects = AbilityKey.newMap(this.triggeringObjects); @@ -902,7 +900,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit // clear maps for copy, the values will be added later clone.additionalAbilities = Maps.newHashMap(); clone.additionalAbilityLists = Maps.newHashMap(); - clone.sVars = Maps.newHashMap(); // run special copy Ability to make a deep copy CardFactory.copySpellAbility(this, clone, host, activ, lki); } catch (final CloneNotSupportedException e) { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index 3f3b33d26b9..f8442310ec0 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -819,13 +819,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone clone = (StaticAbility) clone(); clone.id = lki ? id : nextId(); - // dont use setHostCard to not trigger the not copied parts yet - clone.hostCard = host; - // need to clone the maps too so they can be changed - clone.originalMapParams = Maps.newHashMap(this.originalMapParams); - clone.mapParams = Maps.newHashMap(this.mapParams); - - clone.sVars = Maps.newHashMap(this.sVars); + copyHelper(clone, host); clone.layers = this.generateLayer(); diff --git a/forge-game/src/main/java/forge/game/trigger/Trigger.java b/forge-game/src/main/java/forge/game/trigger/Trigger.java index b9130a8cbd8..8388fa3ccdf 100644 --- a/forge-game/src/main/java/forge/game/trigger/Trigger.java +++ b/forge-game/src/main/java/forge/game/trigger/Trigger.java @@ -37,7 +37,6 @@ import forge.game.zone.ZoneType; import java.util.*; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import com.google.common.collect.Sets; import forge.util.TextUtil; @@ -523,9 +522,7 @@ public abstract class Trigger extends TriggerReplacementBase { public final Trigger copy(Card newHost, boolean lki) { final Trigger copy = (Trigger) clone(); - copy.originalMapParams = Maps.newHashMap(originalMapParams); - copy.mapParams = Maps.newHashMap(originalMapParams); - copy.setHostCard(newHost); + copyHelper(copy, newHost); if (getOverridingAbility() != null) { copy.setOverridingAbility(getOverridingAbility().copy(newHost, lki)); diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDamageAll.java b/forge-game/src/main/java/forge/game/trigger/TriggerDamageAll.java new file mode 100644 index 00000000000..4df7cf871b8 --- /dev/null +++ b/forge-game/src/main/java/forge/game/trigger/TriggerDamageAll.java @@ -0,0 +1,52 @@ +package forge.game.trigger; + +import java.util.Map; + +import forge.game.ability.AbilityKey; +import forge.game.card.Card; +import forge.game.card.CardDamageMap; +import forge.game.spellability.SpellAbility; +import forge.util.Localizer; + +public class TriggerDamageAll extends Trigger { + + public TriggerDamageAll(Map params, Card host, boolean intrinsic) { + super(params, host, intrinsic); + } + + @Override + public boolean performTest(Map runParams) { + + if (hasParam("CombatDamage")) { + if (getParam("CombatDamage").equals("True")) { + if (!((Boolean) runParams.get(AbilityKey.IsCombatDamage))) { + return false; + } + } else if (getParam("CombatDamage").equals("False")) { + if (((Boolean) runParams.get(AbilityKey.IsCombatDamage))) { + return false; + } + } + } + final CardDamageMap table = (CardDamageMap) runParams.get(AbilityKey.DamageMap); + return filterTable(table) > 0; + } + + @Override + public void setTriggeringObjects(SpellAbility sa, Map runParams) { + final CardDamageMap table = (CardDamageMap) runParams.get(AbilityKey.DamageMap); + + sa.setTriggeringObject(AbilityKey.DamageAmount, filterTable(table)); + } + + @Override + public String getImportantStackObjects(SpellAbility sa) { + StringBuilder sb = new StringBuilder(); + sb.append(Localizer.getInstance().getMessage("lblAmount")).append(": ").append(sa.getTriggeringObject(AbilityKey.DamageAmount)); + return sb.toString(); + } + + private int filterTable(CardDamageMap table) { + return table.filteredAmount(getParam("ValidSource"), getParam("ValidTarget"), getHostCard(), null); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerType.java b/forge-game/src/main/java/forge/game/trigger/TriggerType.java index 952a21bb6f9..b0f82c1c153 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerType.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerType.java @@ -43,6 +43,7 @@ public enum TriggerType { CounterRemovedOnce(TriggerCounterRemovedOnce.class), Crewed(TriggerCrewed.class), Cycled(TriggerCycled.class), + DamageAll(TriggerDamageAll.class), DamageDealtOnce(TriggerDamageDealtOnce.class), DamageDone(TriggerDamageDone.class), DamageDoneOnce(TriggerDamageDoneOnce.class), diff --git a/forge-gui/res/cardsfolder/m/mindblade_render.txt b/forge-gui/res/cardsfolder/m/mindblade_render.txt new file mode 100644 index 00000000000..0be61532af9 --- /dev/null +++ b/forge-gui/res/cardsfolder/m/mindblade_render.txt @@ -0,0 +1,8 @@ +Name:Mindblade Render +ManaCost:1 B +Types:Creature Azra Warrior +PT:1/3 +T:Mode$ DamageAll | ValidSource$ Creature.Warrior | ValidTarget$ Player.Opponent | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever your opponents are dealt combat damage, if any of that damage was dealt by a Warrior, you draw a card and you lose 1 life. +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SubAbility$ DBLoseLife +SVar:DBLoseLife:DB$LoseLife | Defined$ You | LifeAmount$ 1 +Oracle: Whenever your opponents are dealt combat damage, if any of that damage was dealt by a Warrior, you draw a card and you lose 1 life.