From d254ccb5192f25395e9e267e080008a37cc06fb0 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 30 May 2026 20:18:24 +0200 Subject: [PATCH 1/2] ManaPool: refactor GameEventManaPool and Mana added as once --- .../main/java/forge/ai/ability/ManaAi.java | 4 +- .../java/forge/ai/simulation/GameCopier.java | 2 +- .../main/java/forge/game/GameSnapshot.java | 2 +- .../game/ability/effects/DrainManaEffect.java | 2 +- .../java/forge/game/cost/CostAddMana.java | 2 +- .../forge/game/event/GameEventManaPool.java | 12 +++-- .../main/java/forge/game/mana/ManaPool.java | 54 +++++++++++-------- .../forge/game/mana/ManaRefundService.java | 9 ++-- .../game/spellability/AbilityManaPart.java | 2 +- 9 files changed, 50 insertions(+), 39 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/ManaAi.java b/forge-ai/src/main/java/forge/ai/ability/ManaAi.java index b35d56b46ae..98551018281 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ManaAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ManaAi.java @@ -263,11 +263,11 @@ public static boolean canRampPool(Player ai, Card source) { if (mp.isEmpty()) { // TODO use color from ability test = new Mana((byte) ManaAtom.COLORLESS, source, null, ai); - mp.addMana(test, false); + mp.addManaNoEvent(test); } boolean lose = mp.willManaBeLostAtEndOfPhase(); if (test != null) { - mp.removeMana(test, false); + mp.removeManaNoEvent(test); } return !lose; } diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java index 7f726f5091d..7f2073f5715 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java @@ -109,7 +109,7 @@ public Game makeCopy(PhaseType advanceToPhase, Player aiPlayer) { newPlayer.setCrankCounter(origPlayer.getCrankCounter()); // TODO creatureAttackedThisTurn for (Mana m : origPlayer.getManaPool()) { - newPlayer.getManaPool().addMana(m, false); + newPlayer.getManaPool().addManaNoEvent(m); } playerMap.put(origPlayer, newPlayer); } diff --git a/forge-game/src/main/java/forge/game/GameSnapshot.java b/forge-game/src/main/java/forge/game/GameSnapshot.java index 716b4367f7d..d74b5ca1428 100644 --- a/forge-game/src/main/java/forge/game/GameSnapshot.java +++ b/forge-game/src/main/java/forge/game/GameSnapshot.java @@ -196,7 +196,7 @@ private void copyManaPool(Player fromPlayer, Player toPlayer) { Game toGame = toPlayer.getGame(); toPlayer.getManaPool().resetPool(); for (Mana m : fromPlayer.getManaPool()) { - toPlayer.getManaPool().addMana(copyMana(m, toGame, toPlayer), false); + toPlayer.getManaPool().addManaNoEvent(copyMana(m, toGame, toPlayer)); } toPlayer.updateManaForView(); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DrainManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DrainManaEffect.java index fc5a98fb5dd..8d831c8bccc 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DrainManaEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DrainManaEffect.java @@ -54,7 +54,7 @@ public void resolve(SpellAbility sa) { } if (sa.hasParam("DrainMana")) { - sa.getActivatingPlayer().getManaPool().add(drained); + sa.getActivatingPlayer().getManaPool().addMana(drained); } if (sa.hasParam("RememberDrainedMana")) { sa.getHostCard().addRemembered(drained.size()); diff --git a/forge-game/src/main/java/forge/game/cost/CostAddMana.java b/forge-game/src/main/java/forge/game/cost/CostAddMana.java index db499ec34cf..96cb48cba08 100644 --- a/forge-game/src/main/java/forge/game/cost/CostAddMana.java +++ b/forge-game/src/main/java/forge/game/cost/CostAddMana.java @@ -94,7 +94,7 @@ public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility sa manaProduced.add(new Mana(attemptedMana, source, null, ai)); } } - ai.getManaPool().add(manaProduced); + ai.getManaPool().addMana(manaProduced); return true; } diff --git a/forge-game/src/main/java/forge/game/event/GameEventManaPool.java b/forge-game/src/main/java/forge/game/event/GameEventManaPool.java index 3f10e426e0e..f2e1792da92 100644 --- a/forge-game/src/main/java/forge/game/event/GameEventManaPool.java +++ b/forge-game/src/main/java/forge/game/event/GameEventManaPool.java @@ -1,14 +1,16 @@ package forge.game.event; -import forge.game.mana.Mana; +import java.util.Set; + +import forge.card.MagicColor; import forge.game.player.Player; import forge.game.player.PlayerView; import forge.util.Lang; -public record GameEventManaPool(PlayerView player, EventValueChangeType mode, byte manaColor) implements GameEvent { +public record GameEventManaPool(PlayerView player, EventValueChangeType mode, Set colors) implements GameEvent { - public GameEventManaPool(Player player, EventValueChangeType mode, Mana mana) { - this(PlayerView.get(player), mode, mana != null ? mana.getColor() : (byte) 0); + public GameEventManaPool(Player player, EventValueChangeType mode, Set colors) { + this(PlayerView.get(player), mode, colors); } @Override @@ -26,7 +28,7 @@ public String toString() { switch (mode) { case Added: case Removed: - sb.append(" - ").append(manaColor); + sb.append(" - ").append(colors); break; default: break; diff --git a/forge-game/src/main/java/forge/game/mana/ManaPool.java b/forge-game/src/main/java/forge/game/mana/ManaPool.java index 549761f3e40..c0483271b8d 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaPool.java +++ b/forge-game/src/main/java/forge/game/mana/ManaPool.java @@ -19,6 +19,8 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Lists; + +import forge.card.MagicColor; import forge.card.mana.ManaAtom; import forge.card.mana.ManaCostShard; import forge.game.Game; @@ -58,21 +60,21 @@ public final int getAmountOfColor(final byte color) { return ofColor == null ? 0 : ofColor.size(); } - public void addMana(final Mana mana) { - addMana(mana, true); - } - public void addMana(final Mana mana, boolean updateView) { + public void addManaNoEvent(final Mana mana) { floatingMana.put(mana.getColor(), mana); - if (updateView) { - owner.updateManaForView(); - owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Added, mana)); - } } - public final void add(final Iterable manaList) { + public final void addMana(final Mana... manaList) { + addMana(Arrays.asList(manaList)); + } + public final void addMana(final Iterable manaList) { + Set colors = EnumSet.noneOf(MagicColor.Color.class); for (final Mana m : manaList) { - addMana(m); + floatingMana.put(m.getColor(), m); + colors.add(MagicColor.Color.fromByte(m.getColor())); } + owner.updateManaForView(); + owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Added, colors)); } /** @@ -182,16 +184,24 @@ private void convertManaColor(final byte originalColor, final byte toColor) { owner.updateManaForView(); } - public boolean removeMana(final Mana mana) { - return removeMana(mana, true); + public boolean removeManaNoEvent(final Mana mana) { + return floatingMana.remove(mana.getColor(), mana); } - public boolean removeMana(final Mana mana, boolean updateView) { - boolean result = floatingMana.remove(mana.getColor(), mana); - if (result && updateView) { - owner.updateManaForView(); - owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Removed, mana)); + + public boolean removeMana(Mana... manaList) { + return removeMana(Arrays.asList(manaList)); + } + + public boolean removeMana(final Iterable manaList) { + Set colors = EnumSet.noneOf(MagicColor.Color.class); + for (Mana m : manaList) { + if (floatingMana.remove(m.getColor(), m)) { + colors.add(MagicColor.Color.fromByte(m.getColor())); + } } - return result; + owner.updateManaForView(); + owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Removed, colors)); + return !colors.isEmpty(); } public final void payManaFromAbility(final SpellAbility saPaidFor, ManaCostBeingPaid manaCost, final SpellAbility saPayment) { @@ -243,7 +253,7 @@ public boolean tryPayCostWithMana(final SpellAbility sa, ManaCostBeingPaid manaC } // only pay mana into manaCost when the Mana could be removed from the Mana pool // if the mana wasn't in the mana pool then something is wrong - if (!removeMana(mana)) { + if (!removeMana(List.of(mana))) { return false; } manaCost.payMana(mana, this); @@ -289,14 +299,12 @@ public boolean accountFor(final AbilityManaPart ma) { return false; } - for (Mana m : removeFloating) { - removeMana(m); - } + removeMana(removeFloating); return true; } public void refundMana(List manaSpent) { - add(manaSpent); + addMana(manaSpent); manaSpent.clear(); } diff --git a/forge-game/src/main/java/forge/game/mana/ManaRefundService.java b/forge-game/src/main/java/forge/game/mana/ManaRefundService.java index 53d75223f46..0ef0812a7e5 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaRefundService.java +++ b/forge-game/src/main/java/forge/game/mana/ManaRefundService.java @@ -9,6 +9,8 @@ import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; public class ManaRefundService { @@ -22,10 +24,9 @@ public void refundManaPaid() { PlayerCollection payers = new PlayerCollection(sa.getActivatingPlayer()); // move non-undoable paying mana back to floating - for (Mana mana : sa.getPayingMana()) { - Player pl = mana.getPlayer(); - pl.getManaPool().addMana(mana); - payers.add(pl); + for (Map.Entry> e : sa.getPayingMana().stream().collect(Collectors.groupingBy(Mana::getPlayer)).entrySet()) { + e.getKey().getManaPool().addMana(e.getValue()); + payers.add(e.getKey()); } sa.getPayingMana().clear(); diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java index 4a4ed7a6b1b..3f170897128 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -193,7 +193,7 @@ public final String produceMana(final String produced, final Player player, Spel } } - manaPool.add(this.lastManaProduced); + manaPool.addMana(this.lastManaProduced); final Map runParams = AbilityKey.mapFromCard(source); runParams.put(AbilityKey.Player, player); From db48f666a4120b09688fa33236ec7c001aee50d8 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 31 May 2026 09:16:01 +0200 Subject: [PATCH 2/2] ~ List.of not needed --- forge-game/src/main/java/forge/game/mana/ManaPool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-game/src/main/java/forge/game/mana/ManaPool.java b/forge-game/src/main/java/forge/game/mana/ManaPool.java index c0483271b8d..ccabbc91606 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaPool.java +++ b/forge-game/src/main/java/forge/game/mana/ManaPool.java @@ -253,7 +253,7 @@ public boolean tryPayCostWithMana(final SpellAbility sa, ManaCostBeingPaid manaC } // only pay mana into manaCost when the Mana could be removed from the Mana pool // if the mana wasn't in the mana pool then something is wrong - if (!removeMana(List.of(mana))) { + if (!removeMana(mana)) { return false; } manaCost.payMana(mana, this);