From 473a81e13c45964520b5b57b3b9b0f18b48538e4 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 4 Sep 2021 20:34:32 +0400 Subject: [PATCH] * Mana pay to cast - fixed random values in games with AI (example: Marath, Will of the Wild, see #8204); --- .../single/c13/MarathWillOfTheWildTest.java | 29 ++++++++++++++++ Mage/src/main/java/mage/watchers/Watcher.java | 11 ++++++ .../common/ManaPaidSourceWatcher.java | 34 ++++++++++++++++++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c13/MarathWillOfTheWildTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c13/MarathWillOfTheWildTest.java index e7035c2143..b04a3ffb38 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/c13/MarathWillOfTheWildTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c13/MarathWillOfTheWildTest.java @@ -3,6 +3,9 @@ package org.mage.test.cards.single.c13; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; +import mage.game.permanent.Permanent; +import mage.watchers.common.ManaPaidSourceWatcher; +import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestCommanderDuelBase; @@ -31,6 +34,32 @@ public class MarathWillOfTheWildTest extends CardTestCommanderDuelBase { waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); checkPermanentCounters("after first cast must have 3x counters", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Marath, Will of the Wild", CounterType.P1P1, 3); + // COPY WATCHER testing (e.g. rollback compatible) + runCode("test copy watcher", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + ManaPaidSourceWatcher watcher = game.getState().getWatcher(ManaPaidSourceWatcher.class); + Permanent perm = game.getBattlefield().getAllPermanents() + .stream() + .filter(p -> p.getName().equals("Marath, Will of the Wild")) + .findFirst() + .orElse(null); + Assert.assertNotNull(perm); + + // check correct copy + int before = watcher.testsReturnTotal(perm); + Assert.assertEquals("original must have 3x", 3, before); + ManaPaidSourceWatcher copiedWatcher = watcher.copy(); + int after = copiedWatcher.testsReturnTotal(perm); + Assert.assertEquals("copied must have 3x", before, after); + + // check correct refs and changes + // simulate ai games (changes in copied watcher must not touch original watcher) + copiedWatcher.testsIncrementManaAmount(game, perm); + int afterChangeCopied = copiedWatcher.testsReturnTotal(perm); + int afterChangeOriginal = watcher.testsReturnTotal(perm); + Assert.assertEquals("after change, copied", 4, afterChangeCopied); + Assert.assertEquals("after change, original", 3, afterChangeOriginal); + }); + // kill castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Marath, Will of the Wild"); setChoice(playerA, true); // move to command diff --git a/Mage/src/main/java/mage/watchers/Watcher.java b/Mage/src/main/java/mage/watchers/Watcher.java index 7b5db9859e..a091867b29 100644 --- a/Mage/src/main/java/mage/watchers/Watcher.java +++ b/Mage/src/main/java/mage/watchers/Watcher.java @@ -5,6 +5,7 @@ import mage.constants.WatcherScope; import mage.game.Game; import mage.game.events.GameEvent; import mage.players.PlayerList; +import mage.util.Copyable; import org.apache.log4j.Logger; import java.io.Serializable; @@ -142,6 +143,14 @@ public abstract class Watcher implements Serializable { Cards list = e.getValue().copy(); target.put(e.getKey(), list); } + } else if (Arrays.stream(((Class) valueType).getInterfaces()).anyMatch(c -> c.equals(Copyable.class))) { + Map source = (Map) field.get(this); + Map target = (Map) field.get(watcher); + target.clear(); + for (Map.Entry e : source.entrySet()) { + Copyable object = (Copyable) e.getValue().copy(); + target.put(e.getKey(), object); + } } else if (valueType.getTypeName().contains("List")) { Map> source = (Map>) field.get(this); Map> target = (Map>) field.get(watcher); @@ -161,6 +170,8 @@ public abstract class Watcher implements Serializable { target.put(e.getKey(), map); } } else { + // TODO: add additional tests to find unsupported watcher data + ((Map) field.get(watcher)).putAll((Map) field.get(this)); } } else if (field.getType() == List.class) { diff --git a/Mage/src/main/java/mage/watchers/common/ManaPaidSourceWatcher.java b/Mage/src/main/java/mage/watchers/common/ManaPaidSourceWatcher.java index 04535667e2..d77afec542 100644 --- a/Mage/src/main/java/mage/watchers/common/ManaPaidSourceWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/ManaPaidSourceWatcher.java @@ -11,6 +11,7 @@ import mage.game.events.GameEvent; import mage.game.events.ManaPaidEvent; import mage.game.events.ZoneChangeEvent; import mage.game.stack.Spell; +import mage.util.Copyable; import mage.watchers.Watcher; import java.io.Serializable; @@ -25,7 +26,8 @@ import java.util.UUID; */ public class ManaPaidSourceWatcher extends Watcher { - private static final class ManaPaidTracker implements Serializable { + private static final class ManaPaidTracker implements Serializable, Copyable { + private int total = 0; private int whiteSnow = 0; private int blueSnow = 0; @@ -35,6 +37,26 @@ public class ManaPaidSourceWatcher extends Watcher { private int colorlessSnow = 0; private int treasure = 0; + private ManaPaidTracker() { + super(); + } + + private ManaPaidTracker(final ManaPaidTracker tracker) { + this.total = tracker.total; + this.whiteSnow = tracker.whiteSnow; + this.blueSnow = tracker.blueSnow; + this.blackSnow = tracker.blackSnow; + this.redSnow = tracker.redSnow; + this.greenSnow = tracker.greenSnow; + this.colorlessSnow = tracker.colorlessSnow; + this.treasure = tracker.treasure; + } + + @Override + public ManaPaidTracker copy() { + return new ManaPaidTracker(this); + } + private void increment(MageObject sourceObject, ManaType manaType, Game game) { total++; if (sourceObject.hasSubtype(SubType.TREASURE, game)) { @@ -129,4 +151,14 @@ public class ManaPaidSourceWatcher extends Watcher { ManaPaidSourceWatcher watcher = game.getState().getWatcher(ManaPaidSourceWatcher.class); return watcher != null && watcher.manaMap.getOrDefault(spell.getSpellAbility().getId(), emptyTracker).checkSnowColor(spell, game); } + + public void testsIncrementManaAmount(Game game, MageObject mageObject) { + // for tests only (logic here: change data in tracker like real event do) + this.manaMap.getOrDefault(mageObject.getId(), null).increment(mageObject, ManaType.RED, game); + } + + public int testsReturnTotal(MageObject mageObject) { + // for tests only + return this.manaMap.getOrDefault(mageObject.getId(), null).total; + } }