From 23d0b05547f65f9bb1f98e3258cd108941f8a262 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 28 Jul 2024 21:34:25 +0200 Subject: [PATCH] SacrificeOnce Trigger --- .../src/main/java/forge/game/GameAction.java | 43 ++++++++++----- .../game/ability/effects/BalanceEffect.java | 6 +-- .../ability/effects/SacrificeAllEffect.java | 14 +++-- .../game/ability/effects/SacrificeEffect.java | 45 +++++++++------- .../main/java/forge/game/cost/CostForage.java | 4 +- .../java/forge/game/cost/CostPayment.java | 5 +- .../java/forge/game/cost/CostSacrifice.java | 10 ++-- .../main/java/forge/game/player/Player.java | 3 +- .../game/trigger/TriggerSacrificedOnce.java | 53 +++++++++++++++++++ .../java/forge/game/trigger/TriggerType.java | 1 + .../src/main/java/forge/player/HumanPlay.java | 4 +- 11 files changed, 134 insertions(+), 54 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/trigger/TriggerSacrificedOnce.java diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index da70aa8ce97..18da3c7ddfc 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -1442,10 +1442,7 @@ public class GameAction { sacrificeList = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, sacrificeList, ZoneType.Graveyard, null); orderedSacrificeList = true; } - for (Card c : sacrificeList) { - c.updateWasDestroyed(true); - sacrifice(c, null, true, mapParams); - } + sacrifice(sacrificeList, null, true, mapParams); setHoldCheckingStaticAbilities(false); table.triggerChangesZoneAll(game, null); @@ -1891,15 +1888,37 @@ public class GameAction { return true; } - public final Card sacrifice(final Card c, final SpellAbility source, final boolean effect, Map params) { - if (!c.canBeSacrificedBy(source, effect)) { - return null; + public final CardCollection sacrifice(final Iterable list, final SpellAbility source, final boolean effect, Map params) { + Multimap lki = MultimapBuilder.hashKeys().arrayListValues().build(); + CardCollection result = new CardCollection(); + for (Card c : list) { + if (c == null) { + continue; + } + + if (!c.canBeSacrificedBy(source, effect)) { + continue; + } + + Card lkiCopy = ((CardCollection) params.get(AbilityKey.LastStateBattlefield)).get(c); + c.getController().addSacrificedThisTurn(lkiCopy, source); + lki.put(c.getController(), lkiCopy); + + c.updateWasDestroyed(true); + + Card changed = sacrificeDestroy(c, source, params); + if (changed != null) { + result.add(changed); + } } - - c.getController().addSacrificedThisTurn(c, source); - - c.updateWasDestroyed(true); - return sacrificeDestroy(c, source, params); + for (Map.Entry> e : lki.asMap().entrySet()) { + // Run triggers + final Map runParams = AbilityKey.mapFromPlayer(e.getKey()); + runParams.put(AbilityKey.Cards, new CardCollection(e.getValue())); + runParams.put(AbilityKey.Cause, source); + game.getTriggerHandler().runTrigger(TriggerType.SacrificedOnce, runParams, false); + } + return result; } public final boolean destroy(final Card c, final SpellAbility sa, final boolean regenerate, Map params) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java index 77fc7413c51..428d97739fd 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java @@ -60,10 +60,8 @@ public class BalanceEffect extends SpellAbilityEffect { if (zone.equals(ZoneType.Hand)) { discardedMap.put(p, p.getController().chooseCardsToDiscardFrom(p, sa, validCards.get(i), numToBalance, numToBalance)); } else { // Battlefield - for (Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) { - if (null == card) continue; - game.getAction().sacrifice(card, sa, true, params); - } + CardCollectionView list = p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid); + game.getAction().sacrifice(list, sa, true, params); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java index e4bc9bc8b20..6a52efe2477 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java @@ -78,15 +78,13 @@ public class SacrificeAllEffect extends SpellAbilityEffect { Map params = AbilityKey.newMap(); CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa); - for (Card sac : list) { + for (Card sac : game.getAction().sacrifice(list, sa, true, params)) { final Card lKICopy = zoneMovements.getLastStateBattlefield().get(sac); - if (game.getAction().sacrifice(sac, sa, true, params) != null) { - if (remSacrificed) { - host.addRemembered(lKICopy); - } - if (sa.hasParam("ImprintSacrificed")) { - host.addImprintedCard(lKICopy); - } + if (remSacrificed) { + host.addRemembered(lKICopy); + } + if (sa.hasParam("ImprintSacrificed")) { + host.addImprintedCard(lKICopy); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java index e2d282a00a0..6997822971e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java @@ -99,7 +99,7 @@ public class SacrificeEffect extends SpellAbilityEffect { if (host.getController().equals(activator) && game.getZoneOf(host).is(ZoneType.Battlefield)) { if (!optional || activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", host.getName()), null)) { - if (game.getAction().sacrifice(host, sa, true, params) != null && remSacrificed) { + if (game.getAction().sacrifice(new CardCollection(host), sa, true, params) != null && remSacrificed) { host.addRemembered(host); } } @@ -155,25 +155,32 @@ public class SacrificeEffect extends SpellAbilityEffect { choosenToSacrifice = GameActionUtil.orderCardsByTheirOwners(game, choosenToSacrifice, ZoneType.Graveyard, sa); - for (Card sac : choosenToSacrifice) { - Card lKICopy = zoneMovements.getLastStateBattlefield().get(sac); - boolean wasSacrificed = !destroy && game.getAction().sacrifice(sac, sa, true, params) != null; - boolean wasDestroyed = destroy && game.getAction().destroy(sac, sa, true, params); - // Run Devour Trigger - if (devour) { - host.addDevoured(lKICopy); - final Map runParams = AbilityKey.newMap(); - runParams.put(AbilityKey.Devoured, lKICopy); - game.getTriggerHandler().runTrigger(TriggerType.Devoured, runParams, false); + if (destroy) { + for (Card sac : choosenToSacrifice) { + Card lKICopy = zoneMovements.getLastStateBattlefield().get(sac); + if (game.getAction().destroy(sac, sa, true, params) && remSacrificed) { + host.addRemembered(lKICopy); + } } - if (exploit) { - host.addExploited(lKICopy); - final Map runParams = AbilityKey.mapFromCard(host); - runParams.put(AbilityKey.Exploited, lKICopy); - game.getTriggerHandler().runTrigger(TriggerType.Exploited, runParams, false); - } - if ((wasDestroyed || wasSacrificed) && remSacrificed) { - host.addRemembered(lKICopy); + } else { + for (Card sac : game.getAction().sacrifice(choosenToSacrifice, sa, true, params)) { + Card lKICopy = zoneMovements.getLastStateBattlefield().get(sac); + // Run Devour Trigger + if (devour) { + host.addDevoured(lKICopy); + final Map runParams = AbilityKey.newMap(); + runParams.put(AbilityKey.Devoured, lKICopy); + game.getTriggerHandler().runTrigger(TriggerType.Devoured, runParams, false); + } + if (exploit) { + host.addExploited(lKICopy); + final Map runParams = AbilityKey.mapFromCard(host); + runParams.put(AbilityKey.Exploited, lKICopy); + game.getTriggerHandler().runTrigger(TriggerType.Exploited, runParams, false); + } + if (remSacrificed) { + host.addRemembered(lKICopy); + } } } } diff --git a/forge-game/src/main/java/forge/game/cost/CostForage.java b/forge-game/src/main/java/forge/game/cost/CostForage.java index ceb1fc9932f..af2130a0e82 100644 --- a/forge-game/src/main/java/forge/game/cost/CostForage.java +++ b/forge-game/src/main/java/forge/game/cost/CostForage.java @@ -38,7 +38,7 @@ public class CostForage extends CostPartWithList { public String toString() { return "Forage"; } - + @Override protected Card doPayment(Player payer, SpellAbility ability, Card targetCard, final boolean effect) { return null; } @Override @@ -60,7 +60,7 @@ public class CostForage extends CostPartWithList { } else if (targetCards.size() == 1) { Map moveParams = AbilityKey.newMap(); AbilityKey.addCardZoneTableParams(moveParams, table); - CardCollection result = new CardCollection(game.getAction().sacrifice(targetCards.getFirst(), ability, effect, moveParams)); + CardCollection result = game.getAction().sacrifice(targetCards, ability, effect, moveParams); triggerForage(payer); return result; } else { diff --git a/forge-game/src/main/java/forge/game/cost/CostPayment.java b/forge-game/src/main/java/forge/game/cost/CostPayment.java index 779a2dd7e7b..7c4ebf3fd2f 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPayment.java +++ b/forge-game/src/main/java/forge/game/cost/CostPayment.java @@ -32,6 +32,7 @@ import forge.card.mana.ManaCostShard; import forge.game.Game; import forge.game.ability.AbilityKey; import forge.game.card.Card; +import forge.game.card.CardCollection; import forge.game.card.CardZoneTable; import forge.game.player.Player; import forge.game.spellability.SpellAbility; @@ -341,7 +342,7 @@ public class CostPayment extends ManaConversionMatrix { if (test) { sa.resetSacrificedAsOffering(); } else if (costIsPaid) { - game.getAction().sacrifice(offering, sa, false, params); + game.getAction().sacrifice(new CardCollection(offering), sa, false, params); } } if (sa.isEmerge()) { @@ -353,7 +354,7 @@ public class CostPayment extends ManaConversionMatrix { if (test) { sa.resetSacrificedAsEmerge(); } else if (costIsPaid) { - game.getAction().sacrifice(emerge, sa, false, params); + game.getAction().sacrifice(new CardCollection(emerge), sa, false, params); sa.setSacrificedAsEmerge(game.getChangeZoneLKIInfo(emerge)); } } diff --git a/forge-game/src/main/java/forge/game/cost/CostSacrifice.java b/forge-game/src/main/java/forge/game/cost/CostSacrifice.java index 830f49f9ea9..c890a7a6854 100644 --- a/forge-game/src/main/java/forge/game/cost/CostSacrifice.java +++ b/forge-game/src/main/java/forge/game/cost/CostSacrifice.java @@ -162,13 +162,17 @@ public class CostSacrifice extends CostPartWithList { } @Override - protected Card doPayment(Player payer, SpellAbility ability, Card targetCard, final boolean effect) { - final Game game = targetCard.getGame(); + protected Card doPayment(Player payer, SpellAbility ability, Card targetCard, final boolean effect) { return null; } + @Override + protected boolean canPayListAtOnce() { return true; } + @Override + protected CardCollectionView doListPayment(Player payer, SpellAbility ability, CardCollectionView targetCards, final boolean effect) { + final Game game = ability.getHostCard().getGame(); // no table there, it is already handled by CostPartWithList Map moveParams = AbilityKey.newMap(); AbilityKey.addCardZoneTableParams(moveParams, table); - return game.getAction().sacrifice(targetCard, ability, effect, moveParams); + return game.getAction().sacrifice(targetCards, ability, effect, moveParams); } /* (non-Javadoc) 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 ea43786f58b..459c5e88929 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -2295,11 +2295,10 @@ public class Player extends GameEntity implements Comparable { investigatedThisTurn = 0; } - public final void addSacrificedThisTurn(final Card c, final SpellAbility source) { + public final void addSacrificedThisTurn(final Card cpy, final SpellAbility source) { // Play the Sacrifice sound game.fireEvent(new GameEventCardSacrificed()); - final Card cpy = CardCopyService.getLKICopy(c); sacrificedThisTurn.add(cpy); // Run triggers diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerSacrificedOnce.java b/forge-game/src/main/java/forge/game/trigger/TriggerSacrificedOnce.java new file mode 100644 index 00000000000..d543718be28 --- /dev/null +++ b/forge-game/src/main/java/forge/game/trigger/TriggerSacrificedOnce.java @@ -0,0 +1,53 @@ +package forge.game.trigger; + +import java.util.Map; + +import forge.game.ability.AbilityKey; +import forge.game.card.Card; +import forge.game.card.CardCollection; +import forge.game.card.CardLists; +import forge.game.spellability.SpellAbility; +import forge.util.Localizer; + +public class TriggerSacrificedOnce extends Trigger { + + public TriggerSacrificedOnce(Map params, Card host, boolean intrinsic) { + super(params, host, intrinsic); + } + + @Override + public boolean performTest(Map runParams) { + if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Player))) { + return false; + } + if (!matchesValidParam("ValidCause", runParams.get(AbilityKey.Cause))) { + return false; + } + if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Cards))) { + return false; + } + return true; + } + + @Override + public void setTriggeringObjects(SpellAbility sa, Map runParams) { + CardCollection cards = (CardCollection) runParams.get(AbilityKey.Cards); + + if (hasParam("ValidCard")) { + cards = CardLists.getValidCards(cards, getParam("ValidCard"), getHostCard().getController(), getHostCard(), this); + } + + sa.setTriggeringObject(AbilityKey.Cards, cards); + sa.setTriggeringObject(AbilityKey.Amount, cards.size()); + sa.setTriggeringObjectsFrom(runParams, AbilityKey.Player, AbilityKey.Cause); + } + + @Override + public String getImportantStackObjects(SpellAbility sa) { + StringBuilder sb = new StringBuilder(); + sb.append(Localizer.getInstance().getMessage("lblPlayer")).append(": ").append(sa.getTriggeringObject(AbilityKey.Player)).append(", "); + sb.append(Localizer.getInstance().getMessage("lblAmount")).append(": ").append(sa.getTriggeringObject(AbilityKey.Amount)); + 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 869cfaeac6e..c8acdbeea74 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerType.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerType.java @@ -118,6 +118,7 @@ public enum TriggerType { RolledDieOnce(TriggerRolledDieOnce.class), RoomEntered(TriggerEnteredRoom.class), Sacrificed(TriggerSacrificed.class), + SacrificedOnce(TriggerSacrificed.class), Scry(TriggerScry.class), SearchedLibrary(TriggerSearchedLibrary.class), SeekAll(TriggerSeekAll.class), diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index 74ee02fbe09..83fe4ef91d1 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -534,7 +534,7 @@ public class HumanPlay { final Card offering = ability.getSacrificedAsOffering(); offering.setUsedToPay(false); if (!manaInputCancelled) { - game.getAction().sacrifice(offering, ability, false, params); + game.getAction().sacrifice(new CardCollection(offering), ability, false, params); } ability.resetSacrificedAsOffering(); } @@ -542,7 +542,7 @@ public class HumanPlay { final Card emerge = ability.getSacrificedAsEmerge(); emerge.setUsedToPay(false); if (!manaInputCancelled) { - game.getAction().sacrifice(emerge, ability, false, params); + game.getAction().sacrifice(new CardCollection(emerge), ability, false, params); ability.setSacrificedAsEmerge(game.getChangeZoneLKIInfo(emerge)); } else { ability.resetSacrificedAsEmerge();