Merge branch 'triggerDamageAll' into 'master'

TriggerDamageAll: add new Trigger for Mindblade Render

See merge request core-developers/forge!2519
This commit is contained in:
Michael Kamensky
2020-02-13 15:12:52 +00:00
18 changed files with 171 additions and 39 deletions

View File

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

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
package forge.game;
import com.google.common.base.Predicate;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* <p>
* Predicate<GameObject> interface.
* </p>
*
* @author Forge
*/
public final class GameObjectPredicates {
public static final Predicate<GameObject> restriction(final String[] restrictions, final Player sourceController, final Card source, final SpellAbility spellAbility) {
return new Predicate<GameObject>() {
@Override
public boolean apply(final GameObject c) {
return (c != null) && c.isValid(restrictions, sourceController, source, spellAbility);
}
};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Card, GameEntity, Integer> {
private Table<Card, GameEntity, Integer> dataMap = HashBasedTable.create();
public CardDamageMap(Table<Card, GameEntity, Integer> damageMap) {
this.putAll(damageMap);
}
@@ -38,13 +42,13 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
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<Card, Map<GameEntity, Integer>> e : this.rowMap().entrySet()) {
final Card sourceLKI = e.getKey();
@@ -58,9 +62,9 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
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<Card, GameEntity, Integer> {
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<AbilityKey, Object> 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<Card, GameEntity, Integer> {
return dataMap;
}
public int filteredAmount(String validSource, String validTarget, Card host, SpellAbility sa) {
int result = 0;
Set<Card> filteredSource = null;
Set<GameEntity> 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<Card, GameEntity, Integer> c : cellSet()) {
if (filteredSource != null && !filteredSource.contains(c.getRowKey())) {
continue;
}
if (filteredTarget != null && !filteredTarget.contains(c.getColumnKey())) {
continue;
}
result += c.getValue();
}
return result;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String, String> params, Card host, boolean intrinsic) {
super(params, host, intrinsic);
}
@Override
public boolean performTest(Map<AbilityKey, Object> 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<AbilityKey, Object> 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);
}
}

View File

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

View File

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