From 66f3288c13dac9b6f3b62b669328549ad88dd42a Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Mon, 1 Jun 2026 07:55:17 +0200 Subject: [PATCH] ManaPool: use Multiset for floatingMana --- .../src/main/java/forge/ai/GameState.java | 9 +- .../game/ability/effects/ManaEffect.java | 4 +- .../main/java/forge/game/mana/ManaPool.java | 152 +++++++++--------- .../java/forge/game/player/PlayerView.java | 7 +- 4 files changed, 82 insertions(+), 90 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index b18ea26cc70..907b17f23d2 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -9,7 +9,6 @@ import forge.card.CardStateName; import forge.card.GamePieceType; import forge.card.MagicColor; -import forge.card.mana.ManaAtom; import forge.game.Game; import forge.game.GameEntity; import forge.game.ability.AbilityFactory; @@ -678,11 +677,9 @@ protected void applyGameOnThread(final Game game) { private String processManaPool(ManaPool manaPool) { StringBuilder mana = new StringBuilder(); - for (final byte c : ManaAtom.MANATYPES) { - int amount = manaPool.getAmountOfColor(c); - for (int i = 0; i < amount; i++) { - mana.append(MagicColor.toShortString(c)).append(" "); - } + + for (final Map.Entry e : manaPool.getView().entrySet()) { + mana.append(StringUtils.repeat(MagicColor.toShortString(e.getKey()) + " ", e.getValue())); } return mana.toString().trim(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java index 85f0a0e9fdf..9c401087fa7 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java @@ -254,8 +254,8 @@ public static void handleSpecialMana(Player chooser, AbilityManaPart abMana, Spe abMana.setExpressChoice(sb.toString().trim()); } else if (type.startsWith("DoubleManaInPool")) { StringBuilder sb = new StringBuilder(); - for (byte color : ManaAtom.MANATYPES) { - sb.append(StringUtils.repeat(MagicColor.toShortString(color) + " ", chooser.getManaPool().getAmountOfColor(color))); + for (Map.Entry e : chooser.getManaPool().getView().entrySet()) { + sb.append(StringUtils.repeat(MagicColor.toShortString(e.getKey()) + " ", e.getValue())); } abMana.setExpressChoice(sb.toString().trim()); } 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 6aec0475509..75eb6ff297f 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaPool.java +++ b/forge-game/src/main/java/forge/game/mana/ManaPool.java @@ -17,8 +17,11 @@ */ package forge.game.mana; -import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.HashMultiset; import com.google.common.collect.Lists; +import com.google.common.collect.Multiset; +import com.google.common.collect.Multisets; +import com.google.common.collect.Sets; import forge.card.MagicColor; import forge.card.mana.ManaAtom; @@ -37,6 +40,8 @@ import forge.game.staticability.StaticAbilityUnspentMana; import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; /** *

@@ -48,7 +53,8 @@ */ public class ManaPool extends ManaConversionMatrix implements Iterable { private final Player owner; - private final ArrayListMultimap floatingMana = ArrayListMultimap.create(); + + private final Multiset floatingMana = HashMultiset.create(); public ManaPool(final Player player) { owner = player; @@ -56,22 +62,28 @@ public ManaPool(final Player player) { } public final int getAmountOfColor(final byte color) { - Collection ofColor = floatingMana.get(color); - return ofColor == null ? 0 : ofColor.size(); + return Multisets.filter(this.floatingMana, m -> m.getColor() == color).size(); } public void addManaNoEvent(final Mana mana) { - floatingMana.put(mana.getColor(), mana); + this.floatingMana.add(mana); } 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) { - floatingMana.put(m.getColor(), m); - colors.add(MagicColor.Color.fromByte(m.getColor())); + Set colors; + + if (manaList instanceof Multiset manaSet) { + colors = manaSet.elementSet().stream().map(m -> MagicColor.Color.fromByte(m.getColor())).collect(Collectors.toSet()); + floatingMana.addAll(manaSet); + } else { + colors = EnumSet.noneOf(MagicColor.Color.class); + for (final Mana m : manaList) { + floatingMana.add(m); + colors.add(MagicColor.Color.fromByte(m.getColor())); + } } owner.updateManaForView(); owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Added, colors)); @@ -94,10 +106,8 @@ public final boolean willManaBeLostAtEndOfPhase() { return false; } - int safeMana = 0; - for (final byte c : StaticAbilityUnspentMana.getManaToKeep(owner)) { - safeMana += getAmountOfColor(c); - } + Collection safeColors = StaticAbilityUnspentMana.getManaToKeep(owner); + int safeMana = Multisets.filter(this.floatingMana, m -> safeColors.contains(m.getColor())).size(); // TODO isPersistentMana @@ -135,36 +145,39 @@ public final List clearPool(boolean isEndOfPhase) { } - final List keys = Lists.newArrayList(floatingMana.keySet()); + final Set safeKeys = Sets.newHashSet(); + if (isEndOfPhase) { - keys.removeAll(StaticAbilityUnspentMana.getManaToKeep(owner)); + safeKeys.addAll(StaticAbilityUnspentMana.getManaToKeep(owner)); } if (convertTo != null) { - keys.remove(convertTo); + safeKeys.add(convertTo); } - for (Byte b : keys) { - Collection cm = floatingMana.get(b); - final List pMana = Lists.newArrayList(); - if (isEndOfPhase && !owner.getGame().getPhaseHandler().is(PhaseType.CLEANUP)) { - for (final Mana mana : cm) { - if (mana.isPersistentMana()) { - pMana.add(mana); - } - if (mana.isCombatMana() && !owner.getGame().getPhaseHandler().is(PhaseType.COMBAT_END)) { - pMana.add(mana); - } + Predicate retain = m -> safeKeys.contains(m.getColor()); + + if (isEndOfPhase && !owner.getGame().getPhaseHandler().is(PhaseType.CLEANUP)) { + retain.or(m -> { + if (m.isPersistentMana()) { + return true; } + if (m.isCombatMana() && !owner.getGame().getPhaseHandler().is(PhaseType.COMBAT_END)) { + return true; + } + return false; + }); + } + + Multiset convertedMana = null; + if (convertTo != null) { + convertedMana = HashMultiset.create(); + for (Multiset.Entry e : Multisets.filter(floatingMana, Predicate.not(retain)::test).entrySet()) { + convertedMana.add(e.getElement().convertColor(convertTo), e.getCount()); } - cm.removeAll(pMana); - if (convertTo != null) { - convertManaColor(b, convertTo); - cm.addAll(pMana); - } else { - cleared.addAll(cm); - cm.clear(); - floatingMana.putAll(b, pMana); - } + } + floatingMana.removeIf(Predicate.not(retain)::test); + if (convertedMana != null) { + floatingMana.addAll(convertedMana); } owner.updateManaForView(); @@ -172,19 +185,8 @@ public final List clearPool(boolean isEndOfPhase) { return cleared; } - private void convertManaColor(final byte originalColor, final byte toColor) { - List convert = Lists.newArrayList(); - Collection cm = floatingMana.get(originalColor); - for (Mana m : cm) { - convert.add(m.convertColor(toColor)); - } - cm.clear(); - floatingMana.putAll(toColor, convert); - owner.updateManaForView(); - } - public boolean removeManaNoEvent(final Mana mana) { - return floatingMana.remove(mana.getColor(), mana); + return floatingMana.remove(mana); } public boolean removeMana(Mana... manaList) { @@ -192,10 +194,16 @@ public boolean removeMana(Mana... 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())); + Set colors; + if (manaList instanceof Multiset manaSet) { + colors = manaSet.elementSet().stream().map(m -> MagicColor.Color.fromByte(m.getColor())).collect(Collectors.toSet()); + Multisets.removeOccurrences(floatingMana, manaSet); + } else { + colors = EnumSet.noneOf(MagicColor.Color.class); + for (Mana m : manaList) { + if (floatingMana.remove(m)) { + colors.add(MagicColor.Color.fromByte(m.getColor())); + } } } owner.updateManaForView(); @@ -224,9 +232,8 @@ public final void payManaFromAbility(final SpellAbility saPaidFor, ManaCostBeing public boolean tryPayCostWithColor(byte colorCode, SpellAbility saPaidFor, ManaCostBeingPaid manaCost, List manaSpentToPay) { Mana manaFound = null; - Collection cm = floatingMana.get(colorCode); - for (final Mana mana : cm) { + for (final Mana mana : Multisets.filter(this.floatingMana, m -> m.getColor() == colorCode)) { if (!mana.meetsManaRestrictions(saPaidFor)) { continue; } @@ -265,7 +272,15 @@ public final boolean isEmpty() { } public final int totalMana() { - return floatingMana.values().size(); + return floatingMana.size(); + } + + public final Map getView() { + return floatingMana.entrySet().stream().collect(Collectors.groupingBy(e -> e.getElement().getColor(), Collectors.summingInt(Multiset.Entry::getCount))); + } + + public final Multiset filter(final Predicate predicate) { + return Multisets.filter(this.floatingMana, predicate::test); } //Account for mana part of ability when undoing it @@ -277,29 +292,14 @@ public boolean accountFor(final AbilityManaPart ma) { return false; } - final List removeFloating = Lists.newArrayList(); - - boolean manaNotAccountedFor = false; // loop over mana produced by mana ability - for (Mana mana : ma.getLastManaProduced()) { - Collection poolLane = floatingMana.get(mana.getColor()); - - if (poolLane != null && poolLane.contains(mana)) { - removeFloating.add(mana); - } else { - manaNotAccountedFor = true; - break; - } - } + Multiset produced = HashMultiset.create(ma.getLastManaProduced()); - // When is it legitimate for all the mana not to be accountable? - // TODO: Does this condition really indicate an bug in Forge? - if (manaNotAccountedFor) { - return false; + if (Multisets.containsOccurrences(floatingMana, produced)) { + removeMana(produced); + return true; } - - removeMana(removeFloating); - return true; + return false; } public void refundMana(List manaSpent) { @@ -363,7 +363,7 @@ public boolean payManaCostFromPool(final ManaCostBeingPaid cost, final SpellAbil @Override public Iterator iterator() { - return floatingMana.values().iterator(); + return floatingMana.iterator(); } } diff --git a/forge-game/src/main/java/forge/game/player/PlayerView.java b/forge-game/src/main/java/forge/game/player/PlayerView.java index dbad76b160e..f25613ea8dd 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerView.java +++ b/forge-game/src/main/java/forge/game/player/PlayerView.java @@ -6,7 +6,6 @@ import forge.LobbyPlayer; import forge.card.CardType; import forge.card.MagicColor; -import forge.card.mana.ManaAtom; import forge.game.GameEntityView; import forge.game.card.Card; import forge.game.card.CardView; @@ -500,11 +499,7 @@ private Map getMana() { return get(TrackableProperty.Mana); } void updateMana(Player p) { - Map mana = new HashMap<>(); - for (byte b : ManaAtom.MANATYPES) { - mana.put(b, p.getManaPool().getAmountOfColor(b)); - } - set(TrackableProperty.Mana, mana); + set(TrackableProperty.Mana, p.getManaPool().getView()); } private List getDetailsList() {