mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 20:58:03 +00:00
Opposition Agent: use timestamp for controlledBy effects
This commit is contained in:
@@ -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()) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user