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

@@ -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<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.");
@@ -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<AbilityKey,Object> params = AbilityKey.newMap();
params.put(AbilityKey.StackSa, tgtSA);
params.put(AbilityKey.StackSi, si);

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;
@@ -1683,7 +1684,7 @@ public class Player extends GameEntity implements Comparable<Player> {
// Replacement effects
final Map<AbilityKey, Object> 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<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),