mirror of
https://github.com/correl/mage.git
synced 2024-12-24 11:50:45 +00:00
Fixed miss copy code in Game object (lki, cards), removed unused code. Possible fixes:
* simulated games was able to change objects from another games (ConcurrentModificationException, related tod202278ccd
, details in3a6cdd2615
); * AI: fixed cards disappear in multiplayer games with computer (details in #6738);
This commit is contained in:
parent
9f882824a0
commit
1664ee01cf
39 changed files with 201 additions and 125 deletions
|
@ -15,8 +15,8 @@ public class TwoPlayerDuel extends GameImpl {
|
|||
this(attackOption, range, mulligan, startLife, 60);
|
||||
}
|
||||
|
||||
public TwoPlayerDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife, int startingSize) {
|
||||
super(attackOption, range, mulligan, startLife, startingSize);
|
||||
public TwoPlayerDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startingLife, int startingHandSize) {
|
||||
super(attackOption, range, mulligan, startingLife, startingHandSize);
|
||||
}
|
||||
|
||||
public TwoPlayerDuel(final TwoPlayerDuel game) {
|
||||
|
|
|
@ -112,7 +112,7 @@ class AjaniStrengthOfThePrideEffect extends OneShotEffect {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player == null || player.getLife() < game.getLife() + 15) {
|
||||
if (player == null || player.getLife() < game.getStartingLife() + 15) {
|
||||
return false;
|
||||
}
|
||||
new ExileSourceEffect().apply(game, source);
|
||||
|
|
|
@ -72,7 +72,7 @@ enum AngelOfDestinyCondition implements Condition {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
return player != null && player.getLife() >= game.getLife() + 15;
|
||||
return player != null && player.getLife() >= game.getStartingLife() + 15;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ enum AnyaMercilessAngelDynamicValue implements DynamicValue {
|
|||
if (controller == null) {
|
||||
return 3 * opponentCount;
|
||||
}
|
||||
int startingLifeTotal = game.getLife();
|
||||
int startingLifeTotal = game.getStartingLife();
|
||||
for (UUID opponentId : game.getOpponents(controller.getId())) {
|
||||
Player opponent = game.getPlayer(opponentId);
|
||||
if (opponent != null && opponent.getLife() < startingLifeTotal / 2) {
|
||||
|
|
|
@ -79,7 +79,7 @@ class AyliEternalPilgrimCondition implements Condition {
|
|||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if(player != null) {
|
||||
return player.getLife() >= game.getLife() + 10;
|
||||
return player.getLife() >= game.getStartingLife() + 10;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ class ChaliceOfLifeEffect extends OneShotEffect {
|
|||
player.gainLife(1, game, source);
|
||||
|
||||
// if you have at least 10 life more than your starting life total, transform Chalice of Life.
|
||||
if (player.getLife() >= game.getLife() + 10) {
|
||||
if (player.getLife() >= game.getStartingLife() + 10) {
|
||||
permanent.transform(game);
|
||||
game.informPlayers(permanent.getName() + " transforms into " + permanent.getSecondCardFace().getName());
|
||||
}
|
||||
|
|
|
@ -47,6 +47,6 @@ enum CosmosElixirCondition implements Condition {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
return player != null && player.getLife() > game.getLife();
|
||||
return player != null && player.getLife() > game.getStartingLife();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ class ExquisiteArchangelEffect extends ReplacementEffectImpl {
|
|||
Permanent sourcePermanent = game.getPermanent(source.getSourceId());
|
||||
if (player != null && sourcePermanent != null) {
|
||||
new ExileSourceEffect().apply(game, source);
|
||||
player.setLife(game.getLife(), game, source);
|
||||
player.setLife(game.getStartingLife(), game, source);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -60,7 +60,7 @@ enum GyrudaDoomOfDepthsCompanionCondition implements CompanionCondition {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegal(Set<Card> deck, int startingSize) {
|
||||
public boolean isLegal(Set<Card> deck, int startingHandSize) {
|
||||
return deck
|
||||
.stream()
|
||||
.mapToInt(MageObject::getManaValue)
|
||||
|
|
|
@ -88,7 +88,7 @@ enum HappilyEverAfterCondition implements Condition {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player == null || player.getLife() < game.getLife()) {
|
||||
if (player == null || player.getLife() < game.getStartingLife()) {
|
||||
return false;
|
||||
}
|
||||
ObjectColor color = new ObjectColor("");
|
||||
|
|
|
@ -63,7 +63,7 @@ enum JeganthaTheWellspringCompanionCondition implements CompanionCondition {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegal(Set<Card> deck, int startingSize) {
|
||||
public boolean isLegal(Set<Card> deck, int startingHandSize) {
|
||||
return deck.stream().noneMatch(JeganthaTheWellspringCompanionCondition::checkCard);
|
||||
}
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ enum KaheeraTheOrphanguardCompanionCondition implements CompanionCondition {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegal(Set<Card> deck, int startingSize) {
|
||||
public boolean isLegal(Set<Card> deck, int startingHandSize) {
|
||||
return deck.stream()
|
||||
.filter(card -> card.hasCardTypeForDeckbuilding(CardType.CREATURE))
|
||||
.allMatch(KaheeraTheOrphanguardCompanionCondition::isCardLegal);
|
||||
|
|
|
@ -66,7 +66,7 @@ enum KerugaCondition implements CompanionCondition {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegal(Set<Card> deck, int startingSize) {
|
||||
public boolean isLegal(Set<Card> deck, int startingHandSize) {
|
||||
return deck.stream().allMatch(card -> card.isLand() || card.getManaValue() >= 3);
|
||||
}
|
||||
}
|
|
@ -125,7 +125,7 @@ class LongRestEffect extends OneShotEffect {
|
|||
if (numCards > 0) {
|
||||
controller.moveCards(cardsToHand, Zone.HAND, source, game);
|
||||
if (numCards >= 8) {
|
||||
controller.setLife(game.getLife(), game, source);
|
||||
controller.setLife(game.getStartingLife(), game, source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ enum LurrusOfTheDreamDenCompanionCondition implements CompanionCondition {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegal(Set<Card> deck, int startingSize) {
|
||||
public boolean isLegal(Set<Card> deck, int startingHandSize) {
|
||||
return deck.stream()
|
||||
.filter(card -> card.isPermanent())
|
||||
.mapToInt(MageObject::getManaValue)
|
||||
|
|
|
@ -83,7 +83,7 @@ enum LutriTheSpellchaserCompanionCondition implements CompanionCondition {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegal(Set<Card> deck, int startingSize) {
|
||||
public boolean isLegal(Set<Card> deck, int startingHandSize) {
|
||||
Map<String, Integer> cardMap = new HashMap<>();
|
||||
deck.stream()
|
||||
.filter(card -> !card.isLand())
|
||||
|
|
|
@ -58,7 +58,7 @@ enum OboshThePreypiercerCompanionCondition implements CompanionCondition {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegal(Set<Card> deck, int startingSize) {
|
||||
public boolean isLegal(Set<Card> deck, int startingHandSize) {
|
||||
return deck
|
||||
.stream()
|
||||
.filter(card -> !card.isLand())
|
||||
|
|
|
@ -61,7 +61,7 @@ class OketrasLastMercyEffect extends OneShotEffect {
|
|||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller != null) {
|
||||
controller.setLife(game.getLife(), game, source);
|
||||
controller.setLife(game.getStartingLife(), game, source);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -62,7 +62,7 @@ enum LifeCondition implements Condition {
|
|||
public boolean apply(Game game, Ability source) {
|
||||
Player you = game.getPlayer(source.getControllerId());
|
||||
if (you != null) {
|
||||
return you.getLife() >= game.getLife();
|
||||
return you.getLife() >= game.getStartingLife();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ class ResoluteArchangelEffect extends OneShotEffect {
|
|||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller != null) {
|
||||
controller.setLife(game.getLife(), game, source);
|
||||
controller.setLife(game.getStartingLife(), game, source);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -82,7 +82,7 @@ enum ControllerLifeLowerThanStrtingLife implements Condition {
|
|||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller != null) {
|
||||
return controller.getLife() < game.getLife();
|
||||
return controller.getLife() < game.getStartingLife();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -101,6 +101,6 @@ enum RighteousValkyrieCondition implements Condition {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
return player != null && player.getLife() >= game.getLife() + 7;
|
||||
return player != null && player.getLife() >= game.getStartingLife() + 7;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ enum SpeakerOfTheHeavensCondition implements Condition {
|
|||
return false;
|
||||
}
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player == null || player.getLife() < game.getLife() + 7) {
|
||||
if (player == null || player.getLife() < game.getStartingLife() + 7) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -77,7 +77,7 @@ class TorgaarFamineIncarnateEffect extends OneShotEffect {
|
|||
public boolean apply(Game game, Ability source) {
|
||||
Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source));
|
||||
if (targetPlayer != null) {
|
||||
int startingLifeTotal = game.getLife();
|
||||
int startingLifeTotal = game.getStartingLife();
|
||||
targetPlayer.setLife(startingLifeTotal / 2, game, source);
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -64,7 +64,7 @@ enum UmoriCondition implements CompanionCondition {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegal(Set<Card> deck, int startingSize) {
|
||||
public boolean isLegal(Set<Card> deck, int startingHandSize) {
|
||||
Set<CardType> cardTypes = new HashSet<>();
|
||||
for (Card card : deck) {
|
||||
// Lands are fine.
|
||||
|
|
|
@ -72,8 +72,8 @@ enum YorionSkyNomadCompanionCondition implements CompanionCondition {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegal(Set<Card> deck, int startingSize) {
|
||||
return deck.size() >= startingSize + 20;
|
||||
public boolean isLegal(Set<Card> deck, int startingHandSize) {
|
||||
return deck.size() >= startingHandSize + 20;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ enum ZirdaTheDawnwakerCompanionCondition implements CompanionCondition {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegal(Set<Card> deck, int startingSize) {
|
||||
public boolean isLegal(Set<Card> deck, int startingHandSize) {
|
||||
return deck
|
||||
.stream()
|
||||
.filter(card -> card.isPermanent())
|
||||
|
|
|
@ -13,6 +13,7 @@ import mage.constants.SubTypeSet;
|
|||
import mage.constants.SuperType;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.util.Copyable;
|
||||
import mage.util.SubTypes;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
@ -21,7 +22,7 @@ import java.util.Collection;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public interface MageObject extends MageItem, Serializable {
|
||||
public interface MageObject extends MageItem, Serializable, Copyable<MageObject> {
|
||||
|
||||
String getName();
|
||||
|
||||
|
@ -136,6 +137,7 @@ public interface MageObject extends MageItem, Serializable {
|
|||
void adjustTargets(Ability ability, Game game);
|
||||
|
||||
// memory object copy (not mtg)
|
||||
@Override
|
||||
MageObject copy();
|
||||
|
||||
// copied card info (mtg)
|
||||
|
|
|
@ -41,8 +41,8 @@ public class CompanionAbility extends SpecialAction {
|
|||
return "Companion — " + companionCondition.getRule();
|
||||
}
|
||||
|
||||
public boolean isLegal(Set<Card> cards, int startingSize) {
|
||||
return companionCondition.isLegal(cards, startingSize);
|
||||
public boolean isLegal(Set<Card> cards, int startingHandSize) {
|
||||
return companionCondition.isLegal(cards, startingHandSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@ public interface CompanionCondition extends Serializable {
|
|||
|
||||
/**
|
||||
* @param deck The set of cards to check.
|
||||
* @param startingSize
|
||||
* @param startingHandSize
|
||||
* @return Whether the companion is valid for that deck.
|
||||
*/
|
||||
boolean isLegal(Set<Card> deck, int startingSize);
|
||||
boolean isLegal(Set<Card> deck, int startingHandSize);
|
||||
}
|
||||
|
|
|
@ -64,9 +64,7 @@ public class MageDrawAction extends MageAction {
|
|||
if (!player.isTopCardRevealed() && numDrawn > 0) {
|
||||
game.fireInformEvent(player.getLogName() + " draws " + CardUtil.numberToText(numDrawn, "a") + " card" + (numDrawn > 1 ? "s" : ""));
|
||||
}
|
||||
|
||||
setScore(player, score);
|
||||
game.setStateCheckRequired();
|
||||
}
|
||||
return numDrawn;
|
||||
}
|
||||
|
|
|
@ -36,6 +36,9 @@ public abstract class MeldCard extends CardImpl {
|
|||
this.halves = new CardsImpl(card.halves);
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract MeldCard copy();
|
||||
|
||||
public void setMelded(boolean isMelded, Game game) {
|
||||
game.getState().getCardState(getId()).setMelded(isMelded);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import mage.constants.SubType;
|
|||
import mage.constants.SuperType;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.util.Copyable;
|
||||
import mage.util.GameLog;
|
||||
import mage.util.SubTypes;
|
||||
|
||||
|
@ -28,7 +27,7 @@ import java.util.UUID;
|
|||
/**
|
||||
* @author LevelX2
|
||||
*/
|
||||
public abstract class Designation implements MageObject, Copyable<Designation> {
|
||||
public abstract class Designation implements MageObject {
|
||||
|
||||
private static final List<CardType> emptySet = new ArrayList<>();
|
||||
private static final ObjectColor emptyColor = new ObjectColor();
|
||||
|
@ -69,6 +68,9 @@ public abstract class Designation implements MageObject, Copyable<Designation> {
|
|||
this.unique = designation.unique;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract Designation copy();
|
||||
|
||||
@Override
|
||||
public FrameStyle getFrameStyle() {
|
||||
return frameStyle;
|
||||
|
|
|
@ -12,12 +12,13 @@ import mage.abilities.AbilitiesImpl;
|
|||
import mage.abilities.Ability;
|
||||
import mage.counters.Counter;
|
||||
import mage.counters.Counters;
|
||||
import mage.util.Copyable;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward
|
||||
*/
|
||||
public class CardState implements Serializable {
|
||||
public class CardState implements Serializable, Copyable<CardState> {
|
||||
|
||||
protected boolean faceDown;
|
||||
protected Map<String, String> info;
|
||||
|
@ -50,6 +51,7 @@ public class CardState implements Serializable {
|
|||
this.melded = state.melded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardState copy() {
|
||||
return new CardState(this);
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
|
|||
|
||||
int getNumPlayers();
|
||||
|
||||
int getLife();
|
||||
int getStartingLife();
|
||||
|
||||
RangeOfInfluence getRangeOfInfluence();
|
||||
|
||||
|
@ -244,10 +244,6 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
|
|||
|
||||
Player getLosingPlayer();
|
||||
|
||||
void setStateCheckRequired();
|
||||
|
||||
boolean getStateCheckRequired();
|
||||
|
||||
//client event methods
|
||||
void addTableEventListener(Listener<TableEvent> listener);
|
||||
|
||||
|
|
|
@ -28,8 +28,8 @@ public abstract class GameCommanderImpl extends GameImpl {
|
|||
|
||||
protected boolean startingPlayerSkipsDraw = true;
|
||||
|
||||
public GameCommanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife, int startingSize) {
|
||||
super(attackOption, range, mulligan, startLife, startingSize);
|
||||
public GameCommanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startingLife, int startingHandSize) {
|
||||
super(attackOption, range, mulligan, startingLife, startingHandSize);
|
||||
}
|
||||
|
||||
public GameCommanderImpl(final GameCommanderImpl game) {
|
||||
|
|
|
@ -59,7 +59,10 @@ import mage.target.Target;
|
|||
import mage.target.TargetCard;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.util.*;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.GameLog;
|
||||
import mage.util.MessageToClient;
|
||||
import mage.util.RandomUtil;
|
||||
import mage.util.functions.CopyApplier;
|
||||
import mage.watchers.Watcher;
|
||||
import mage.watchers.common.*;
|
||||
|
@ -71,14 +74,23 @@ import java.util.*;
|
|||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Game object. It must contain static data (e.g. no changeable in the game like game settings)
|
||||
* <p>
|
||||
* "transient field" logic uses for serialization/replays (mark temporary fields as transient,
|
||||
* also look for non restored fields in copy constructor for details)
|
||||
* <p>
|
||||
* WARNING, if you add new fields then don't forget to add it to copy constructor (deep copy, not ref).
|
||||
* If it's a temporary/auto-generated data then mark that field as transient and comment in copy constructor.
|
||||
*/
|
||||
public abstract class GameImpl implements Game {
|
||||
|
||||
private static final int ROLLBACK_TURNS_MAX = 4;
|
||||
private static final String UNIT_TESTS_ERROR_TEXT = "Error in unit tests";
|
||||
private static final Logger logger = Logger.getLogger(GameImpl.class);
|
||||
|
||||
private transient Object customData;
|
||||
|
||||
private transient Object customData; // temporary data, used in AI simulations
|
||||
private transient Player losingPlayer; // temporary data, used in AI simulations
|
||||
protected boolean simulation = false;
|
||||
protected boolean checkPlayableState = false;
|
||||
|
||||
|
@ -95,18 +107,21 @@ public abstract class GameImpl implements Game {
|
|||
protected Map<Zone, Map<UUID, CardState>> lkiCardState = new EnumMap<>(Zone.class);
|
||||
protected Map<UUID, Map<Integer, MageObject>> lkiExtended = new HashMap<>();
|
||||
// Used to check if an object was moved by the current effect in resolution (so Wrath like effect can be handled correctly)
|
||||
protected Map<Zone, Set<UUID>> shortLivingLKI = new EnumMap<>(Zone.class);
|
||||
protected Map<Zone, Set<UUID>> lkiShortLiving = new EnumMap<>(Zone.class);
|
||||
|
||||
// Permanents entering the Battlefield while handling replacement effects before they are added to the battlefield
|
||||
protected Map<UUID, Permanent> permanentsEntering = new HashMap<>();
|
||||
// used to set the counters a permanent adds the battlefield (if no replacement effect is used e.g. Persist)
|
||||
protected Map<UUID, Counters> enterWithCounters = new HashMap<>();
|
||||
|
||||
protected GameState state;
|
||||
private transient Stack<Integer> savedStates = new Stack<>();
|
||||
protected transient GameStates gameStates = new GameStates();
|
||||
|
||||
// game states to allow player rollback
|
||||
protected transient Map<Integer, GameState> gameStatesRollBack = new HashMap<>();
|
||||
protected boolean executingRollback;
|
||||
protected int turnToGoToForRollback;
|
||||
protected transient boolean executingRollback;
|
||||
protected transient int turnToGoToForRollback;
|
||||
|
||||
protected Date startTime;
|
||||
protected Date endTime;
|
||||
|
@ -121,73 +136,126 @@ public abstract class GameImpl implements Game {
|
|||
protected GameOptions gameOptions;
|
||||
protected String startMessage;
|
||||
|
||||
// private final transient LinkedList<MageAction> actions;
|
||||
private Player scorePlayer;
|
||||
// private int score = 0;
|
||||
private Player losingPlayer;
|
||||
private boolean stateCheckRequired = false;
|
||||
private boolean scopeRelevant = false; // replacement effects: used to indicate that currently applied replacement effects have to check for scope relevance (614.12 13/01/18)
|
||||
private boolean saveGame = false; // replay code, not done
|
||||
private int priorityTime; // match time limit
|
||||
private final int startingLife;
|
||||
private final int startingHandSize;
|
||||
protected transient PlayerList playerList; // auto-generated from state, don't copy
|
||||
|
||||
// used to indicate that currently applied replacement effects have to check for scope relevance (614.12 13/01/18)
|
||||
private boolean scopeRelevant = false;
|
||||
private boolean saveGame = false;
|
||||
private int priorityTime;
|
||||
|
||||
private final int startLife;
|
||||
private final int startingSize;
|
||||
protected PlayerList playerList; // auto-generated from state, don't copy
|
||||
|
||||
// infinite loop check (no copy of this attributes neccessary)
|
||||
private int infiniteLoopCounter; // used to check if the game is in an infinite loop
|
||||
private int lastNumberOfAbilitiesOnTheStack; // used to check how long no new ability was put to stack
|
||||
private List<Integer> lastPlayersLifes = null; // if life is going down, it's no infinite loop
|
||||
private final LinkedList<UUID> stackObjectsCheck = new LinkedList<>(); // used to check if different sources used the stack
|
||||
|
||||
// used to set the counters a permanent adds the battlefield (if no replacement effect is used e.g. Persist)
|
||||
protected Map<UUID, Counters> enterWithCounters = new HashMap<>();
|
||||
// infinite loop check (temporary data, do not copy)
|
||||
private transient int infiniteLoopCounter; // used to check if the game is in an infinite loop
|
||||
private transient int lastNumberOfAbilitiesOnTheStack; // used to check how long no new ability was put to stack
|
||||
private transient List<Integer> lastPlayersLifes = null; // if life is going down, it's no infinite loop
|
||||
private transient final LinkedList<UUID> stackObjectsCheck = new LinkedList<>(); // used to check if different sources used the stack
|
||||
|
||||
// temporary store for income concede commands, don't copy
|
||||
private final LinkedList<UUID> concedingPlayers = new LinkedList<>();
|
||||
|
||||
public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife, int startingSize) {
|
||||
public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startingLife, int startingHandSize) {
|
||||
this.id = UUID.randomUUID();
|
||||
this.range = range;
|
||||
this.mulligan = mulligan;
|
||||
this.attackOption = attackOption;
|
||||
this.state = new GameState();
|
||||
this.startLife = startLife;
|
||||
this.startingLife = startingLife;
|
||||
this.executingRollback = false;
|
||||
this.startingSize = startingSize;
|
||||
this.startingHandSize = startingHandSize;
|
||||
initGameDefaultWatchers();
|
||||
}
|
||||
|
||||
public GameImpl(final GameImpl game) {
|
||||
this.id = game.id;
|
||||
this.ready = game.ready;
|
||||
this.startingPlayerId = game.startingPlayerId;
|
||||
this.winnerId = game.winnerId;
|
||||
this.range = game.range;
|
||||
this.mulligan = game.getMulligan().copy();
|
||||
this.attackOption = game.attackOption;
|
||||
this.state = game.state.copy();
|
||||
this.gameCards = game.gameCards;
|
||||
//this.customData = game.customData; // temporary data, no need on game copy
|
||||
//this.losingPlayer = game.losingPlayer; // temporary data, no need on game copy
|
||||
this.simulation = game.simulation;
|
||||
this.checkPlayableState = game.checkPlayableState;
|
||||
this.gameOptions = game.gameOptions;
|
||||
this.lki.putAll(game.lki);
|
||||
this.lkiExtended.putAll(game.lkiExtended);
|
||||
this.lkiCardState.putAll(game.lkiCardState);
|
||||
this.shortLivingLKI.putAll(game.shortLivingLKI);
|
||||
this.permanentsEntering.putAll(game.permanentsEntering);
|
||||
|
||||
this.stateCheckRequired = game.stateCheckRequired;
|
||||
this.scorePlayer = game.scorePlayer;
|
||||
this.scopeRelevant = game.scopeRelevant;
|
||||
this.priorityTime = game.priorityTime;
|
||||
this.saveGame = game.saveGame;
|
||||
this.startLife = game.startLife;
|
||||
this.enterWithCounters.putAll(game.enterWithCounters);
|
||||
this.startingSize = game.startingSize;
|
||||
this.id = game.id;
|
||||
|
||||
this.ready = game.ready;
|
||||
//this.tableEventSource = game.tableEventSource; // client-server part, not need on copy/simulations
|
||||
//this.playerQueryEventSource = game.playerQueryEventSource; // client-server part, not need on copy/simulations
|
||||
|
||||
for (Entry<UUID, Card> entry : game.gameCards.entrySet()) {
|
||||
this.gameCards.put(entry.getKey(), entry.getValue().copy());
|
||||
}
|
||||
for (Entry<UUID, MeldCard> entry : game.meldCards.entrySet()) {
|
||||
this.meldCards.put(entry.getKey(), entry.getValue().copy());
|
||||
}
|
||||
|
||||
// lki
|
||||
for (Entry<Zone, Map<UUID, MageObject>> entry : game.lki.entrySet()) {
|
||||
Map<UUID, MageObject> lkiMap = new HashMap<>();
|
||||
for (Entry<UUID, MageObject> entryMap : entry.getValue().entrySet()) {
|
||||
lkiMap.put(entryMap.getKey(), entryMap.getValue().copy());
|
||||
}
|
||||
this.lki.put(entry.getKey(), lkiMap);
|
||||
}
|
||||
// lkiCardState
|
||||
for (Entry<Zone, Map<UUID, CardState>> entry : game.lkiCardState.entrySet()) {
|
||||
Map<UUID, CardState> lkiMap = new HashMap<>();
|
||||
for (Entry<UUID, CardState> entryMap : entry.getValue().entrySet()) {
|
||||
lkiMap.put(entryMap.getKey(), entryMap.getValue().copy());
|
||||
}
|
||||
this.lkiCardState.put(entry.getKey(), lkiMap);
|
||||
}
|
||||
// lkiExtended
|
||||
for (Entry<UUID, Map<Integer, MageObject>> entry : game.lkiExtended.entrySet()) {
|
||||
Map<Integer, MageObject> lkiMap = new HashMap<>();
|
||||
for (Entry<Integer, MageObject> entryMap : entry.getValue().entrySet()) {
|
||||
lkiMap.put(entryMap.getKey(), entryMap.getValue().copy());
|
||||
}
|
||||
this.lkiExtended.put(entry.getKey(), lkiMap);
|
||||
}
|
||||
// lkiShortLiving
|
||||
for (Entry<Zone, Set<UUID>> entry : game.lkiShortLiving.entrySet()) {
|
||||
this.lkiShortLiving.put(entry.getKey(), new HashSet<>(entry.getValue()));
|
||||
}
|
||||
|
||||
for (Entry<UUID, Permanent> entry : game.permanentsEntering.entrySet()) {
|
||||
this.permanentsEntering.put(entry.getKey(), entry.getValue().copy());
|
||||
}
|
||||
for (Entry<UUID, Counters> entry : game.enterWithCounters.entrySet()) {
|
||||
this.enterWithCounters.put(entry.getKey(), entry.getValue().copy());
|
||||
}
|
||||
|
||||
this.state = game.state.copy();
|
||||
// client-server part, not need on copy/simulations:
|
||||
/*
|
||||
this.savedStates = game.savedStates;
|
||||
this.gameStates = game.gameStates;
|
||||
this.gameStatesRollBack = game.gameStatesRollBack;
|
||||
this.executingRollback = game.executingRollback;
|
||||
this.turnToGoToForRollback = game.turnToGoToForRollback;
|
||||
*/
|
||||
|
||||
this.startTime = game.startTime;
|
||||
this.endTime = game.endTime;
|
||||
this.startingPlayerId = game.startingPlayerId;
|
||||
this.winnerId = game.winnerId;
|
||||
this.gameStopped = game.gameStopped;
|
||||
|
||||
this.range = game.range;
|
||||
this.mulligan = game.mulligan.copy();
|
||||
|
||||
this.attackOption = game.attackOption;
|
||||
this.gameOptions = game.gameOptions.copy();
|
||||
this.startMessage = game.startMessage;
|
||||
|
||||
this.scopeRelevant = game.scopeRelevant;
|
||||
this.saveGame = game.saveGame;
|
||||
this.priorityTime = game.priorityTime;
|
||||
this.startingLife = game.startingLife;
|
||||
this.startingHandSize = game.startingHandSize;
|
||||
//this.playerList = game.playerList; // auto-generated list, don't copy
|
||||
|
||||
// loop check code, no need to copy
|
||||
/*
|
||||
this.infiniteLoopCounter = game.infiniteLoopCounter;
|
||||
this.lastNumberOfAbilitiesOnTheStack = game.lastNumberOfAbilitiesOnTheStack;
|
||||
this.lastPlayersLifes = game.lastPlayersLifes;
|
||||
this.stackObjectsCheck = game.stackObjectsCheck;
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -605,7 +673,7 @@ public abstract class GameImpl implements Game {
|
|||
// copied cards removes, but delayed triggered possible from it, see https://github.com/magefree/mage/issues/5437
|
||||
// TODO: remove that workround after LKI rework, see GameState.copyCard
|
||||
if (card == null) {
|
||||
card = (Card) state.getValue(GameState.COPIED_CARD_KEY + cardId.toString());
|
||||
card = (Card) state.getValue(GameState.COPIED_CARD_KEY + cardId);
|
||||
}
|
||||
return card;
|
||||
}
|
||||
|
@ -845,7 +913,6 @@ public abstract class GameImpl implements Game {
|
|||
public void start(UUID choosingPlayerId) {
|
||||
startTime = new Date();
|
||||
if (state.getPlayers().values().iterator().hasNext()) {
|
||||
scorePlayer = state.getPlayers().values().iterator().next();
|
||||
init(choosingPlayerId);
|
||||
play(startingPlayerId);
|
||||
}
|
||||
|
@ -1039,7 +1106,7 @@ public abstract class GameImpl implements Game {
|
|||
for (Ability ability : card.getAbilities(this)) {
|
||||
if (ability instanceof CompanionAbility) {
|
||||
CompanionAbility companionAbility = (CompanionAbility) ability;
|
||||
if (companionAbility.isLegal(new HashSet<>(player.getLibrary().getCards(this)), startingSize)) {
|
||||
if (companionAbility.isLegal(new HashSet<>(player.getLibrary().getCards(this)), startingHandSize)) {
|
||||
potentialCompanions.add(card);
|
||||
break;
|
||||
}
|
||||
|
@ -1123,7 +1190,7 @@ public abstract class GameImpl implements Game {
|
|||
for (UUID playerId : state.getPlayerList(startingPlayerId)) {
|
||||
Player player = getPlayer(playerId);
|
||||
if (!gameOptions.testMode || player.getLife() == 0) {
|
||||
player.initLife(this.getLife());
|
||||
player.initLife(this.getStartingLife());
|
||||
}
|
||||
if (!gameOptions.testMode) {
|
||||
player.drawCards(startingHandSize, null, this);
|
||||
|
@ -3128,7 +3195,7 @@ public abstract class GameImpl implements Game {
|
|||
|
||||
@Override
|
||||
public boolean getShortLivingLKI(UUID objectId, Zone zone) {
|
||||
Set<UUID> idSet = shortLivingLKI.get(zone);
|
||||
Set<UUID> idSet = lkiShortLiving.get(zone);
|
||||
if (idSet != null) {
|
||||
return idSet.contains(objectId);
|
||||
}
|
||||
|
@ -3153,7 +3220,7 @@ public abstract class GameImpl implements Game {
|
|||
// remembers if a object was in a zone during the resolution of an effect
|
||||
// e.g. Wrath destroys all and you the question is is the replacement effect to apply because the source was also moved by the same effect
|
||||
// because it happens all at the same time the replacement effect has still to be applied
|
||||
Set<UUID> idSet = shortLivingLKI.computeIfAbsent(zone, k -> new HashSet<>());
|
||||
Set<UUID> idSet = lkiShortLiving.computeIfAbsent(zone, k -> new HashSet<>());
|
||||
idSet.add(objectId);
|
||||
if (object instanceof Permanent) {
|
||||
Map<Integer, MageObject> lkiExtendedMap = lkiExtended.computeIfAbsent(objectId, k -> new HashMap<>());
|
||||
|
@ -3184,7 +3251,7 @@ public abstract class GameImpl implements Game {
|
|||
|
||||
@Override
|
||||
public void resetShortLivingLKI() {
|
||||
shortLivingLKI.clear();
|
||||
lkiShortLiving.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3336,16 +3403,6 @@ public abstract class GameImpl implements Game {
|
|||
playerQueryEventSource.informPlayer(player.getId(), message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getStateCheckRequired() {
|
||||
return stateCheckRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStateCheckRequired() {
|
||||
stateCheckRequired = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, only self scope replacement effects are applied
|
||||
*
|
||||
|
@ -3420,8 +3477,8 @@ public abstract class GameImpl implements Game {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getLife() {
|
||||
return startLife;
|
||||
public int getStartingLife() {
|
||||
return startingLife;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package mage.game;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.util.Copyable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
|
@ -12,7 +13,7 @@ import java.util.Set;
|
|||
*
|
||||
* @author ayratn
|
||||
*/
|
||||
public class GameOptions implements Serializable {
|
||||
public class GameOptions implements Serializable, Copyable<GameOptions> {
|
||||
|
||||
private static final GameOptions deinstance = new GameOptions();
|
||||
|
||||
|
@ -50,10 +51,28 @@ public class GameOptions implements Serializable {
|
|||
* Names of users banned from participating in the game
|
||||
*/
|
||||
public Set<String> bannedUsers = Collections.emptySet();
|
||||
|
||||
|
||||
/**
|
||||
* Use planechase variant
|
||||
*/
|
||||
public boolean planeChase = false;
|
||||
|
||||
public GameOptions() {
|
||||
super();
|
||||
}
|
||||
|
||||
private GameOptions(final GameOptions options) {
|
||||
this.testMode = options.testMode;
|
||||
this.stopOnTurn = options.stopOnTurn;
|
||||
this.stopAtStep = options.stopAtStep;
|
||||
this.skipInitShuffling = options.skipInitShuffling;
|
||||
this.rollbackTurnsAllowed = options.rollbackTurnsAllowed;
|
||||
this.bannedUsers.addAll(options.bannedUsers);
|
||||
this.planeChase = options.planeChase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameOptions copy() {
|
||||
return new GameOptions(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -440,7 +440,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
this.canLoseLife = true;
|
||||
this.topCardRevealed = false;
|
||||
this.payManaMode = false;
|
||||
this.setLife(game.getLife(), game, null);
|
||||
this.setLife(game.getStartingLife(), game, null);
|
||||
this.setReachedNextTurnAfterLeaving(false);
|
||||
|
||||
this.clearCastSourceIdManaCosts();
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
|
||||
|
||||
package mage.util;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
@FunctionalInterface
|
||||
|
|
Loading…
Reference in a new issue