From 5e71acb082ce2325a2531da599628fd7f04b4c0c Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Fri, 27 Nov 2020 15:40:06 +0100 Subject: [PATCH] Opposition Agent: use timestamp for controlledBy effects --- forge-game/src/main/java/forge/game/Game.java | 15 +- .../main/java/forge/game/StaticEffect.java | 1 + .../ability/effects/ChangeZoneEffect.java | 43 +++--- .../ability/effects/ControlPlayerEffect.java | 5 +- .../main/java/forge/game/player/Player.java | 138 +++++++++--------- .../java/forge/game/player/PlayerView.java | 5 +- .../StaticAbilityContinuous.java | 8 +- .../forge/trackable/TrackableProperty.java | 1 - .../cardsfolder/upcoming/opposition_agent.txt | 2 +- 9 files changed, 106 insertions(+), 112 deletions(-) diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index f0f7d4d37ee..c50f64617d4 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -421,7 +421,7 @@ public class Game { public synchronized void setGameOver(GameEndReason reason) { age = GameStage.GameOver; for (Player p : allPlayers) { - p.setMindSlaveMaster(null); // for correct totals + p.clearController(); } for (Player p : getPlayers()) { @@ -949,19 +949,6 @@ public class Game { return result; } - public Player getControlOppSearchLib() { - Player result = null; - long maxValue = 0; - for (Player p : getPlayers()) { - Long v = p.getHighestControlOppSearchLib(); - if (v != null && v > maxValue) { - maxValue = v; - result = p; - } - } - return result; - } - public void onCleanupPhase() { clearCounterAddedThisTurn(); for (Player player : getPlayers()) { diff --git a/forge-game/src/main/java/forge/game/StaticEffect.java b/forge-game/src/main/java/forge/game/StaticEffect.java index 37b630098eb..4bc15b3be33 100644 --- a/forge-game/src/main/java/forge/game/StaticEffect.java +++ b/forge-game/src/main/java/forge/game/StaticEffect.java @@ -214,6 +214,7 @@ public class StaticEffect { p.removeMaxLandPlays(getTimestamp()); p.removeMaxLandPlaysInfinite(getTimestamp()); + p.removeControlledWhileSearching(getTimestamp()); p.removeControlVote(getTimestamp()); p.removeAdditionalVote(getTimestamp()); p.removeAdditionalOptionalVote(getTimestamp()); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 3169e495b47..e938a9f9973 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -43,7 +43,7 @@ import java.util.List; import java.util.Map; public class ChangeZoneEffect extends SpellAbilityEffect { - + private boolean isHidden(SpellAbility sa) { boolean hidden = sa.hasParam("Hidden"); if (!hidden && sa.hasParam("Origin")) { @@ -484,7 +484,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { final boolean optional = sa.hasParam("Optional"); final long ts = game.getNextTimestamp(); boolean combatChanged = false; - + for (final Card tgtC : tgtCards) { final Card gameCard = game.getCardState(tgtC, null); // gameCard is LKI in that case, the card is not in game anymore @@ -644,7 +644,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { combatChanged = true; } if (sa.hasParam("Ninjutsu")) { - // Ninjutsu need to get the Defender of the Returned Creature + // Ninjutsu need to get the Defender of the Returned Creature final Card returned = sa.getPaidList("Returned").getFirst(); final GameEntity defender = game.getCombat().getDefenderByAttacker(returned); game.getCombat().addAttacker(movedCard, defender); @@ -870,11 +870,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } } - String changeType = sa.getParam("ChangeType"); + String changeType = sa.getParam("ChangeType"); CardCollection fetchList; - Player originalDecider = decider; - Player deciderControl = game.getControlOppSearchLib(); boolean shuffleMandatory = true; boolean searchedLibrary = false; if (defined) { @@ -891,14 +889,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect { fetchList = new CardCollection(player.getCardsIn(origin)); if (origin.contains(ZoneType.Library) && !sa.hasParam("NoLooking")) { searchedLibrary = true; - if (deciderControl != null) { decider = deciderControl; } if (decider.hasKeyword("LimitSearchLibrary")) { // Aven Mindcensor fetchList.removeAll(player.getCardsIn(ZoneType.Library)); final int fetchNum = Math.min(player.getCardsIn(ZoneType.Library).size(), 4); if (fetchNum == 0) { searchedLibrary = false; - if (deciderControl != null) { decider = originalDecider; } } else { fetchList.addAll(player.getCardsIn(ZoneType.Library, fetchNum)); @@ -906,11 +902,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } if (!decider.canSearchLibraryWith(sa, player)) { fetchList.removeAll(player.getCardsIn(ZoneType.Library)); - // "if you do/sb does, shuffle" is not mandatory (usually a triggered ability), should has this param. + // "if you do/sb does, shuffle" is not mandatory (usually a triggered ability), should has this param. // "then shuffle" is mandatory shuffleMandatory = !sa.hasParam("ShuffleNonMandatory"); searchedLibrary = false; - if (deciderControl != null) { decider = originalDecider; } } } } @@ -919,7 +914,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect { DelayedReveal delayedReveal = null; if (!defined && !sa.hasParam("AlreadyRevealed")) { if (origin.contains(ZoneType.Library) && searchedLibrary) { - if (deciderControl != null) { decider = deciderControl; } final int fetchNum = Math.min(player.getCardsIn(ZoneType.Library).size(), 4); CardCollectionView shown = !decider.hasKeyword("LimitSearchLibrary") ? player.getCardsIn(ZoneType.Library) : player.getCardsIn(ZoneType.Library, fetchNum); // Look at whole library before moving onto choosing a card @@ -930,11 +924,17 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } } + Long controlTimestamp = null; if (searchedLibrary) { - if (deciderControl != null) { decider = deciderControl; } if (decider.equals(player)) { - // should only count the number of searching player's own library + Map.Entry searchControlPlayer = player.getControlledWhileSearching(); + if (searchControlPlayer != null) { + controlTimestamp = searchControlPlayer.getKey(); + player.addController(controlTimestamp, searchControlPlayer.getValue()); + } + decider.incLibrarySearched(); + // should only count the number of searching player's own library // Panglacial Wurm CardCollection canCastWhileSearching = CardLists.getKeyword(fetchList, "While you're searching your library, you may cast CARDNAME from your library."); @@ -1113,7 +1113,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { Player newController = sa.getActivatingPlayer(); if (sa.hasParam("NewController")) { newController = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("NewController"), sa).get(0); - } + } c.setController(newController, game.getNextTimestamp()); } if (sa.hasParam("WithCounters")) { @@ -1235,7 +1235,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { else { movedCard = game.getAction().moveTo(c.getController().getZone(destination), c, cause, moveParams); } - + movedCards.add(movedCard); if (originZone != null) { @@ -1247,7 +1247,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { runParams.put(AbilityKey.Championed, c); game.getTriggerHandler().runTrigger(TriggerType.Championed, runParams, false); } - + if (remember) { source.addRemembered(movedCard); // addRememberedFromCardState ? @@ -1271,7 +1271,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { || (sa.hasParam("Reveal") && !movedCards.isEmpty())) && !sa.hasParam("NoReveal")) { game.getAction().reveal(movedCards, player); } - + if ((origin.contains(ZoneType.Library) && !destination.equals(ZoneType.Library) && !defined && shuffleMandatory) || (sa.hasParam("Shuffle") && "True".equals(sa.getParam("Shuffle")))) { player.shuffle(sa); @@ -1282,6 +1282,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect { game.fireEvent(new GameEventCombatChanged()); } triggerList.triggerChangesZoneAll(game); + + // remove Controlled While Searching + if (controlTimestamp != null) { + player.removeController(controlTimestamp); + } } private static boolean allowMultiSelect(Player decider, SpellAbility sa) { @@ -1307,13 +1312,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect { * @param si * a {@link forge.game.spellability.SpellAbilityStackInstance} * object. - * @param game + * @param game */ private static void removeFromStack(final SpellAbility tgtSA, final SpellAbility srcSA, final SpellAbilityStackInstance si, final Game game, CardZoneTable triggerList) { final Card tgtHost = tgtSA.getHostCard(); final Zone originZone = tgtHost.getZone(); game.getStack().remove(si); - + Map params = AbilityKey.newMap(); params.put(AbilityKey.StackSa, tgtSA); params.put(AbilityKey.StackSi, si); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java index 0dbefa24bdb..443d3d8bfa2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java @@ -36,13 +36,14 @@ public class ControlPlayerEffect extends SpellAbilityEffect { game.getUntap().addUntil(pTarget, new GameCommand() { @Override public void run() { - pTarget.setMindSlaveMaster(activator); + long ts = game.getNextTimestamp(); + pTarget.addController(ts, activator); // on following cleanup release control game.getEndOfTurn().addUntil(new GameCommand() { @Override public void run() { - pTarget.setMindSlaveMaster(null); + pTarget.removeController(ts); } }); } 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 babac61ed42..173be4275e2 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -60,6 +60,7 @@ import forge.util.*; import forge.util.collect.FCollection; import forge.util.collect.FCollectionView; import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; import java.util.*; import java.util.Map.Entry; @@ -144,8 +145,10 @@ public class Player extends GameEntity implements Comparable { private PlayerStatistics stats = new PlayerStatistics(); private PlayerController controller; - private PlayerController controllerCreator = null; - private Player mindSlaveMaster = null; + + private NavigableMap> controlledBy = Maps.newTreeMap(); + + private NavigableMap controlledWhileSearching = Maps.newTreeMap(); private int teamNumber = -1; private Card activeScheme = null; @@ -165,8 +168,6 @@ public class Player extends GameEntity implements Comparable { private Map additionalOptionalVotes = Maps.newHashMap(); private SortedSet controlVotes = Sets.newTreeSet(); - private SortedSet controlOppSearchLib = Sets.newTreeSet(); - private final AchievementTracker achievementTracker = new AchievementTracker(); private final PlayerView view; @@ -1683,7 +1684,7 @@ public class Player extends GameEntity implements Comparable { // Replacement effects final Map repRunParams = AbilityKey.mapFromAffected(this); repRunParams.put(AbilityKey.Number, n); - + if (destination == ZoneType.Graveyard && !bottom) { switch (getGame().getReplacementHandler().run(ReplacementType.Mill, repRunParams)) { case NotReplaced: @@ -2413,40 +2414,13 @@ public class Player extends GameEntity implements Comparable { } public final LobbyPlayer getOriginalLobbyPlayer() { - return controllerCreator.getLobbyPlayer(); + return controller.getLobbyPlayer(); } public final RegisteredPlayer getRegisteredPlayer() { return game.getMatch().getPlayers().get(game.getRegisteredPlayers().indexOf(this)); } - public final boolean isMindSlaved() { - return mindSlaveMaster != null; - } - - public final Player getMindSlaveMaster() { - return mindSlaveMaster; - } - - public final void setMindSlaveMaster(final Player mindSlaveMaster0) { - if (mindSlaveMaster == mindSlaveMaster0) { - return; - } - mindSlaveMaster = mindSlaveMaster0; - view.updateMindSlaveMaster(this); - - if (mindSlaveMaster != null) { - final LobbyPlayer oldLobbyPlayer = getLobbyPlayer(); - final PlayerController oldController = getController(); - final IGameEntitiesFactory master = (IGameEntitiesFactory)mindSlaveMaster.getLobbyPlayer(); - controller = master.createMindSlaveController(mindSlaveMaster, this); - game.fireEvent(new GameEventPlayerControl(this, oldLobbyPlayer, oldController, getLobbyPlayer(), controller)); - } else { - controller = controllerCreator; - game.fireEvent(new GameEventPlayerControl(this, getLobbyPlayer(), controller, null, null)); - } - } - private void setOutcome(PlayerOutcome outcome) { stats.setOutcome(outcome); } @@ -2548,14 +2522,73 @@ public class Player extends GameEntity implements Comparable { } public final PlayerController getController() { + if (!controlledBy.isEmpty()) { + return controlledBy.lastEntry().getValue().getValue(); + } return controller; } + public final Player getControllingPlayer() { + if (!controlledBy.isEmpty()) { + return controlledBy.lastEntry().getValue().getKey(); + } + return null; + } + + public void addController(long timestamp, Player pl) { + final IGameEntitiesFactory master = (IGameEntitiesFactory)pl.getLobbyPlayer(); + addController(timestamp, master.createMindSlaveController(pl, this), true); + } + + public void addController(long timestamp, PlayerController pc, boolean event) { + final LobbyPlayer oldLobbyPlayer = getLobbyPlayer(); + final PlayerController oldController = getController(); + + controlledBy.put(timestamp, Pair.of(pc.getPlayer(), pc)); + getView().updateMindSlaveMaster(this); + + if (event) { + game.fireEvent(new GameEventPlayerControl(this, oldLobbyPlayer, oldController, getLobbyPlayer(), getController())); + } + } + + public void removeController(long timestamp) { + removeController(timestamp, true); + } + public void removeController(long timestamp, boolean event) { + final LobbyPlayer oldLobbyPlayer = getLobbyPlayer(); + final PlayerController oldController = getController(); + + controlledBy.remove(timestamp); + if (event) { + game.fireEvent(new GameEventPlayerControl(this, oldLobbyPlayer, oldController, getLobbyPlayer(), getController())); + } + } + + public void clearController() { + controlledBy.clear(); + } + + + public Map.Entry getControlledWhileSearching() { + if (controlledWhileSearching.isEmpty()) { + return null; + } + return controlledWhileSearching.lastEntry(); + } + + public void addControlledWhileSearching(long timestamp, Player pl) { + controlledWhileSearching.put(timestamp, pl); + } + + public void removeControlledWhileSearching(long timestamp) { + controlledWhileSearching.remove(timestamp); + } + public final void setFirstController(PlayerController ctrlr) { - if (controllerCreator != null) { + if (controller != null) { throw new IllegalStateException("Controller creator already assigned"); } - controllerCreator = ctrlr; controller = ctrlr; updateAvatar(); updateSleeve(); @@ -2576,12 +2609,12 @@ public class Player extends GameEntity implements Comparable { * Run a procedure using a different controller */ public void runWithController(Runnable proc, PlayerController tempController) { - PlayerController oldController = controller; - controller = tempController; + long ts = game.getNextTimestamp(); + this.addController(ts, tempController, false); try { proc.run(); } finally { - controller = oldController; + this.removeController(ts, false); } } @@ -3020,7 +3053,7 @@ public class Player extends GameEntity implements Comparable { CardCollectionView view = CardCollection.getView(legalCompanions); SpellAbility fakeSa = new SpellAbility.EmptySa(ApiType.CompanionChoose, legalCompanions.get(0), this); - return controller.chooseSingleEntityForEffect(view, fakeSa, Localizer.getInstance().getMessage("lblChooseACompanion"), true, null); + return player.chooseSingleEntityForEffect(view, fakeSa, Localizer.getInstance().getMessage("lblChooseACompanion"), true, null); } public boolean deckMatchesDeckRestriction(Card source, String restriction) { @@ -3414,33 +3447,6 @@ public class Player extends GameEntity implements Comparable { return controlVotes.last(); } - public boolean addControlOppSearchLib(long timestamp) { - if (controlOppSearchLib.add(timestamp)) { - updateControlOppSearchLib(); - return true; - } - return false; - } - - void updateControlOppSearchLib() { // needs to update all players - Player control = getGame().getControlOppSearchLib(); - for (Player pl : getGame().getPlayers()) { - pl.getView().updateControlOppSearchLib(pl.equals(control)); - getGame().fireEvent(new GameEventPlayerStatsChanged(pl, false)); - } - } - - public Set getControlOppSearchLib() { - return controlOppSearchLib; - } - - public Long getHighestControlOppSearchLib() { - if (controlOppSearchLib.isEmpty()) { - return null; - } - return controlOppSearchLib.last(); - } - public void addCycled(SpellAbility sp) { cycledThisTurn++; 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 e042df95cbb..311177cdc3e 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerView.java +++ b/forge-game/src/main/java/forge/game/player/PlayerView.java @@ -296,9 +296,6 @@ public class PlayerView extends GameEntityView { set(TrackableProperty.ControlVotes, val); } - public boolean getControlOppSearchLib() { return get(TrackableProperty.ControlOppSearchLib); } - public void updateControlOppSearchLib(boolean val) { set(TrackableProperty.ControlOppSearchLib, val); } - public ImmutableMultiset getKeywords() { return get(TrackableProperty.Keywords); } @@ -358,7 +355,7 @@ public class PlayerView extends GameEntityView { return get(TrackableProperty.MindSlaveMaster); } void updateMindSlaveMaster(Player p) { - set(TrackableProperty.MindSlaveMaster, PlayerView.get(p.getMindSlaveMaster())); + set(TrackableProperty.MindSlaveMaster, PlayerView.get(p.getControllingPlayer())); } public FCollectionView getAnte() { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java index c5eab42e25a..084d22c194c 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -552,11 +552,9 @@ public final class StaticAbilityContinuous { p.addMaxLandPlays(se.getTimestamp(), add); } } - if (params.containsKey("ControlOpponentsWhile")) { - String cow = params.get("ControlOpponentsWhile"); - if (cow.equals("SearchingLibrary")) { - p.addControlOppSearchLib(se.getTimestamp()); - } + if (params.containsKey("ControlOpponentsSearchingLibrary")) { + Player cntl = Iterables.getFirst(AbilityUtils.getDefinedPlayers(hostCard, params.get("ControlOpponentsSearchingLibrary"), null), null); + p.addControlledWhileSearching(se.getTimestamp(), cntl); } if (params.containsKey("ControlVote")) { diff --git a/forge-game/src/main/java/forge/trackable/TrackableProperty.java b/forge-game/src/main/java/forge/trackable/TrackableProperty.java index 1d892fe2db3..c1c1c09f676 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableProperty.java +++ b/forge-game/src/main/java/forge/trackable/TrackableProperty.java @@ -149,7 +149,6 @@ public enum TrackableProperty { AdditionalVote(TrackableTypes.IntegerType), OptionalAdditionalVote(TrackableTypes.IntegerType), ControlVotes(TrackableTypes.BooleanType), - ControlOppSearchLib(TrackableTypes.BooleanType), Keywords(TrackableTypes.KeywordCollectionViewType, FreezeMode.IgnoresFreeze), Commander(TrackableTypes.CardViewCollectionType, FreezeMode.IgnoresFreeze), CommanderCast(TrackableTypes.IntegerMapType), diff --git a/forge-gui/res/cardsfolder/upcoming/opposition_agent.txt b/forge-gui/res/cardsfolder/upcoming/opposition_agent.txt index 04ff29fea0b..d38dd963e25 100755 --- a/forge-gui/res/cardsfolder/upcoming/opposition_agent.txt +++ b/forge-gui/res/cardsfolder/upcoming/opposition_agent.txt @@ -3,7 +3,7 @@ ManaCost:2 B Types:Creature Human Rogue PT:3/2 K:Flash -S:Mode$ Continuous | Affected$ You | ControlOpponentsWhile$ SearchingLibrary | Description$ You control your opponents while they're searching their libraries. +S:Mode$ Continuous | Affected$ Opponent | ControlOpponentsSearchingLibrary$ You | Description$ You control your opponents while they're searching their libraries. R:Event$ Moved | ValidCard$ Card.OppOwn | FoundSearchingLibrary$ True | Origin$ Library | ReplaceWith$ RepExile | ActiveZones$ Battlefield | Description$ While an opponent is searching their library, they exile each card they find. You may play those cards for as long as they remain exiled, and you may spend mana as though it were mana of any color to cast them. SVar:RepExile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard | SubAbility$ DBEffect SVar:DBEffect:DB$ Effect | Duration$ Permanent | StaticAbilities$ MayPlay | RememberObjects$ ReplacedCard | ForgetOnMoved$ Exile