diff --git a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java index 21f3a61a609..2e0340854ac 100644 --- a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java +++ b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java @@ -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) { diff --git a/Mage.Sets/src/mage/cards/a/AjaniStrengthOfThePride.java b/Mage.Sets/src/mage/cards/a/AjaniStrengthOfThePride.java index c3b9095d31d..d0f076bbe36 100644 --- a/Mage.Sets/src/mage/cards/a/AjaniStrengthOfThePride.java +++ b/Mage.Sets/src/mage/cards/a/AjaniStrengthOfThePride.java @@ -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); diff --git a/Mage.Sets/src/mage/cards/a/AngelOfDestiny.java b/Mage.Sets/src/mage/cards/a/AngelOfDestiny.java index cedbe2d9ea5..77b2c0e9d5a 100644 --- a/Mage.Sets/src/mage/cards/a/AngelOfDestiny.java +++ b/Mage.Sets/src/mage/cards/a/AngelOfDestiny.java @@ -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; } } diff --git a/Mage.Sets/src/mage/cards/a/AnyaMercilessAngel.java b/Mage.Sets/src/mage/cards/a/AnyaMercilessAngel.java index 2accd0b07c8..b1bdf5f02fe 100644 --- a/Mage.Sets/src/mage/cards/a/AnyaMercilessAngel.java +++ b/Mage.Sets/src/mage/cards/a/AnyaMercilessAngel.java @@ -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) { diff --git a/Mage.Sets/src/mage/cards/a/AyliEternalPilgrim.java b/Mage.Sets/src/mage/cards/a/AyliEternalPilgrim.java index 6c99187647a..ca711e757c5 100644 --- a/Mage.Sets/src/mage/cards/a/AyliEternalPilgrim.java +++ b/Mage.Sets/src/mage/cards/a/AyliEternalPilgrim.java @@ -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; } diff --git a/Mage.Sets/src/mage/cards/c/ChaliceOfLife.java b/Mage.Sets/src/mage/cards/c/ChaliceOfLife.java index 9d106207ade..50e16626431 100644 --- a/Mage.Sets/src/mage/cards/c/ChaliceOfLife.java +++ b/Mage.Sets/src/mage/cards/c/ChaliceOfLife.java @@ -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()); } diff --git a/Mage.Sets/src/mage/cards/c/CosmosElixir.java b/Mage.Sets/src/mage/cards/c/CosmosElixir.java index 038177976a9..830d3f325db 100644 --- a/Mage.Sets/src/mage/cards/c/CosmosElixir.java +++ b/Mage.Sets/src/mage/cards/c/CosmosElixir.java @@ -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(); } } diff --git a/Mage.Sets/src/mage/cards/e/ExquisiteArchangel.java b/Mage.Sets/src/mage/cards/e/ExquisiteArchangel.java index 40f66d3605b..013cc8b4e4e 100644 --- a/Mage.Sets/src/mage/cards/e/ExquisiteArchangel.java +++ b/Mage.Sets/src/mage/cards/e/ExquisiteArchangel.java @@ -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; diff --git a/Mage.Sets/src/mage/cards/g/GyrudaDoomOfDepths.java b/Mage.Sets/src/mage/cards/g/GyrudaDoomOfDepths.java index aec97874ad3..dc715790dfb 100644 --- a/Mage.Sets/src/mage/cards/g/GyrudaDoomOfDepths.java +++ b/Mage.Sets/src/mage/cards/g/GyrudaDoomOfDepths.java @@ -60,7 +60,7 @@ enum GyrudaDoomOfDepthsCompanionCondition implements CompanionCondition { } @Override - public boolean isLegal(Set deck, int startingSize) { + public boolean isLegal(Set deck, int startingHandSize) { return deck .stream() .mapToInt(MageObject::getManaValue) diff --git a/Mage.Sets/src/mage/cards/h/HappilyEverAfter.java b/Mage.Sets/src/mage/cards/h/HappilyEverAfter.java index f4f010f7be8..edd89de7cf1 100644 --- a/Mage.Sets/src/mage/cards/h/HappilyEverAfter.java +++ b/Mage.Sets/src/mage/cards/h/HappilyEverAfter.java @@ -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(""); diff --git a/Mage.Sets/src/mage/cards/j/JeganthaTheWellspring.java b/Mage.Sets/src/mage/cards/j/JeganthaTheWellspring.java index a18abde231f..fc7a5cf1b95 100644 --- a/Mage.Sets/src/mage/cards/j/JeganthaTheWellspring.java +++ b/Mage.Sets/src/mage/cards/j/JeganthaTheWellspring.java @@ -63,7 +63,7 @@ enum JeganthaTheWellspringCompanionCondition implements CompanionCondition { } @Override - public boolean isLegal(Set deck, int startingSize) { + public boolean isLegal(Set deck, int startingHandSize) { return deck.stream().noneMatch(JeganthaTheWellspringCompanionCondition::checkCard); } diff --git a/Mage.Sets/src/mage/cards/k/KaheeraTheOrphanguard.java b/Mage.Sets/src/mage/cards/k/KaheeraTheOrphanguard.java index c6c0b3580dc..90a868ddd7b 100644 --- a/Mage.Sets/src/mage/cards/k/KaheeraTheOrphanguard.java +++ b/Mage.Sets/src/mage/cards/k/KaheeraTheOrphanguard.java @@ -96,7 +96,7 @@ enum KaheeraTheOrphanguardCompanionCondition implements CompanionCondition { } @Override - public boolean isLegal(Set deck, int startingSize) { + public boolean isLegal(Set deck, int startingHandSize) { return deck.stream() .filter(card -> card.hasCardTypeForDeckbuilding(CardType.CREATURE)) .allMatch(KaheeraTheOrphanguardCompanionCondition::isCardLegal); diff --git a/Mage.Sets/src/mage/cards/k/KerugaTheMacrosage.java b/Mage.Sets/src/mage/cards/k/KerugaTheMacrosage.java index 0abde82c078..fade4c0b3df 100644 --- a/Mage.Sets/src/mage/cards/k/KerugaTheMacrosage.java +++ b/Mage.Sets/src/mage/cards/k/KerugaTheMacrosage.java @@ -66,7 +66,7 @@ enum KerugaCondition implements CompanionCondition { } @Override - public boolean isLegal(Set deck, int startingSize) { + public boolean isLegal(Set deck, int startingHandSize) { return deck.stream().allMatch(card -> card.isLand() || card.getManaValue() >= 3); } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/l/LongRest.java b/Mage.Sets/src/mage/cards/l/LongRest.java index 427b604f83a..6c33c98d469 100644 --- a/Mage.Sets/src/mage/cards/l/LongRest.java +++ b/Mage.Sets/src/mage/cards/l/LongRest.java @@ -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; } diff --git a/Mage.Sets/src/mage/cards/l/LurrusOfTheDreamDen.java b/Mage.Sets/src/mage/cards/l/LurrusOfTheDreamDen.java index 50311b57549..0646597eb48 100644 --- a/Mage.Sets/src/mage/cards/l/LurrusOfTheDreamDen.java +++ b/Mage.Sets/src/mage/cards/l/LurrusOfTheDreamDen.java @@ -67,7 +67,7 @@ enum LurrusOfTheDreamDenCompanionCondition implements CompanionCondition { } @Override - public boolean isLegal(Set deck, int startingSize) { + public boolean isLegal(Set deck, int startingHandSize) { return deck.stream() .filter(card -> card.isPermanent()) .mapToInt(MageObject::getManaValue) diff --git a/Mage.Sets/src/mage/cards/l/LutriTheSpellchaser.java b/Mage.Sets/src/mage/cards/l/LutriTheSpellchaser.java index c8b377537bf..523117ba913 100644 --- a/Mage.Sets/src/mage/cards/l/LutriTheSpellchaser.java +++ b/Mage.Sets/src/mage/cards/l/LutriTheSpellchaser.java @@ -83,7 +83,7 @@ enum LutriTheSpellchaserCompanionCondition implements CompanionCondition { } @Override - public boolean isLegal(Set deck, int startingSize) { + public boolean isLegal(Set deck, int startingHandSize) { Map cardMap = new HashMap<>(); deck.stream() .filter(card -> !card.isLand()) diff --git a/Mage.Sets/src/mage/cards/o/OboshThePreypiercer.java b/Mage.Sets/src/mage/cards/o/OboshThePreypiercer.java index 593d445aa4c..231739eb43d 100644 --- a/Mage.Sets/src/mage/cards/o/OboshThePreypiercer.java +++ b/Mage.Sets/src/mage/cards/o/OboshThePreypiercer.java @@ -58,7 +58,7 @@ enum OboshThePreypiercerCompanionCondition implements CompanionCondition { } @Override - public boolean isLegal(Set deck, int startingSize) { + public boolean isLegal(Set deck, int startingHandSize) { return deck .stream() .filter(card -> !card.isLand()) diff --git a/Mage.Sets/src/mage/cards/o/OketrasLastMercy.java b/Mage.Sets/src/mage/cards/o/OketrasLastMercy.java index 9fc6cade773..9e52bd9f9c6 100644 --- a/Mage.Sets/src/mage/cards/o/OketrasLastMercy.java +++ b/Mage.Sets/src/mage/cards/o/OketrasLastMercy.java @@ -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; diff --git a/Mage.Sets/src/mage/cards/p/PathOfBravery.java b/Mage.Sets/src/mage/cards/p/PathOfBravery.java index 0855fed0072..5c11488da34 100644 --- a/Mage.Sets/src/mage/cards/p/PathOfBravery.java +++ b/Mage.Sets/src/mage/cards/p/PathOfBravery.java @@ -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; } diff --git a/Mage.Sets/src/mage/cards/r/ResoluteArchangel.java b/Mage.Sets/src/mage/cards/r/ResoluteArchangel.java index 9c87de76855..a2f45ca67fb 100644 --- a/Mage.Sets/src/mage/cards/r/ResoluteArchangel.java +++ b/Mage.Sets/src/mage/cards/r/ResoluteArchangel.java @@ -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; } diff --git a/Mage.Sets/src/mage/cards/r/RighteousValkyrie.java b/Mage.Sets/src/mage/cards/r/RighteousValkyrie.java index 4b3cb6782e1..6caaf1bad32 100644 --- a/Mage.Sets/src/mage/cards/r/RighteousValkyrie.java +++ b/Mage.Sets/src/mage/cards/r/RighteousValkyrie.java @@ -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; } } diff --git a/Mage.Sets/src/mage/cards/s/SpeakerOfTheHeavens.java b/Mage.Sets/src/mage/cards/s/SpeakerOfTheHeavens.java index fc43ccaae9d..a2e8a69cf5c 100644 --- a/Mage.Sets/src/mage/cards/s/SpeakerOfTheHeavens.java +++ b/Mage.Sets/src/mage/cards/s/SpeakerOfTheHeavens.java @@ -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; diff --git a/Mage.Sets/src/mage/cards/t/TorgaarFamineIncarnate.java b/Mage.Sets/src/mage/cards/t/TorgaarFamineIncarnate.java index ade0bd777f8..0ff894bd023 100644 --- a/Mage.Sets/src/mage/cards/t/TorgaarFamineIncarnate.java +++ b/Mage.Sets/src/mage/cards/t/TorgaarFamineIncarnate.java @@ -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; diff --git a/Mage.Sets/src/mage/cards/u/UmoriTheCollector.java b/Mage.Sets/src/mage/cards/u/UmoriTheCollector.java index 9e740c5d269..13cff2b991b 100644 --- a/Mage.Sets/src/mage/cards/u/UmoriTheCollector.java +++ b/Mage.Sets/src/mage/cards/u/UmoriTheCollector.java @@ -64,7 +64,7 @@ enum UmoriCondition implements CompanionCondition { } @Override - public boolean isLegal(Set deck, int startingSize) { + public boolean isLegal(Set deck, int startingHandSize) { Set cardTypes = new HashSet<>(); for (Card card : deck) { // Lands are fine. diff --git a/Mage.Sets/src/mage/cards/y/YorionSkyNomad.java b/Mage.Sets/src/mage/cards/y/YorionSkyNomad.java index a25c2e61cbf..66b3843287d 100644 --- a/Mage.Sets/src/mage/cards/y/YorionSkyNomad.java +++ b/Mage.Sets/src/mage/cards/y/YorionSkyNomad.java @@ -72,8 +72,8 @@ enum YorionSkyNomadCompanionCondition implements CompanionCondition { } @Override - public boolean isLegal(Set deck, int startingSize) { - return deck.size() >= startingSize + 20; + public boolean isLegal(Set deck, int startingHandSize) { + return deck.size() >= startingHandSize + 20; } } diff --git a/Mage.Sets/src/mage/cards/z/ZirdaTheDawnwaker.java b/Mage.Sets/src/mage/cards/z/ZirdaTheDawnwaker.java index fbc568f2e27..508e1bacf51 100644 --- a/Mage.Sets/src/mage/cards/z/ZirdaTheDawnwaker.java +++ b/Mage.Sets/src/mage/cards/z/ZirdaTheDawnwaker.java @@ -73,7 +73,7 @@ enum ZirdaTheDawnwakerCompanionCondition implements CompanionCondition { } @Override - public boolean isLegal(Set deck, int startingSize) { + public boolean isLegal(Set deck, int startingHandSize) { return deck .stream() .filter(card -> card.isPermanent()) diff --git a/Mage/src/main/java/mage/MageObject.java b/Mage/src/main/java/mage/MageObject.java index fa92eeb7006..970fc3627ce 100644 --- a/Mage/src/main/java/mage/MageObject.java +++ b/Mage/src/main/java/mage/MageObject.java @@ -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 { 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) diff --git a/Mage/src/main/java/mage/abilities/keyword/CompanionAbility.java b/Mage/src/main/java/mage/abilities/keyword/CompanionAbility.java index 23d90378b9e..ca3a36fe85a 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CompanionAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CompanionAbility.java @@ -41,8 +41,8 @@ public class CompanionAbility extends SpecialAction { return "Companion — " + companionCondition.getRule(); } - public boolean isLegal(Set cards, int startingSize) { - return companionCondition.isLegal(cards, startingSize); + public boolean isLegal(Set cards, int startingHandSize) { + return companionCondition.isLegal(cards, startingHandSize); } } diff --git a/Mage/src/main/java/mage/abilities/keyword/CompanionCondition.java b/Mage/src/main/java/mage/abilities/keyword/CompanionCondition.java index ad470d3b3c5..d00441681c4 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CompanionCondition.java +++ b/Mage/src/main/java/mage/abilities/keyword/CompanionCondition.java @@ -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 deck, int startingSize); + boolean isLegal(Set deck, int startingHandSize); } diff --git a/Mage/src/main/java/mage/actions/MageDrawAction.java b/Mage/src/main/java/mage/actions/MageDrawAction.java index c3d0c8760af..fb368f55c45 100644 --- a/Mage/src/main/java/mage/actions/MageDrawAction.java +++ b/Mage/src/main/java/mage/actions/MageDrawAction.java @@ -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; } diff --git a/Mage/src/main/java/mage/cards/MeldCard.java b/Mage/src/main/java/mage/cards/MeldCard.java index 05355ec5ab2..7cc5988110d 100644 --- a/Mage/src/main/java/mage/cards/MeldCard.java +++ b/Mage/src/main/java/mage/cards/MeldCard.java @@ -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); } diff --git a/Mage/src/main/java/mage/designations/Designation.java b/Mage/src/main/java/mage/designations/Designation.java index f2608d48179..142daffc9fc 100644 --- a/Mage/src/main/java/mage/designations/Designation.java +++ b/Mage/src/main/java/mage/designations/Designation.java @@ -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 { +public abstract class Designation implements MageObject { private static final List emptySet = new ArrayList<>(); private static final ObjectColor emptyColor = new ObjectColor(); @@ -69,6 +68,9 @@ public abstract class Designation implements MageObject, Copyable { this.unique = designation.unique; } + @Override + public abstract Designation copy(); + @Override public FrameStyle getFrameStyle() { return frameStyle; diff --git a/Mage/src/main/java/mage/game/CardState.java b/Mage/src/main/java/mage/game/CardState.java index 8302c100a17..a5569f4e71d 100644 --- a/Mage/src/main/java/mage/game/CardState.java +++ b/Mage/src/main/java/mage/game/CardState.java @@ -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 { protected boolean faceDown; protected Map info; @@ -50,6 +51,7 @@ public class CardState implements Serializable { this.melded = state.melded; } + @Override public CardState copy() { return new CardState(this); } diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index e8f0d8862da..914a397dc47 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -51,7 +51,7 @@ public interface Game extends MageItem, Serializable, Copyable { int getNumPlayers(); - int getLife(); + int getStartingLife(); RangeOfInfluence getRangeOfInfluence(); @@ -244,10 +244,6 @@ public interface Game extends MageItem, Serializable, Copyable { Player getLosingPlayer(); - void setStateCheckRequired(); - - boolean getStateCheckRequired(); - //client event methods void addTableEventListener(Listener listener); diff --git a/Mage/src/main/java/mage/game/GameCommanderImpl.java b/Mage/src/main/java/mage/game/GameCommanderImpl.java index cce2beeb371..da0c0ebfe04 100644 --- a/Mage/src/main/java/mage/game/GameCommanderImpl.java +++ b/Mage/src/main/java/mage/game/GameCommanderImpl.java @@ -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) { diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index cd2c4fe71a6..4d1cb8e3bf1 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -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) + *

+ * "transient field" logic uses for serialization/replays (mark temporary fields as transient, + * also look for non restored fields in copy constructor for details) + *

+ * 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> lkiCardState = new EnumMap<>(Zone.class); protected Map> 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> shortLivingLKI = new EnumMap<>(Zone.class); + protected Map> lkiShortLiving = new EnumMap<>(Zone.class); // Permanents entering the Battlefield while handling replacement effects before they are added to the battlefield protected Map permanentsEntering = new HashMap<>(); + // used to set the counters a permanent adds the battlefield (if no replacement effect is used e.g. Persist) + protected Map enterWithCounters = new HashMap<>(); protected GameState state; private transient Stack savedStates = new Stack<>(); protected transient GameStates gameStates = new GameStates(); + // game states to allow player rollback protected transient Map 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 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 lastPlayersLifes = null; // if life is going down, it's no infinite loop - private final LinkedList 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 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 lastPlayersLifes = null; // if life is going down, it's no infinite loop + private transient final LinkedList stackObjectsCheck = new LinkedList<>(); // used to check if different sources used the stack // temporary store for income concede commands, don't copy private final LinkedList 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 entry : game.gameCards.entrySet()) { + this.gameCards.put(entry.getKey(), entry.getValue().copy()); + } + for (Entry entry : game.meldCards.entrySet()) { + this.meldCards.put(entry.getKey(), entry.getValue().copy()); + } + + // lki + for (Entry> entry : game.lki.entrySet()) { + Map lkiMap = new HashMap<>(); + for (Entry entryMap : entry.getValue().entrySet()) { + lkiMap.put(entryMap.getKey(), entryMap.getValue().copy()); + } + this.lki.put(entry.getKey(), lkiMap); + } + // lkiCardState + for (Entry> entry : game.lkiCardState.entrySet()) { + Map lkiMap = new HashMap<>(); + for (Entry entryMap : entry.getValue().entrySet()) { + lkiMap.put(entryMap.getKey(), entryMap.getValue().copy()); + } + this.lkiCardState.put(entry.getKey(), lkiMap); + } + // lkiExtended + for (Entry> entry : game.lkiExtended.entrySet()) { + Map lkiMap = new HashMap<>(); + for (Entry entryMap : entry.getValue().entrySet()) { + lkiMap.put(entryMap.getKey(), entryMap.getValue().copy()); + } + this.lkiExtended.put(entry.getKey(), lkiMap); + } + // lkiShortLiving + for (Entry> entry : game.lkiShortLiving.entrySet()) { + this.lkiShortLiving.put(entry.getKey(), new HashSet<>(entry.getValue())); + } + + for (Entry entry : game.permanentsEntering.entrySet()) { + this.permanentsEntering.put(entry.getKey(), entry.getValue().copy()); + } + for (Entry 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 idSet = shortLivingLKI.get(zone); + Set 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 idSet = shortLivingLKI.computeIfAbsent(zone, k -> new HashSet<>()); + Set idSet = lkiShortLiving.computeIfAbsent(zone, k -> new HashSet<>()); idSet.add(objectId); if (object instanceof Permanent) { Map 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 diff --git a/Mage/src/main/java/mage/game/GameOptions.java b/Mage/src/main/java/mage/game/GameOptions.java index 81bf98babf1..f3e7c6d4752 100644 --- a/Mage/src/main/java/mage/game/GameOptions.java +++ b/Mage/src/main/java/mage/game/GameOptions.java @@ -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 { 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 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); + } } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 9f17e6cee31..7bcf3a53c2c 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -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(); diff --git a/Mage/src/main/java/mage/util/Copyable.java b/Mage/src/main/java/mage/util/Copyable.java index e5a2216f438..16bea463c6e 100644 --- a/Mage/src/main/java/mage/util/Copyable.java +++ b/Mage/src/main/java/mage/util/Copyable.java @@ -1,9 +1,6 @@ - - package mage.util; /** - * * @author BetaSteward_at_googlemail.com */ @FunctionalInterface