mirror of
https://github.com/correl/mage.git
synced 2024-12-26 11:09:27 +00:00
parent
565bb373f2
commit
3f42d24e2c
1 changed files with 143 additions and 197 deletions
|
@ -1,22 +1,13 @@
|
||||||
package mage.abilities.keyword;
|
package mage.abilities.keyword;
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import mage.MageObject;
|
import mage.MageObject;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.SpecialAction;
|
import mage.abilities.SpecialAction;
|
||||||
import mage.abilities.SpellAbility;
|
import mage.abilities.SpellAbility;
|
||||||
import mage.abilities.StaticAbility;
|
|
||||||
import mage.abilities.common.SimpleStaticAbility;
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
import mage.abilities.costs.AlternativeCost2;
|
|
||||||
import mage.abilities.costs.AlternativeCost2Impl;
|
|
||||||
import mage.abilities.costs.AlternativeSourceCosts;
|
|
||||||
import mage.abilities.costs.Cost;
|
import mage.abilities.costs.Cost;
|
||||||
import mage.abilities.costs.Costs;
|
import mage.abilities.costs.Costs;
|
||||||
import mage.abilities.costs.CostsImpl;
|
|
||||||
import mage.abilities.costs.mana.GenericManaCost;
|
import mage.abilities.costs.mana.GenericManaCost;
|
||||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
import mage.abilities.effects.AsThoughEffectImpl;
|
import mage.abilities.effects.AsThoughEffectImpl;
|
||||||
|
@ -24,9 +15,13 @@ import mage.abilities.effects.Effect;
|
||||||
import mage.abilities.effects.OneShotEffect;
|
import mage.abilities.effects.OneShotEffect;
|
||||||
import mage.abilities.effects.common.ExileTargetEffect;
|
import mage.abilities.effects.common.ExileTargetEffect;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
|
import mage.cards.ModalDoubleFacesCard;
|
||||||
|
import mage.cards.SplitCard;
|
||||||
import mage.constants.AsThoughEffectType;
|
import mage.constants.AsThoughEffectType;
|
||||||
import mage.constants.Duration;
|
import mage.constants.Duration;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
|
import mage.constants.SpellAbilityCastMode;
|
||||||
|
import mage.constants.SpellAbilityType;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.game.ExileZone;
|
import mage.game.ExileZone;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
@ -48,10 +43,12 @@ public class ForetellAbility extends SpecialAction {
|
||||||
this.card = card;
|
this.card = card;
|
||||||
this.usesStack = Boolean.FALSE;
|
this.usesStack = Boolean.FALSE;
|
||||||
this.addCost(new GenericManaCost(2));
|
this.addCost(new GenericManaCost(2));
|
||||||
this.addEffect(new ForetellExileEffect(card)); // exile the card
|
// exile the card
|
||||||
this.addEffect(new ForetellPlayFromExileEffect(card)); // cast from exile with foretell
|
this.addEffect(new ForetellExileEffect(card));
|
||||||
addSubAbility(new ForetellCostAbility(card, foretellCost)); // foretell alternative cost
|
// foretell cost from exile : it can't be any other cost
|
||||||
addSubAbility(new SimpleStaticAbility(Zone.ALL, new ForetellLookAtCardEffect())); // look at face-down card anytime
|
addSubAbility(new ForetellCostAbility(foretellCost));
|
||||||
|
// look at face-down card anytime
|
||||||
|
addSubAbility(new SimpleStaticAbility(Zone.ALL, new ForetellLookAtCardEffect()));
|
||||||
this.setRuleVisible(false);
|
this.setRuleVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +120,7 @@ class ForetellExileEffect extends OneShotEffect {
|
||||||
public ForetellExileEffect(Card card) {
|
public ForetellExileEffect(Card card) {
|
||||||
super(Outcome.Neutral);
|
super(Outcome.Neutral);
|
||||||
this.card = card;
|
this.card = card;
|
||||||
|
staticText = "Foretold this card";
|
||||||
}
|
}
|
||||||
|
|
||||||
public ForetellExileEffect(final ForetellExileEffect effect) {
|
public ForetellExileEffect(final ForetellExileEffect effect) {
|
||||||
|
@ -141,8 +139,10 @@ class ForetellExileEffect extends OneShotEffect {
|
||||||
if (controller != null
|
if (controller != null
|
||||||
&& card != null) {
|
&& card != null) {
|
||||||
UUID exileId = CardUtil.getExileZoneId(card.getId().toString() + "foretellAbility", game);
|
UUID exileId = CardUtil.getExileZoneId(card.getId().toString() + "foretellAbility", game);
|
||||||
Effect effect = new ExileTargetEffect(exileId, " Foretell Turn Number: " + game.getTurnNum()); // foretell turn number shows up on exile window
|
// foretell turn number shows up on exile window
|
||||||
game.getState().setValue(card.getId().toString() + "Foretell Turn Number", game.getTurnNum()); // remember turn number it was cast
|
Effect effect = new ExileTargetEffect(exileId, " Foretell Turn Number: " + game.getTurnNum());
|
||||||
|
// remember turn number it was cast
|
||||||
|
game.getState().setValue(card.getId().toString() + "Foretell Turn Number", game.getTurnNum());
|
||||||
effect.setTargetPointer(new FixedTarget(card.getId()));
|
effect.setTargetPointer(new FixedTarget(card.getId()));
|
||||||
effect.apply(game, source);
|
effect.apply(game, source);
|
||||||
card.setFaceDown(true, game);
|
card.setFaceDown(true, game);
|
||||||
|
@ -152,80 +152,108 @@ class ForetellExileEffect extends OneShotEffect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ForetellPlayFromExileEffect extends AsThoughEffectImpl {
|
class ForetellCostAbility extends SpellAbility {
|
||||||
|
|
||||||
private Card card;
|
private String abilityName;
|
||||||
|
private SpellAbility spellAbilityToResolve;
|
||||||
|
|
||||||
ForetellPlayFromExileEffect(Card card) {
|
public ForetellCostAbility(String foretellCost) {
|
||||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE,
|
super(null, "Foretell", Zone.EXILED, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.NORMAL);
|
||||||
Duration.EndOfGame, Outcome.Neutral);
|
this.setAdditionalCostsRuleVisible(false);
|
||||||
this.card = card;
|
this.name = "Foretell " + foretellCost;
|
||||||
staticText = "Foretell";
|
this.addCost(new ManaCostsImpl(foretellCost));
|
||||||
}
|
|
||||||
|
|
||||||
private ForetellPlayFromExileEffect(final ForetellPlayFromExileEffect effect) {
|
|
||||||
super(effect);
|
|
||||||
this.card = effect.card;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean apply(Game game, Ability source) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ForetellPlayFromExileEffect copy() {
|
|
||||||
return new ForetellPlayFromExileEffect(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
|
|
||||||
return applies(objectId, null, source, game, affectedControllerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
|
|
||||||
if (!Objects.equals(source.getControllerId(), playerId)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Player player = game.getPlayer(playerId);
|
|
||||||
if (player != null
|
|
||||||
&& game.getState().getValue(card.getId().toString() + "Foretell Turn Number") != null) {
|
|
||||||
UUID objectIdToCast = CardUtil.getMainCardId(game, objectId);
|
|
||||||
if (card == null
|
|
||||||
|| !card.getId().equals(objectIdToCast)
|
|
||||||
|| Zone.EXILED != game.getState().getZone(card.getId())
|
|
||||||
|| ((int) game.getState().getValue(card.getId().toString() + "Foretell Turn Number") == game.getTurnNum())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ForetellCostAbility extends StaticAbility implements AlternativeSourceCosts {
|
|
||||||
|
|
||||||
// Stolen from LevelX's code
|
|
||||||
protected static final String FORETELL_KEYWORD = "Foretell";
|
|
||||||
protected static final String REMINDER_TEXT = "(During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.)";
|
|
||||||
|
|
||||||
protected List<AlternativeCost2> foretellCosts = new LinkedList<>();
|
|
||||||
|
|
||||||
// needed to check activation status, if card changes zone after casting it
|
|
||||||
private int zoneChangeCounter = 0;
|
|
||||||
|
|
||||||
public ForetellCostAbility(Card card, String foretellCost) {
|
|
||||||
super(Zone.EXILED, null);
|
|
||||||
name = FORETELL_KEYWORD;
|
|
||||||
this.addForetellCost(foretellCost);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ForetellCostAbility(final ForetellCostAbility ability) {
|
public ForetellCostAbility(final ForetellCostAbility ability) {
|
||||||
super(ability);
|
super(ability);
|
||||||
this.foretellCosts.addAll(ability.foretellCosts);
|
this.spellAbilityType = ability.spellAbilityType;
|
||||||
this.zoneChangeCounter = ability.zoneChangeCounter;
|
this.abilityName = ability.abilityName;
|
||||||
|
this.spellAbilityToResolve = ability.spellAbilityToResolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActivationStatus canActivate(UUID playerId, Game game) {
|
||||||
|
if (super.canActivate(playerId, game).canActivate()) {
|
||||||
|
Card card = game.getCard(getSourceId());
|
||||||
|
if (card != null) {
|
||||||
|
// Card must be in the exile zone
|
||||||
|
if (game.getState().getZone(card.getId()) != Zone.EXILED) {
|
||||||
|
return ActivationStatus.getFalse();
|
||||||
|
}
|
||||||
|
// Card must be Foretold
|
||||||
|
if (game.getState().getValue(card.getId().toString() + "Foretell Turn Number") == null
|
||||||
|
&& game.getState().getValue(card.getId().toString() + "foretellAbility") == null) {
|
||||||
|
return ActivationStatus.getFalse();
|
||||||
|
}
|
||||||
|
// Can't be cast if the turn it was Foretold is the same
|
||||||
|
if ((int) game.getState().getValue(card.getId().toString() + "Foretell Turn Number") == game.getTurnNum()) {
|
||||||
|
return ActivationStatus.getFalse();
|
||||||
|
}
|
||||||
|
// Cards with no Mana Costs cant't be flashbacked (e.g. Ancestral Vision)
|
||||||
|
if (card.getManaCost().isEmpty()) {
|
||||||
|
return ActivationStatus.getFalse();
|
||||||
|
}
|
||||||
|
if (card instanceof SplitCard) {
|
||||||
|
if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) {
|
||||||
|
return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game);
|
||||||
|
} else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) {
|
||||||
|
return ((SplitCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game);
|
||||||
|
}
|
||||||
|
} else if (card instanceof ModalDoubleFacesCard) {
|
||||||
|
if (((ModalDoubleFacesCard) card).getLeftHalfCard().getName().equals(abilityName)) {
|
||||||
|
return ((ModalDoubleFacesCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game);
|
||||||
|
} else if (((ModalDoubleFacesCard) card).getRightHalfCard().getName().equals(abilityName)) {
|
||||||
|
return ((ModalDoubleFacesCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return card.getSpellAbility().canActivate(playerId, game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ActivationStatus.getFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpellAbility getSpellAbilityToResolve(Game game) {
|
||||||
|
Card card = game.getCard(getSourceId());
|
||||||
|
if (card != null) {
|
||||||
|
if (spellAbilityToResolve == null) {
|
||||||
|
SpellAbility spellAbilityCopy = null;
|
||||||
|
if (card instanceof SplitCard) {
|
||||||
|
if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) {
|
||||||
|
spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy();
|
||||||
|
} else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) {
|
||||||
|
spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy();
|
||||||
|
}
|
||||||
|
} else if (card instanceof ModalDoubleFacesCard) {
|
||||||
|
if (((ModalDoubleFacesCard) card).getLeftHalfCard().getName().equals(abilityName)) {
|
||||||
|
spellAbilityCopy = ((ModalDoubleFacesCard) card).getLeftHalfCard().getSpellAbility().copy();
|
||||||
|
} else if (((ModalDoubleFacesCard) card).getRightHalfCard().getName().equals(abilityName)) {
|
||||||
|
spellAbilityCopy = ((ModalDoubleFacesCard) card).getRightHalfCard().getSpellAbility().copy();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
spellAbilityCopy = card.getSpellAbility().copy();
|
||||||
|
}
|
||||||
|
if (spellAbilityCopy == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
spellAbilityCopy.setId(this.getId());
|
||||||
|
spellAbilityCopy.getManaCosts().clear();
|
||||||
|
spellAbilityCopy.getManaCostsToPay().clear();
|
||||||
|
spellAbilityCopy.getCosts().addAll(this.getCosts().copy());
|
||||||
|
spellAbilityCopy.addCost(this.getManaCosts().copy());
|
||||||
|
spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode());
|
||||||
|
spellAbilityToResolve = spellAbilityCopy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return spellAbilityToResolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Costs<Cost> getCosts() {
|
||||||
|
if (spellAbilityToResolve == null) {
|
||||||
|
return super.getCosts();
|
||||||
|
}
|
||||||
|
return spellAbilityToResolve.getCosts();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -233,127 +261,45 @@ class ForetellCostAbility extends StaticAbility implements AlternativeSourceCost
|
||||||
return new ForetellCostAbility(this);
|
return new ForetellCostAbility(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final AlternativeCost2 addForetellCost(String manaString) {
|
|
||||||
AlternativeCost2 foretellCost = new AlternativeCost2Impl(FORETELL_KEYWORD, REMINDER_TEXT, new ManaCostsImpl(manaString));
|
|
||||||
foretellCosts.add(foretellCost);
|
|
||||||
return foretellCost;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActivated(Ability ability, Game game) {
|
public String getRule(boolean all) {
|
||||||
Card card = game.getCard(sourceId);
|
return this.getRule();
|
||||||
if (card != null
|
|
||||||
&& card.getZoneChangeCounter(game) <= zoneChangeCounter + 1) {
|
|
||||||
for (AlternativeCost2 cost : foretellCosts) {
|
|
||||||
if (cost.isActivated(game)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable(Ability source, Game game) {
|
|
||||||
Card card = game.getCard(source.getSourceId());
|
|
||||||
if (card != null
|
|
||||||
&& game.getState().getValue(card.getId().toString() + "Foretell Turn Number") != null) {
|
|
||||||
return Zone.STACK == game.getState().getZone(card.getId())
|
|
||||||
&& ((int) game.getState().getValue(card.getId().toString()
|
|
||||||
+ "Foretell Turn Number") != game.getTurnNum());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean askToActivateAlternativeCosts(Ability ability, Game game) {
|
|
||||||
if (ability instanceof SpellAbility) {
|
|
||||||
Player player = game.getPlayer(controllerId);
|
|
||||||
if (player != null) {
|
|
||||||
this.resetForetell();
|
|
||||||
for (AlternativeCost2 foretellCost : foretellCosts) {
|
|
||||||
if (foretellCost.canPay(ability, this, controllerId, game)
|
|
||||||
&& player.chooseUse(Outcome.Benefit,
|
|
||||||
new StringBuilder(FORETELL_KEYWORD).append(" ")
|
|
||||||
.append(foretellCost.getText(true)).append(" ?").toString(), ability, game)) {
|
|
||||||
activateForetellCost(foretellCost, game);
|
|
||||||
ability.getManaCostsToPay().clear();
|
|
||||||
ability.getCosts().clear();
|
|
||||||
for (Iterator it = ((Costs) foretellCost).iterator(); it.hasNext();) {
|
|
||||||
Cost cost = (Cost) it.next();
|
|
||||||
if (cost instanceof ManaCostsImpl) {
|
|
||||||
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
|
|
||||||
} else {
|
|
||||||
ability.getCosts().add(cost.copy());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isActivated(ability, game);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activateForetellCost(AlternativeCost2 cost, Game game) {
|
|
||||||
cost.activate();
|
|
||||||
// remember zone change counter
|
|
||||||
if (zoneChangeCounter == 0) {
|
|
||||||
Card card = game.getCard(getSourceId());
|
|
||||||
if (card != null) {
|
|
||||||
zoneChangeCounter = card.getZoneChangeCounter(game);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Foretell source card not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Costs<Cost> getCosts() {
|
|
||||||
Costs<Cost> alterCosts = new CostsImpl<>();
|
|
||||||
for (AlternativeCost2 aCost : foretellCosts) {
|
|
||||||
alterCosts.add(aCost.getCost());
|
|
||||||
}
|
|
||||||
return alterCosts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetForetell() {
|
|
||||||
for (AlternativeCost2 cost : foretellCosts) {
|
|
||||||
cost.reset();
|
|
||||||
}
|
|
||||||
zoneChangeCounter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCastMessageSuffix(Game game) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
int position = 0;
|
|
||||||
for (AlternativeCost2 cost : foretellCosts) {
|
|
||||||
if (cost.isActivated(game)) {
|
|
||||||
sb.append(cost.getCastSuffixMessage(position));
|
|
||||||
++position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRule() {
|
public String getRule() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sbRule = new StringBuilder("Foretell");
|
||||||
int numberCosts = 0;
|
if (!costs.isEmpty()) {
|
||||||
String remarkText = "";
|
sbRule.append("—");
|
||||||
for (AlternativeCost2 foretellCost : foretellCosts) {
|
|
||||||
if (numberCosts == 0) {
|
|
||||||
sb.append(foretellCost.getText(false));
|
|
||||||
remarkText = foretellCost.getReminderText();
|
|
||||||
} else {
|
} else {
|
||||||
sb.append(" and/or ").append(foretellCost.getText(true));
|
sbRule.append(' ');
|
||||||
}
|
}
|
||||||
++numberCosts;
|
if (!manaCosts.isEmpty()) {
|
||||||
|
sbRule.append(manaCosts.getText());
|
||||||
}
|
}
|
||||||
if (numberCosts == 1) {
|
if (!costs.isEmpty()) {
|
||||||
sb.append(' ').append(remarkText);
|
if (!manaCosts.isEmpty()) {
|
||||||
|
sbRule.append(", ");
|
||||||
|
}
|
||||||
|
sbRule.append(costs.getText());
|
||||||
|
sbRule.append('.');
|
||||||
|
}
|
||||||
|
if (abilityName != null) {
|
||||||
|
sbRule.append(' ');
|
||||||
|
sbRule.append(abilityName);
|
||||||
|
}
|
||||||
|
sbRule.append(" <i>(During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost.)</i>");
|
||||||
|
return sbRule.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.toString();
|
/**
|
||||||
|
* Used for split card in PlayerImpl method:
|
||||||
|
* getOtherUseableActivatedAbilities
|
||||||
|
*
|
||||||
|
* @param abilityName
|
||||||
|
*/
|
||||||
|
public void setAbilityName(String abilityName) {
|
||||||
|
this.abilityName = abilityName;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue