Opposition Agent: use timestamp for controlledBy effects

This commit is contained in:
Hans Mackowiak
2020-11-27 15:40:06 +01:00
committed by Northmoc
parent db4ed8d27e
commit 5e71acb082
9 changed files with 106 additions and 112 deletions

View File

@@ -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()) {

View File

@@ -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());

View File

@@ -873,8 +873,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
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));
@@ -910,7 +906,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// "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<Long, Player> 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.");
@@ -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) {

View File

@@ -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);
}
});
}

View File

@@ -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<Player> {
private PlayerStatistics stats = new PlayerStatistics();
private PlayerController controller;
private PlayerController controllerCreator = null;
private Player mindSlaveMaster = null;
private NavigableMap<Long, Pair<Player, PlayerController>> controlledBy = Maps.newTreeMap();
private NavigableMap<Long, Player> controlledWhileSearching = Maps.newTreeMap();
private int teamNumber = -1;
private Card activeScheme = null;
@@ -165,8 +168,6 @@ public class Player extends GameEntity implements Comparable<Player> {
private Map<Long, Integer> additionalOptionalVotes = Maps.newHashMap();
private SortedSet<Long> controlVotes = Sets.newTreeSet();
private SortedSet<Long> controlOppSearchLib = Sets.newTreeSet();
private final AchievementTracker achievementTracker = new AchievementTracker();
private final PlayerView view;
@@ -2413,40 +2414,13 @@ public class Player extends GameEntity implements Comparable<Player> {
}
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<Player> {
}
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<Long, Player> 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<Player> {
* 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<Player> {
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<Player> {
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<Long> getControlOppSearchLib() {
return controlOppSearchLib;
}
public Long getHighestControlOppSearchLib() {
if (controlOppSearchLib.isEmpty()) {
return null;
}
return controlOppSearchLib.last();
}
public void addCycled(SpellAbility sp) {
cycledThisTurn++;

View File

@@ -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<String> 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<CardView> getAnte() {

View File

@@ -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")) {

View File

@@ -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),

View File

@@ -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