diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 2ad89291285..7c2a25b1efe 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -468,8 +468,7 @@ public class AiController { } } } - - return player.canPlayLand(c); + return Iterables.any(c.getAllPossibleAbilities(player, true), SpellAbility::isLandAbility); }); return landList; } @@ -1376,30 +1375,12 @@ public class AiController { Card land = chooseBestLandToPlay(landsWannaPlay); if ((!player.canLoseLife() || player.cantLoseForZeroOrLessLife() || ComputerUtil.getDamageFromETB(player, land) < player.getLife()) && (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land))) { - final List abilities = Lists.newArrayList(); + final List abilities = land.getAllPossibleAbilities(player, true); + // skip non Land Abilities + abilities.removeIf(sa -> !sa.isLandAbility()); - // TODO extend this logic to evaluate MDFC with both sides land - // this can only happen if its a MDFC land - if (!land.isLand()) { - land.setState(CardStateName.Modal, true); - land.setBackSide(true); - } - - LandAbility la = new LandAbility(land, player, null); - la.setCardState(land.getCurrentState()); - if (la.canPlay()) { - abilities.add(la); - } - - // add mayPlay option - for (CardPlayOption o : land.mayPlay(player)) { - la = new LandAbility(land, player, o); - la.setCardState(land.getCurrentState()); - if (la.canPlay()) { - abilities.add(la); - } - } if (!abilities.isEmpty()) { + // TODO extend this logic to evaluate MDFC with both sides land return abilities; } } @@ -1570,7 +1551,7 @@ public class AiController { Iterables.removeIf(saList, spellAbility -> { //don't include removedAI cards if somehow the AI can play the ability or gain control of unsupported card // TODO allow when experimental profile? - return spellAbility instanceof LandAbility || (spellAbility.getHostCard() != null && ComputerUtilCard.isCardRemAIDeck(spellAbility.getHostCard())); + return spellAbility.isLandAbility() || (spellAbility.getHostCard() != null && ComputerUtilCard.isCardRemAIDeck(spellAbility.getHostCard())); }); //update LivingEndPlayer useLivingEnd = Iterables.any(player.getZone(ZoneType.Library), CardPredicates.nameEquals("Living End")); diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java index 1add3ce299d..d316ea836a5 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java @@ -45,7 +45,7 @@ public class ComputerUtilAbility { return false; } } - return player.canPlayLand(c); + return player.canPlayLand(c, false, c.getFirstSpellAbility()); }); final CardCollection landsNotInHand = new CardCollection(player.getCardsIn(ZoneType.Graveyard)); diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 2c15791a6d6..b1a282a20b8 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -810,7 +810,7 @@ public class PlayerControllerAi extends PlayerController { @Override public boolean playChosenSpellAbility(SpellAbility sa) { - if (sa instanceof LandAbility) { + if (sa.isLandAbility()) { if (sa.canPlay()) { sa.resolve(); } diff --git a/forge-ai/src/main/java/forge/ai/ability/DiscoverAi.java b/forge-ai/src/main/java/forge/ai/ability/DiscoverAi.java index 9541d8266d5..c0ac69a99b7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DiscoverAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DiscoverAi.java @@ -8,7 +8,7 @@ import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; -import forge.game.spellability.LandAbility; + import forge.game.spellability.Spell; import forge.game.spellability.SpellAbility; @@ -45,7 +45,7 @@ public class DiscoverAi extends SpellAbilityAi { public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map params) { Card c = (Card)params.get("Card"); for (SpellAbility s : AbilityUtils.getBasicSpellsFromPlayEffect(c, ai)) { - if (s instanceof LandAbility) { + if (s.isLandAbility()) { // return false or we get a ClassCastException later if the AI encounters MDFC with land backside return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/PlayAi.java b/forge-ai/src/main/java/forge/ai/ability/PlayAi.java index 5736c4f8486..5e7e9c482ff 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PlayAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PlayAi.java @@ -154,7 +154,7 @@ public class PlayAi extends SpellAbilityAi { if (!sa.matchesValidParam("ValidSA", s)) { continue; } - if (s instanceof LandAbility) { + if (s.isLandAbility()) { // might want to run some checks here but it's rare anyway return true; } diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java index e78415429a9..c6b354ef553 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java @@ -1,6 +1,6 @@ package forge.ai.simulation; -import forge.game.spellability.LandAbility; + import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -158,9 +158,9 @@ public class GameSimulator { } public Score simulateSpellAbility(SpellAbility origSa, GameStateEvaluator eval, boolean resolve) { SpellAbility sa; - if (origSa instanceof LandAbility) { + if (origSa.isLandAbility()) { Card hostCard = (Card) copier.find(origSa.getHostCard()); - if (!aiPlayer.playLand(hostCard, false)) { + if (!aiPlayer.playLand(hostCard, false, origSa)) { System.err.println("Simulation: Couldn't play land! " + origSa); } sa = origSa; diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java b/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java index de05636ddc8..eeab7621bb0 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java @@ -260,7 +260,10 @@ public class GameStateEvaluator { // The value should be more than the value of having a card in hand, so if a land has an // activated ability but not a mana ability, it will still be played. for (SpellAbility m: c.getNonManaAbilities()) { - if (!m.getPayCosts().hasTapCost()) { + if (m.isLandAbility()) { + // Land Ability has no extra Score + continue; + } if (!m.getPayCosts().hasTapCost()) { // probably a manland, rate it higher than a rainbow land value += 25; } else if (m.getPayCosts().hasSpecificCostType(CostSacrifice.class)) { diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java index 28dfb46d184..483449ace0a 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java @@ -24,7 +24,7 @@ import forge.game.card.CardPredicates; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.AbilitySub; -import forge.game.spellability.LandAbility; + import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityCondition; import forge.game.zone.ZoneType; @@ -136,7 +136,7 @@ public class SpellAbilityPicker { private static boolean isSorcerySpeed(SpellAbility sa, Player player) { // TODO: Can we use the actual rules engine for this instead of trying to do the logic ourselves? - if (sa instanceof LandAbility) { + if (sa.isLandAbility()) { return true; } if (sa.isSpell()) { @@ -327,16 +327,16 @@ public class SpellAbilityPicker { } private AiPlayDecision canPlayAndPayForSim(final SpellAbility sa) { - if (!sa.isLegalAfterStack()) { - return AiPlayDecision.CantPlaySa; - } if (!sa.checkRestrictions(sa.getHostCard(), player)) { return AiPlayDecision.CantPlaySa; } - if (sa instanceof LandAbility) { + if (sa.isLandAbility()) { return AiPlayDecision.WillPlay; } + if (!sa.isLegalAfterStack()) { + return AiPlayDecision.CantPlaySa; + } if (!sa.canPlay()) { return AiPlayDecision.CantPlaySa; } diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index 96aaabeaee9..4c33fc82c94 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -170,7 +170,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView, * * @return a boolean. */ - public final boolean isSecondary() { + public boolean isSecondary() { return getParamOrDefault("Secondary", "False").equals("True"); } diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index d39e690c43a..e321a159155 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -91,10 +91,10 @@ public final class GameActionUtil { return alternatives; } - if (sa.isSpell()) { + if (sa.isSpell() || sa.isLandAbility()) { boolean lkicheck = false; - Card newHost = ((Spell)sa).getAlternateHost(source); + Card newHost = sa.getAlternateHost(source); if (newHost != null) { source = newHost; lkicheck = true; diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index c5ff9a72f14..0ed80f17159 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -2953,7 +2953,7 @@ public class AbilityUtils { } for (SpellAbility s : list) { - if (s instanceof LandAbility) { + if (s.isLandAbility()) { // CR 305.3 if (controller.getGame().getPhaseHandler().isPlayerTurn(controller) && controller.canPlayLand(tgtCard, true, s)) { sas.add(s); @@ -2981,9 +2981,7 @@ public class AbilityUtils { private static void collectSpellsForPlayEffect(final List result, final CardState state, final Player controller, final boolean withAltCost) { if (state.getType().isLand()) { - LandAbility la = new LandAbility(state.getCard(), controller, null); - la.setCardState(state); - result.add(la); + result.add(state.getFirstSpellAbility()); } final Iterable spells = state.getSpellAbilities(); for (SpellAbility sa : spells) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/DiscoverEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DiscoverEffect.java index 0d9333ce947..31f38e3dd13 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DiscoverEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DiscoverEffect.java @@ -1,7 +1,5 @@ package forge.game.ability.effects; -import com.google.common.base.Predicates; -import com.google.common.collect.Iterables; import forge.game.Game; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; @@ -15,7 +13,7 @@ import forge.game.cost.CostPart; import forge.game.cost.CostReveal; import forge.game.player.Player; import forge.game.player.PlayerCollection; -import forge.game.spellability.LandAbility; + import forge.game.spellability.SpellAbility; import forge.game.trigger.TriggerType; import forge.game.zone.PlayerZone; @@ -94,7 +92,7 @@ public class DiscoverEffect extends SpellAbilityEffect { List sas = AbilityUtils.getBasicSpellsFromPlayEffect(found, p); // filter out land abilities due to MDFC or similar - Iterables.removeIf(sas, Predicates.instanceOf(LandAbility.class)); + sas.removeIf(sp -> sp.isLandAbility()); // the spell must also have a mana value equal to or less than the discover number sas.removeIf(sp -> sp.getPayCosts().getTotalMana().getCMC() > num); diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java index 4ceb5ea64ad..70f41014817 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java @@ -39,7 +39,7 @@ import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementLayer; import forge.game.spellability.AlternativeCost; -import forge.game.spellability.LandAbility; + import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityPredicates; import forge.game.zone.Zone; @@ -343,7 +343,7 @@ public class PlayEffect extends SpellAbilityEffect { final Zone originZone = tgtCard.getZone(); // lands will be played - if (tgtSA instanceof LandAbility) { + if (tgtSA.isLandAbility()) { tgtSA.resolve(); amount--; if (remember) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java index 8e68bf65b3d..6361442621f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java @@ -57,7 +57,7 @@ public class PlayLandVariantEffect extends SpellAbilityEffect { PaperCard ran = Aggregates.random(cards); random = CardFactory.getCard(ran, activator, game); cards.remove(ran); - } while (!activator.canPlayLand(random, false)); + } while (!activator.canPlayLand(random, false, random.getFirstSpellAbility())); source.addCloneState(CardFactory.getCloneStates(random, source, sa), game.getNextTimestamp()); source.updateStateForView(); diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 6e73894e478..f94ad7292d5 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -7430,7 +7430,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { for (SpellAbility sa : getState(CardStateName.Modal).getSpellAbilities()) { //add alternative costs as additional spell abilities // only add Spells there - if (sa.isSpell()) { + if (sa.isSpell() || sa.isLandAbility()) { abilities.add(sa); abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player, false)); } @@ -7466,106 +7466,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } abilities.removeAll(toRemove); - // Land Abilities below, move them to CardFactory after MayPlayRefactor - if (getLastKnownZone().is(ZoneType.Battlefield)) { - return abilities; - } - if (getState(CardStateName.Original).getType().isLand()) { - LandAbility la = new LandAbility(this, player, null); - la.setCardState(oState); - if (la.canPlay()) { - abilities.add(la); - } - - Card source = this; - boolean lkicheck = false; - - // if Card is Facedown, need to check if MayPlay still applies - if (isFaceDown()) { - lkicheck = true; - source = CardCopyService.getLKICopy(source); - source.forceTurnFaceUp(); - } - - if (lkicheck) { - // double freeze tracker, so it doesn't update view - game.getTracker().freeze(); - CardCollection preList = new CardCollection(source); - game.getAction().checkStaticAbilities(false, Sets.newHashSet(source), preList); - } - - // extra for MayPlay - for (CardPlayOption o : source.mayPlay(player)) { - la = new LandAbility(this, player, o); - la.setCardState(oState); - if (la.canPlay()) { - abilities.add(la); - } - } - - // reset static abilities - if (lkicheck) { - game.getAction().checkStaticAbilities(false); - // clear delayed changes, this check should not have updated the view - game.getTracker().clearDelayed(); - // need to unfreeze tracker - game.getTracker().unfreeze(); - } - } - - if (isModal() && hasState(CardStateName.Modal)) { - CardState modal = getState(CardStateName.Modal); - if (modal.getType().isLand()) { - LandAbility la = new LandAbility(this, player, null); - la.setCardState(modal); - - Card source = CardCopyService.getLKICopy(this); - boolean lkicheck = true; - - // if Card is Facedown, need to check if MayPlay still applies - if (isFaceDown()) { - source.forceTurnFaceUp(); - } - - // the modal state is not copied with lki, need to copy it extra - if (!source.hasState(CardStateName.Modal)) { - source.addAlternateState(CardStateName.Modal, false); - source.getState(CardStateName.Modal).copyFrom(this.getState(CardStateName.Modal), true); - } - - source.setSplitStateToPlayAbility(la); - - if (la.canPlay(source)) { - abilities.add(la); - } - - if (lkicheck) { - // double freeze tracker, so it doesn't update view - game.getTracker().freeze(); - CardCollection preList = new CardCollection(source); - game.getAction().checkStaticAbilities(false, Sets.newHashSet(source), preList); - } - - // extra for MayPlay - for (CardPlayOption o : source.mayPlay(player)) { - la = new LandAbility(this, player, o); - la.setCardState(modal); - if (la.canPlay(source)) { - abilities.add(la); - } - } - - // reset static abilities - if (lkicheck) { - game.getAction().checkStaticAbilities(false); - // clear delayed changes, this check should not have updated the view - game.getTracker().clearDelayed(); - // need to unfreeze tracker - game.getTracker().unfreeze(); - } - } - } - return abilities; } diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index 81885497755..dbf3ab681e5 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -413,17 +413,16 @@ public class CardFactory { // SpellPermanent only for Original State if (c.getCurrentStateName() == CardStateName.Original || c.getCurrentStateName() == CardStateName.Modal || c.getCurrentStateName().toString().startsWith("Specialize")) { - // this is the "default" spell for permanents like creatures and artifacts - if (c.isPermanent() && !c.isAura() && !c.isLand()) { + if (c.isLand()) { + SpellAbility sa = new LandAbility(c); + sa.setCardState(c.getCurrentState()); + c.addSpellAbility(sa); + } else if (c.isPermanent() && !c.isAura()) { + // this is the "default" spell for permanents like creatures and artifacts SpellAbility sa = new SpellPermanent(c); - - // Currently only for Modal, might react different when state is always set - //if (c.getCurrentStateName() == CardStateName.Modal) { - sa.setCardState(c.getCurrentState()); - //} + sa.setCardState(c.getCurrentState()); c.addSpellAbility(sa); } - // TODO add LandAbility there when refactor MayPlay } CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities()); diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index 1cf780a538a..dd00c35e796 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -32,7 +32,7 @@ import forge.game.event.*; import forge.game.player.Player; import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementType; -import forge.game.spellability.LandAbility; + import forge.game.spellability.SpellAbility; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerType; @@ -1064,7 +1064,7 @@ public class PhaseHandler implements java.io.Serializable { final Zone currentZone = saHost.getZone(); // Need to check if Zone did change - if (currentZone != null && originZone != null && !currentZone.equals(originZone) && (sa.isSpell() || sa instanceof LandAbility)) { + if (currentZone != null && originZone != null && !currentZone.equals(originZone) && (sa.isSpell() || sa.isLandAbility())) { // currently there can be only one Spell put on the Stack at once, or Land Abilities be played final CardZoneTable triggerList = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard()); triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), saHost); diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 35f655d96f8..f51e1eab79c 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -43,7 +43,7 @@ import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementType; import forge.game.spellability.AbilitySub; -import forge.game.spellability.LandAbility; + import forge.game.spellability.SpellAbility; import forge.game.staticability.*; import forge.game.trigger.Trigger; @@ -1691,9 +1691,9 @@ public class Player extends GameEntity implements Comparable { game.fireEvent(new GameEventShuffle(this)); } - public final boolean playLand(final Card land, final boolean ignoreZoneAndTiming) { + public final boolean playLand(final Card land, final boolean ignoreZoneAndTiming, SpellAbility cause) { // Dakkon Blackblade Avatar will use a similar effect - if (canPlayLand(land, ignoreZoneAndTiming)) { + if (canPlayLand(land, ignoreZoneAndTiming, cause)) { playLandNoCheck(land, null); return true; } @@ -1706,7 +1706,7 @@ public class Player extends GameEntity implements Comparable { land.setController(this, 0); if (land.isFaceDown()) { land.turnFaceUp(null); - if (cause instanceof LandAbility) { + if (cause.isLandAbility()) { land.changeToState(cause.getCardStateName()); } } @@ -1730,12 +1730,6 @@ public class Player extends GameEntity implements Comparable { return c; } - public final boolean canPlayLand(final Card land) { - return canPlayLand(land, false); - } - public final boolean canPlayLand(final Card land, final boolean ignoreZoneAndTiming) { - return canPlayLand(land, ignoreZoneAndTiming, null); - } public final boolean canPlayLand(final Card land, final boolean ignoreZoneAndTiming, SpellAbility landSa) { if (!ignoreZoneAndTiming) { // CR 305.3 diff --git a/forge-game/src/main/java/forge/game/spellability/LandAbility.java b/forge-game/src/main/java/forge/game/spellability/LandAbility.java index f8901a57d76..f58c58b8337 100644 --- a/forge-game/src/main/java/forge/game/spellability/LandAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/LandAbility.java @@ -21,24 +21,20 @@ import forge.card.CardStateName; import forge.card.mana.ManaCost; import forge.game.card.Card; import forge.game.card.CardCopyService; -import forge.game.card.CardPlayOption; -import forge.game.cost.Cost; import forge.game.player.Player; import forge.game.staticability.StaticAbility; +import forge.game.zone.ZoneType; import forge.util.CardTranslation; import forge.util.Localizer; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; -public class LandAbility extends Ability { +public class LandAbility extends AbilityStatic { - public LandAbility(Card sourceCard, Player p, CardPlayOption mayPlay) { - super(sourceCard, new Cost(ManaCost.NO_COST, false)); - setActivatingPlayer(p); - setMayPlay(mayPlay); - } public LandAbility(Card sourceCard) { - this(sourceCard, sourceCard.getController(), null); + super(sourceCard, ManaCost.NO_COST); + + getRestrictions().setZone(ZoneType.Hand); } public boolean canPlay(Card newHost) { @@ -46,11 +42,21 @@ public class LandAbility extends Ability { return p.canPlayLand(newHost, false, this); } + @Override + public boolean isLandAbility() { return true; } + + @Override + public boolean isSecondary() { + return true; + } + @Override public boolean canPlay() { Card land = this.getHostCard(); final Player p = this.getActivatingPlayer(); - + if (p == null) { + return false; + } if (this.getCardState() != null && land.getCurrentStateName() != this.getCardStateName()) { if (!land.isLKI()) { land = CardCopyService.getLKICopy(land); @@ -113,4 +119,41 @@ public class LandAbility extends Ability { return sb.toString(); } + @Override + public Card getAlternateHost(Card source) { + boolean lkicheck = false; + + // need to be done before so it works with Vivien and Zoetic Cavern + if (source.isFaceDown() && source.isInZone(ZoneType.Exile)) { + if (!source.isLKI()) { + source = CardCopyService.getLKICopy(source); + } + + source.forceTurnFaceUp(); + lkicheck = true; + } + + if (getCardState() != null && source.getCurrentStateName() != getCardStateName()) { + if (!source.isLKI()) { + source = CardCopyService.getLKICopy(source); + } + CardStateName stateName = getCardState().getStateName(); + if (!source.hasState(stateName)) { + source.addAlternateState(stateName, false); + source.getState(stateName).copyFrom(getHostCard().getState(stateName), true); + } + + source.setState(stateName, false); + if (getHostCard().isDoubleFaced()) { + source.setBackSide(getHostCard().getRules().getSplitType().getChangedStateName().equals(stateName)); + } + + // need to reset CMC + source.setLKICMC(-1); + source.setLKICMC(source.getCMC()); + lkicheck = true; + } + + return lkicheck ? source : null; + } } \ No newline at end of file diff --git a/forge-game/src/main/java/forge/game/spellability/Spell.java b/forge-game/src/main/java/forge/game/spellability/Spell.java index 32ff21ec841..c3cfcce3e9d 100644 --- a/forge-game/src/main/java/forge/game/spellability/Spell.java +++ b/forge-game/src/main/java/forge/game/spellability/Spell.java @@ -153,6 +153,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable this.castFaceDown = faceDown; } + @Override public Card getAlternateHost(Card source) { boolean lkicheck = false; diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 2429b3c0992..aa6bfafb962 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -527,6 +527,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public boolean isSpell() { return false; } public boolean isAbility() { return true; } public boolean isActivatedAbility() { return false; } + public boolean isLandAbility() { return false; } public boolean isTurnFaceUp() { return isMorphUp() || isDisguiseUp() || isManifestUp() || isCloakUp(); @@ -2185,7 +2186,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } } else if (incR[0].contains("LandAbility")) { - if (!(root instanceof LandAbility)) { + if (!(root.isLandAbility())) { return testFailed; } } @@ -2544,7 +2545,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit if (getRestrictions().isInstantSpeed()) { return true; } - if ((isSpell() || this instanceof LandAbility) && (isCastFromPlayEffect() || host.isInstant() || host.hasKeyword(Keyword.FLASH))) { + if ((isSpell() || this.isLandAbility()) && (isCastFromPlayEffect() || host.isInstant() || host.hasKeyword(Keyword.FLASH))) { return true; } @@ -2589,6 +2590,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return true; } + public Card getAlternateHost(Card source) { + return null; + } + public boolean hasOptionalKeywordAmount(KeywordInterface kw) { return this.optionalKeywordAmount.contains(kw.getKeyword(), Pair.of(kw.getIdx(), kw.getStaticId())); } diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java index 367987c4f7d..df3a5b2d282 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java @@ -1,6 +1,6 @@ package forge.ai.simulation; -import forge.game.spellability.LandAbility; + import java.util.ArrayList; import java.util.List; @@ -84,7 +84,7 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest { SpellAbilityPicker picker = new SpellAbilityPicker(game, p); SpellAbility sa = picker.chooseSpellAbilityToPlay(null); - AssertJUnit.assertTrue(sa instanceof LandAbility); + AssertJUnit.assertTrue(sa.isLandAbility()); AssertJUnit.assertEquals(mountain, sa.getHostCard()); Plan plan = picker.getPlan(); diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPassPriority.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPassPriority.java index 13d8fe9f832..c134489837d 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPassPriority.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPassPriority.java @@ -25,7 +25,7 @@ import forge.game.card.Card; import forge.game.player.Player; import forge.game.player.PlayerController; import forge.game.player.actions.PassPriorityAction; -import forge.game.spellability.LandAbility; + import forge.game.spellability.SpellAbility; import forge.localinstance.properties.ForgePreferences.FPref; import forge.model.FModel; @@ -169,7 +169,7 @@ public class InputPassPriority extends InputSyncronizedBase { if (sa.isSpell()) { return Localizer.getInstance().getMessage("lblCastSpell"); } - if (sa instanceof LandAbility) { + if (sa.isLandAbility()) { return Localizer.getInstance().getMessage("lblPlayLand"); } return Localizer.getInstance().getMessage("lblActivateAbility"); diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index b8887145913..1d9323108a3 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -33,7 +33,7 @@ import forge.game.mana.ManaRefundService; import forge.game.player.Player; import forge.game.player.PlayerController; import forge.game.player.PlayerView; -import forge.game.spellability.LandAbility; + import forge.game.spellability.OptionalCostValue; import forge.game.spellability.SpellAbility; import forge.game.staticability.StaticAbilityManaConvert; @@ -70,7 +70,7 @@ public class HumanPlay { Card source = sa.getHostCard(); sa.setActivatingPlayer(p); - if (sa instanceof LandAbility) { + if (sa.isLandAbility()) { if (sa.canPlay()) { sa.resolve(); }