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());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user