From d8b665f64fd624f6bc8f06eddcfd3d7b5f198d74 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 10 Apr 2021 18:43:40 +0200 Subject: [PATCH] TriggerCounterPlayerAddedAll: new Trigger that triggers when a player puts more counters on one object --- .../main/java/forge/ai/AiCostDecision.java | 14 ++-- .../main/java/forge/game/CardTraitBase.java | 8 +- .../forge/game/GameEntityCounterTable.java | 76 +++++++++++++------ .../java/forge/game/ability/AbilityKey.java | 1 + .../ability/effects/CountersPutEffect.java | 32 ++++---- .../src/main/java/forge/game/card/Card.java | 2 +- .../forge/game/cost/CostRemoveAnyCounter.java | 17 +++-- .../main/java/forge/game/player/Player.java | 2 +- .../trigger/TriggerCounterPlayerAddedAll.java | 49 ++++++++++++ .../java/forge/game/trigger/TriggerType.java | 1 + .../cardsfolder/upcoming/bold_plagiarist.txt | 8 ++ .../java/forge/player/HumanCostDecision.java | 18 ++--- 12 files changed, 159 insertions(+), 69 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/trigger/TriggerCounterPlayerAddedAll.java create mode 100644 forge-gui/res/cardsfolder/upcoming/bold_plagiarist.txt diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index 4598da340e6..4c8a9d9f98a 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -566,7 +566,7 @@ public class AiCostDecision extends CostDecisionMakerBase { int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove); if (thisRemove > 0) { removed += thisRemove; - table.put(prefCard, CounterType.get(cType), thisRemove); + table.put(null, prefCard, CounterType.get(cType), thisRemove); } } } @@ -632,7 +632,7 @@ public class AiCostDecision extends CostDecisionMakerBase { int thisRemove = Math.min(card.getCounters(ctype), c - toRemove); if (thisRemove > 0) { toRemove += thisRemove; - table.put(card, ctype, thisRemove); + table.put(null, card, ctype, thisRemove); } } } @@ -660,7 +660,7 @@ public class AiCostDecision extends CostDecisionMakerBase { int over = Math.min(e.getValue(), c - toRemove); if (over > 0) { toRemove += over; - table.put(crd, e.getKey(), over); + table.put(null, crd, e.getKey(), over); } } } @@ -690,7 +690,7 @@ public class AiCostDecision extends CostDecisionMakerBase { int over = Math.min(e.getValue(), c - toRemove); if (over > 0) { toRemove += over; - table.put(crd, e.getKey(), over); + table.put(null, crd, e.getKey(), over); } } } @@ -730,7 +730,7 @@ public class AiCostDecision extends CostDecisionMakerBase { int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove); if (over > 0) { toRemove += over; - table.put(crd, CounterType.get(CounterEnumType.QUEST), over); + table.put(null, crd, CounterType.get(CounterEnumType.QUEST), over); } } } @@ -754,7 +754,7 @@ public class AiCostDecision extends CostDecisionMakerBase { int thisRemove = Math.min(card.getCounters(cost.counter), c - toRemove); if (thisRemove > 0) { toRemove += thisRemove; - table.put(card, cost.counter, thisRemove); + table.put(null, card, cost.counter, thisRemove); } } } @@ -768,7 +768,7 @@ public class AiCostDecision extends CostDecisionMakerBase { int thisRemove = Math.min(e.getValue(), c - toRemove); if (thisRemove > 0) { toRemove += thisRemove; - table.put(card, e.getKey(), thisRemove); + table.put(null, card, e.getKey(), thisRemove); } } } diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index 3b93568fde8..f0ab86412eb 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -163,12 +163,16 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView, * @return a boolean. */ public boolean matchesValid(final Object o, final String[] valids, final Card srcCard) { + return matchesValid(o, valids, srcCard, srcCard.getController()); + } + + public boolean matchesValid(final Object o, final String[] valids, final Card srcCard, final Player srcPlayer) { if (o instanceof GameObject) { final GameObject c = (GameObject) o; - return c.isValid(valids, srcCard.getController(), srcCard, this); + return c.isValid(valids, srcPlayer, srcCard, this); } else if (o instanceof Iterable) { for (Object o2 : (Iterable)o) { - if (matchesValid(o2, valids, srcCard)) { + if (matchesValid(o2, valids, srcCard, srcPlayer)) { return true; } } diff --git a/forge-game/src/main/java/forge/game/GameEntityCounterTable.java b/forge-game/src/main/java/forge/game/GameEntityCounterTable.java index cea3fda90cc..11970f36fa7 100644 --- a/forge-game/src/main/java/forge/game/GameEntityCounterTable.java +++ b/forge-game/src/main/java/forge/game/GameEntityCounterTable.java @@ -2,6 +2,9 @@ package forge.game; import java.util.Map; +import org.apache.commons.lang3.ObjectUtils; + +import com.google.common.base.Optional; import com.google.common.collect.ForwardingTable; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Maps; @@ -10,38 +13,46 @@ import com.google.common.collect.Table; import forge.game.ability.AbilityKey; import forge.game.card.Card; import forge.game.card.CounterType; +import forge.game.player.Player; import forge.game.trigger.TriggerType; -public class GameEntityCounterTable extends ForwardingTable { +public class GameEntityCounterTable extends ForwardingTable, GameEntity, Map> { - private Table dataMap = HashBasedTable.create(); + private Table, GameEntity, Map> dataMap = HashBasedTable.create(); /* * (non-Javadoc) * @see com.google.common.collect.ForwardingTable#delegate() */ @Override - protected Table delegate() { + protected Table, GameEntity, Map> delegate() { return dataMap; } - /* (non-Javadoc) - * @see com.google.common.collect.ForwardingTable#put(java.lang.Object, java.lang.Object, java.lang.Object) - */ - @Override - public Integer put(GameEntity rowKey, CounterType columnKey, Integer value) { - return super.put(rowKey, columnKey, get(rowKey, columnKey) + value); - } - - - @Override - public Integer get(Object rowKey, Object columnKey) { - if (!contains(rowKey, columnKey)) { - return 0; // helper to not return null value + public Integer put(Player putter, GameEntity object, CounterType type, Integer value) { + Optional o = Optional.fromNullable(putter); + Map map = get(o, object); + if (map == null) { + map = Maps.newHashMap(); + put(o, object, map); } - return super.get(rowKey, columnKey); + return map.put(type, ObjectUtils.firstNonNull(map.get(type), 0) + value); } + public int get(Player putter, GameEntity object, CounterType type) { + Optional o = Optional.fromNullable(putter); + return ObjectUtils.firstNonNull(get(o, object).get(type), 0); + } + + public int totalValues() { + int result = 0; + for (Map m : values()) { + for (Integer i : m.values()) { + result += i; + } + } + return result; + } /* * returns the counters that can still be removed from game entity @@ -52,7 +63,7 @@ public class GameEntityCounterTable extends ForwardingTable alreadyRemoved = row(ge); + Map alreadyRemoved = column(ge).get(Optional.absent()); for (Map.Entry e : ge.getCounters().entrySet()) { Integer rest = e.getValue() - (alreadyRemoved.containsKey(e.getKey()) ? alreadyRemoved.get(e.getKey()) : 0); if (rest > 0) { @@ -65,19 +76,34 @@ public class GameEntityCounterTable extends ForwardingTable filterTable(CounterType type, String valid, Card host, CardTraitBase sa) { Map result = Maps.newHashMap(); - for (Map.Entry e : column(type).entrySet()) { - if (e.getValue() > 0 && e.getKey().isValid(valid, host.getController(), host, sa)) { - result.put(e.getKey(), e.getValue()); + for (Map.Entry, Map>> gm : columnMap().entrySet()) { + if (gm.getKey().isValid(valid, host.getController(), host, sa)) { + for (Map cm : gm.getValue().values()) { + Integer old = ObjectUtils.firstNonNull(result.get(gm.getKey()), 0); + Integer v = ObjectUtils.firstNonNull(cm.get(type), 0); + result.put(gm.getKey(), old + v); + } } } return result; } public void triggerCountersPutAll(final Game game) { - if (!isEmpty()) { - final Map runParams = AbilityKey.newMap(); - runParams.put(AbilityKey.Objects, this); - game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false); + if (isEmpty()) { + return; } + for (Cell, GameEntity, Map> c : cellSet()) { + if (c.getValue().isEmpty()) { + continue; + } + final Map runParams = AbilityKey.newMap(); + runParams.put(AbilityKey.Source, c.getRowKey().get()); + runParams.put(AbilityKey.Object, c.getColumnKey()); + runParams.put(AbilityKey.CounterMap, c.getValue()); + game.getTriggerHandler().runTrigger(TriggerType.CounterPlayerAddedAll, runParams, false); + } + final Map runParams = AbilityKey.newMap(); + runParams.put(AbilityKey.Objects, this); + game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false); } } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityKey.java b/forge-game/src/main/java/forge/game/ability/AbilityKey.java index b3f4f97dd62..a3d3089fa63 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityKey.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityKey.java @@ -38,6 +38,7 @@ public enum AbilityKey { CounterAmount("CounterAmount"), CounteredSA("CounteredSA"), CounterNum("CounterNum"), + CounterMap("CounterMap"), CounterTable("CounterTable"), CounterType("CounterType"), Crew("Crew"), diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java index dd2374a8976..0a4ac612f04 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java @@ -399,19 +399,9 @@ public class CountersPutEffect extends SpellAbilityEffect { final Game game = card.getGame(); final Player activator = sa.getActivatingPlayer(); - CounterType counterType = null; String amount = sa.getParamOrDefault("CounterNum", "1"); boolean rememberAmount = sa.hasParam("RememberAmount"); - if (!sa.hasParam("EachExistingCounter") && !sa.hasParam("EachFromSource") && !sa.hasParam("SharedKeywords")) { - try { - counterType = AbilityUtils.getCounterType(sa.getParam("CounterType"), sa); - } catch (Exception e) { - System.out.println("Counter type doesn't match, nor does an SVar exist with the type name."); - return; - } - } - Player placer = activator; if (sa.hasParam("Placer")) { final String pstr = sa.getParam("Placer"); @@ -422,7 +412,13 @@ public class CountersPutEffect extends SpellAbilityEffect { GameEntityCounterTable table = new GameEntityCounterTable(); - if (sa.hasParam("SharedKeywords")) { + if (sa.hasParam("TriggeredCounterMap")) { + @SuppressWarnings("unchecked") + Map counterMap = (Map) sa.getTriggeringObject(AbilityKey.CounterMap); + for (Map.Entry e : counterMap.entrySet()) { + resolvePerType(sa, placer, e.getKey(), e.getValue(), table); + } + } else if (sa.hasParam("SharedKeywords")) { List keywords = Arrays.asList(sa.getParam("SharedKeywords").split(" & ")); List zones = ZoneType.listValueOf(sa.getParam("SharedKeywordsZone")); String[] restrictions = sa.hasParam("SharedRestrictions") ? sa.getParam("SharedRestrictions").split(",") : new String[]{"Card"}; @@ -431,13 +427,19 @@ public class CountersPutEffect extends SpellAbilityEffect { resolvePerType(sa, placer, CounterType.getType(k), counterAmount, table); } } else { + CounterType counterType = null; + if (!sa.hasParam("EachExistingCounter") && !sa.hasParam("EachFromSource")) { + try { + counterType = AbilityUtils.getCounterType(sa.getParam("CounterType"), sa); + } catch (Exception e) { + System.out.println("Counter type doesn't match, nor does an SVar exist with the type name."); + return; + } + } resolvePerType(sa, placer, counterType, counterAmount, table); } - int totalAdded = 0; - for (Integer i : table.values()) { - totalAdded += i; - } + int totalAdded = table.totalValues(); if (totalAdded > 0 && rememberAmount) { // TODO use SpellAbility Remember later diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 87f644cce74..9419efea9b4 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -1482,7 +1482,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { addCounterTimestamp(counterType); } if (table != null) { - table.put(this, counterType, addAmount); + table.put(source, this, counterType, addAmount); } return addAmount; } diff --git a/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java b/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java index ab780854b28..83a94d48073 100644 --- a/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java +++ b/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java @@ -17,7 +17,10 @@ */ package forge.game.cost; -import com.google.common.collect.Table; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.common.base.Optional; import forge.game.GameEntity; import forge.game.ability.AbilityUtils; @@ -115,11 +118,13 @@ public class CostRemoveAnyCounter extends CostPart { final Card source = ability.getHostCard(); int removed = 0; - for (Table.Cell cell : decision.counterTable.cellSet()) { - removed += cell.getValue(); - cell.getRowKey().subtractCounter(cell.getColumnKey(), cell.getValue()); - if (cell.getRowKey() instanceof Card) { - cell.getRowKey().getGame().updateLastStateForCard((Card) cell.getRowKey()); + for (Entry> e : decision.counterTable.row(Optional.absent()).entrySet()) { + for (Entry v : e.getValue().entrySet()) { + removed += v.getValue(); + e.getKey().subtractCounter(v.getKey(), v.getValue()); + } + if (e.getKey() instanceof Card) { + e.getKey().getGame().updateLastStateForCard((Card) e.getKey()); } } diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 9523f64478c..33e1211bf69 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -1030,7 +1030,7 @@ public class Player extends GameEntity implements Comparable { getGame().getTriggerHandler().runTrigger(TriggerType.CounterAddedOnce, AbilityKey.newMap(runParams), false); } if (table != null) { - table.put(this, counterType, addAmount); + table.put(source, this, counterType, addAmount); } return addAmount; } diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerCounterPlayerAddedAll.java b/forge-game/src/main/java/forge/game/trigger/TriggerCounterPlayerAddedAll.java new file mode 100644 index 00000000000..60674ad0c55 --- /dev/null +++ b/forge-game/src/main/java/forge/game/trigger/TriggerCounterPlayerAddedAll.java @@ -0,0 +1,49 @@ +package forge.game.trigger; + +import java.util.Map; + +import forge.game.ability.AbilityKey; +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.util.Localizer; + +public class TriggerCounterPlayerAddedAll extends Trigger { + + public TriggerCounterPlayerAddedAll(Map params, Card host, boolean intrinsic) { + super(params, host, intrinsic); + } + + @Override + public boolean performTest(Map runParams) { + if (!matchesValidParam("ValidSource", runParams.get(AbilityKey.Source))) { + return false; + } + if (!matchesValidParam("ValidObject", runParams.get(AbilityKey.Object))) { + return false; + } + if (hasParam("ValidObjectToSource")) { + if (!matchesValid(runParams.get(AbilityKey.Object), getParam("ValidObjectToSource").split(","), getHostCard(), + (Player)runParams.get(AbilityKey.Source))) { + return false; + } + } + return true; + } + + @Override + public void setTriggeringObjects(SpellAbility sa, Map runParams) { + sa.setTriggeringObjectsFrom(runParams, AbilityKey.Source, AbilityKey.Player, AbilityKey.Object, AbilityKey.CounterMap); + + } + + @Override + public String getImportantStackObjects(SpellAbility sa) { + StringBuilder sb = new StringBuilder(); + sb.append(Localizer.getInstance().getMessage("lblAddedOnce")).append(": "); + sb.append(sa.getTriggeringObject(AbilityKey.Player)).append(": "); + sb.append(sa.getTriggeringObject(AbilityKey.Object)); + return sb.toString(); + } + +} 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 affa61ccdb4..1bc132d9836 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerType.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerType.java @@ -39,6 +39,7 @@ public enum TriggerType { Clashed(TriggerClashed.class), CounterAdded(TriggerCounterAdded.class), CounterAddedOnce(TriggerCounterAddedOnce.class), + CounterPlayerAddedAll(TriggerCounterPlayerAddedAll.class), CounterAddedAll(TriggerCounterAddedAll.class), Countered(TriggerCountered.class), CounterRemoved(TriggerCounterRemoved.class), diff --git a/forge-gui/res/cardsfolder/upcoming/bold_plagiarist.txt b/forge-gui/res/cardsfolder/upcoming/bold_plagiarist.txt new file mode 100644 index 00000000000..0755c4d0f41 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bold_plagiarist.txt @@ -0,0 +1,8 @@ +Name:Bold Plagiarist +ManaCost:3 B +Types:Creature Vampire Rogue +PT:2/2 +K:Flash +T:Mode$ CounterPlayerAddedAll | ValidSource$ Opponent | ValidObjectToSource$ Creature.YouCtrl | Execute$ TrigCounter | TriggerDescription$ Whenever an opponent puts one or more counters on a creature they control, they put the same number and kind of counters on CARDNAME. +SVar:TrigCounter:DB$ PutCounter | Defined$ Self | Placer$ TriggeredSource | TriggeredCounterMap$ True +Oracle:Flash\nWhenever an opponent puts one or more counters on a creature they control, they put the same number and kind of counters on Bold Plagiarist. diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index c7e34a9aa4d..8484c7d5bdb 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -861,11 +861,11 @@ public class HumanCostDecision extends CostDecisionMakerBase { return false; } - if (c.getCounters(cType) <= counterTable.get(c, cType)) { + if (c.getCounters(cType) <= counterTable.get(null, c, cType)) { return false; } - counterTable.put(c, cType, 1); + counterTable.put(null, c, cType, 1); onSelectStateChanged(c, true); refresh(); @@ -878,13 +878,13 @@ public class HumanCostDecision extends CostDecisionMakerBase { return null; } if (counterType != null) { - if (c.getCounters(counterType) <= counterTable.get(c, counterType)) { + if (c.getCounters(counterType) <= counterTable.get(null, c, counterType)) { return null; } } else { boolean found = false; for (Map.Entry e : c.getCounters().entrySet()) { - if (e.getValue() > counterTable.get(c, e.getKey())) { + if (e.getValue() > counterTable.get(null, c, e.getKey())) { found = true; break; } @@ -915,13 +915,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { } private int getDistibutedCounters() { - int sum = 0; - - for (Integer v : this.counterTable.values()) { - sum += v; - } - - return sum; + return counterTable.totalValues(); } protected final boolean isValidChoice(final GameEntity choice) { @@ -934,7 +928,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public Collection getSelected() { - return counterTable.rowKeySet(); + return counterTable.columnKeySet(); } }