Merge branch 'master' into feature/implement-harmonize-ability

This commit is contained in:
theelk801 2025-04-08 09:20:18 -04:00
commit 2fbd7624b9
150 changed files with 5325 additions and 843 deletions

View file

@ -6,8 +6,8 @@ import mage.MageObject;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.keyword.FlashAbility;
import mage.cards.AdventureCardSpell;
import mage.cards.Card;
import mage.cards.SpellOptionCard;
import mage.cards.SplitCard;
import mage.constants.*;
import mage.game.Game;
@ -99,7 +99,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
// forced to cast (can be part id or main id)
Set<UUID> idsToCheck = new HashSet<>();
idsToCheck.add(object.getId());
if (object instanceof Card && !(object instanceof AdventureCardSpell)) {
if (object instanceof Card && !(object instanceof SpellOptionCard)) {
idsToCheck.add(((Card) object).getMainCard().getId());
}
for (UUID idToCheck : idsToCheck) {

View file

@ -70,7 +70,7 @@ public class BecomesTargetAnyTriggeredAbility extends TriggeredAbilityImpl {
if (permanent == null || !filterTarget.match(permanent, getControllerId(), this, game)) {
return false;
}
StackObject targetingObject = CardUtil.getTargetingStackObject(event, game);
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game);
if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) {
return false;
}

View file

@ -54,7 +54,7 @@ public class BecomesTargetAttachedTriggeredAbility extends TriggeredAbilityImpl
if (enchantment == null || enchantment.getAttachedTo() == null || !event.getTargetId().equals(enchantment.getAttachedTo())) {
return false;
}
StackObject targetingObject = CardUtil.getTargetingStackObject(event, game);
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game);
if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) {
return false;
}

View file

@ -63,7 +63,7 @@ public class BecomesTargetControllerTriggeredAbility extends TriggeredAbilityImp
return false;
}
}
StackObject targetingObject = CardUtil.getTargetingStackObject(event, game);
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game);
if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) {
return false;
}

View file

@ -57,7 +57,7 @@ public class BecomesTargetSourceTriggeredAbility extends TriggeredAbilityImpl {
if (!event.getTargetId().equals(getSourceId())) {
return false;
}
StackObject targetingObject = CardUtil.getTargetingStackObject(event, game);
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game);
if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) {
return false;
}

View file

@ -4,7 +4,7 @@ package mage.abilities.condition.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.cards.AdventureCardSpell;
import mage.cards.SpellOptionCard;
import mage.cards.Card;
import mage.cards.ModalDoubleFacedCardHalf;
import mage.cards.SplitCardHalf;
@ -20,7 +20,7 @@ public enum IsBeingCastFromHandCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
MageObject object = game.getObject(source);
if (object instanceof SplitCardHalf || object instanceof AdventureCardSpell || object instanceof ModalDoubleFacedCardHalf) {
if (object instanceof SplitCardHalf || object instanceof SpellOptionCard || object instanceof ModalDoubleFacedCardHalf) {
UUID mainCardId = ((Card) object).getMainCard().getId();
object = game.getObject(mainCardId);
}

View file

@ -0,0 +1,19 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.watchers.common.SpellsCastWatcher;
/**
* @author androosss
*/
public enum YouCastExactOneSpellThisTurnCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class);
return watcher != null && watcher.getSpellsCastThisTurn(source.getControllerId()).size() == 1;
}
}

View file

@ -549,9 +549,9 @@ public class ContinuousEffects implements Serializable {
// rules:
// 708.4. In every zone except the stack, the characteristics of a split card are those of its two halves combined.
idToCheck = ((SplitCardHalf) objectToCheck).getMainCard().getId();
} else if (!type.needPlayCardAbility() && objectToCheck instanceof AdventureCardSpell) {
// adventure spell uses alternative characteristics for spell/stack, all other cases must use main card
idToCheck = ((AdventureCardSpell) objectToCheck).getMainCard().getId();
} else if (!type.needPlayCardAbility() && objectToCheck instanceof CardWithSpellOption) {
// adventure/omen spell uses alternative characteristics for spell/stack, all other cases must use main card
idToCheck = ((CardWithSpellOption) objectToCheck).getMainCard().getId();
} else if (!type.needPlayCardAbility() && objectToCheck instanceof ModalDoubleFacedCardHalf) {
// each mdf side uses own characteristics to check for playing, all other cases must use main card
// rules:

View file

@ -5,7 +5,7 @@ import mage.abilities.MageSingleton;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.AdventureCardSpell;
import mage.cards.AdventureSpellCard;
import mage.cards.Card;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
@ -51,10 +51,10 @@ public class ExileAdventureSpellEffect extends OneShotEffect implements MageSing
Spell spell = game.getStack().getSpell(source.getId());
if (spell != null) {
Card spellCard = spell.getCard();
if (spellCard instanceof AdventureCardSpell) {
if (spellCard instanceof AdventureSpellCard) {
UUID exileId = adventureExileId(controller.getId(), game);
game.getExile().createZone(exileId, "On an Adventure from " + controller.getName());
AdventureCardSpell adventureSpellCard = (AdventureCardSpell) spellCard;
AdventureSpellCard adventureSpellCard = (AdventureSpellCard) spellCard;
Card parentCard = adventureSpellCard.getParentCard();
if (controller.moveCardsToExile(parentCard, source, game, true, exileId, "On an Adventure from " + controller.getName())) {
ContinuousEffect effect = new AdventureCastFromExileEffect();

View file

@ -39,12 +39,19 @@ public class EndureSourceEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return doEndure(source.getSourcePermanentOrLKI(game), 1, game, source);
}
public static boolean doEndure(Permanent permanent, int amount, Game game, Ability source) {
if (permanent == null || amount < 1) {
return false;
}
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (permanent != null && player.chooseUse(
Player controller = game.getPlayer(permanent.getControllerId());
if (controller == null) {
return false;
}
if (permanent.getZoneChangeCounter(game) == game.getState().getZoneChangeCounter(permanent.getId())
&& controller.chooseUse(
Outcome.BoostCreature, "Put " + CardUtil.numberToText(amount, "a") + " +1/+1 counter" +
(amount > 1 ? "s" : "") + " on " + permanent.getName() + " or create " +
CardUtil.addArticle("" + amount) + ' ' + amount + '/' + amount + " Spirit token?",

View file

@ -257,7 +257,7 @@ public class ForetellAbility extends SpecialAction {
game.getState().addOtherAbility(rightHalfCard, ability);
}
}
} else if (card instanceof AdventureCard) {
} else if (card instanceof CardWithSpellOption) {
if (foretellCost != null) {
Card creatureCard = card.getMainCard();
ForetellCostAbility ability = new ForetellCostAbility(foretellCost);
@ -268,7 +268,7 @@ public class ForetellAbility extends SpecialAction {
game.getState().addOtherAbility(creatureCard, ability);
}
if (foretellSplitCost != null) {
Card spellCard = ((AdventureCard) card).getSpellCard();
Card spellCard = ((CardWithSpellOption) card).getSpellCard();
ForetellCostAbility ability = new ForetellCostAbility(foretellSplitCost);
ability.setSourceId(spellCard.getId());
ability.setControllerId(source.getControllerId());
@ -360,11 +360,11 @@ public class ForetellAbility extends SpecialAction {
} else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) {
return ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game);
}
} else if (card instanceof AdventureCard) {
} else if (card instanceof CardWithSpellOption) {
if (card.getMainCard().getName().equals(abilityName)) {
return card.getMainCard().getSpellAbility().canActivate(playerId, game);
} else if (((AdventureCard) card).getSpellCard().getName().equals(abilityName)) {
return ((AdventureCard) card).getSpellCard().getSpellAbility().canActivate(playerId, game);
} else if (((CardWithSpellOption) card).getSpellCard().getName().equals(abilityName)) {
return ((CardWithSpellOption) card).getSpellCard().getSpellAbility().canActivate(playerId, game);
}
}
return card.getSpellAbility().canActivate(playerId, game);
@ -391,11 +391,11 @@ public class ForetellAbility extends SpecialAction {
} else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) {
spellAbilityCopy = ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().copy();
}
} else if (card instanceof AdventureCard) {
} else if (card instanceof CardWithSpellOption) {
if (card.getMainCard().getName().equals(abilityName)) {
spellAbilityCopy = card.getMainCard().getSpellAbility().copy();
} else if (((AdventureCard) card).getSpellCard().getName().equals(abilityName)) {
spellAbilityCopy = ((AdventureCard) card).getSpellCard().getSpellAbility().copy();
} else if (((CardWithSpellOption) card).getSpellCard().getName().equals(abilityName)) {
spellAbilityCopy = ((CardWithSpellOption) card).getSpellCard().getSpellAbility().copy();
}
} else {
spellAbilityCopy = card.getSpellAbility().copy();

View file

@ -268,11 +268,11 @@ class PlotSpellAbility extends SpellAbility {
} else if (((CardWithHalves) mainCard).getRightHalfCard().getName().equals(faceCardName)) {
return ((CardWithHalves) mainCard).getRightHalfCard().getSpellAbility().canActivate(playerId, game);
}
} else if (card instanceof AdventureCard) {
} else if (card instanceof CardWithSpellOption) {
if (card.getMainCard().getName().equals(faceCardName)) {
return card.getMainCard().getSpellAbility().canActivate(playerId, game);
} else if (((AdventureCard) card).getSpellCard().getName().equals(faceCardName)) {
return ((AdventureCard) card).getSpellCard().getSpellAbility().canActivate(playerId, game);
} else if (((CardWithSpellOption) card).getSpellCard().getName().equals(faceCardName)) {
return ((CardWithSpellOption) card).getSpellCard().getSpellAbility().canActivate(playerId, game);
}
}
return card.getSpellAbility().canActivate(playerId, game);
@ -294,11 +294,11 @@ class PlotSpellAbility extends SpellAbility {
} else if (((CardWithHalves) card).getRightHalfCard().getName().equals(faceCardName)) {
spellAbilityCopy = ((CardWithHalves) card).getRightHalfCard().getSpellAbility().copy();
}
} else if (card instanceof AdventureCard) {
} else if (card instanceof CardWithSpellOption) {
if (card.getMainCard().getName().equals(faceCardName)) {
spellAbilityCopy = card.getMainCard().getSpellAbility().copy();
} else if (((AdventureCard) card).getSpellCard().getName().equals(faceCardName)) {
spellAbilityCopy = ((AdventureCard) card).getSpellCard().getSpellAbility().copy();
} else if (((CardWithSpellOption) card).getSpellCard().getName().equals(faceCardName)) {
spellAbilityCopy = ((CardWithSpellOption) card).getSpellCard().getSpellAbility().copy();
}
} else {
spellAbilityCopy = card.getSpellAbility().copy();

View file

@ -77,7 +77,7 @@ public class WardAbility extends TriggeredAbilityImpl {
if (!getSourceId().equals(event.getTargetId())) {
return false;
}
StackObject targetingObject = CardUtil.getTargetingStackObject(event, game);
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game);
if (targetingObject == null || !game.getOpponents(getControllerId()).contains(targetingObject.getControllerId())) {
return false;
}

View file

@ -1,98 +1,29 @@
package mage.cards;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.constants.CardType;
import mage.constants.SpellAbilityType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.ZoneChangeEvent;
import mage.util.CardUtil;
import java.util.List;
import java.util.UUID;
/**
* @author phulin
*/
public abstract class AdventureCard extends CardImpl {
/* The adventure spell card, i.e. Swift End. */
protected AdventureCardSpell spellCard;
public abstract class AdventureCard extends CardWithSpellOption {
public AdventureCard(UUID ownerId, CardSetInfo setInfo, CardType[] types, CardType[] typesSpell, String costs, String adventureName, String costsSpell) {
super(ownerId, setInfo, types, costs);
this.spellCard = new AdventureCardSpellImpl(ownerId, setInfo, adventureName, typesSpell, costsSpell, this);
this.spellCard = new AdventureSpellCard(ownerId, setInfo, adventureName, typesSpell, costsSpell, this);
}
public AdventureCard(AdventureCard card) {
super(card);
}
public void finalizeAdventure() {
spellCard.finalizeAdventure();
}
protected AdventureCard(final AdventureCard card) {
super(card);
this.spellCard = card.getSpellCard().copy();
this.spellCard.setParentCard(this);
}
public AdventureCardSpell getSpellCard() {
return spellCard;
}
public void setParts(AdventureCardSpell cardSpell) {
// for card copy only - set new parts
this.spellCard = cardSpell;
cardSpell.setParentCard(this);
}
@Override
public void assignNewId() {
super.assignNewId();
spellCard.assignNewId();
}
@Override
public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List<UUID> appliedEffects) {
if (super.moveToZone(toZone, source, game, flag, appliedEffects)) {
Zone currentZone = game.getState().getZone(getId());
game.getState().setZone(getSpellCard().getId(), currentZone);
return true;
}
return false;
}
@Override
public void setZone(Zone zone, Game game) {
super.setZone(zone, game);
game.setZone(getSpellCard().getId(), zone);
}
@Override
public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List<UUID> appliedEffects) {
if (super.moveToExile(exileId, name, source, game, appliedEffects)) {
Zone currentZone = game.getState().getZone(getId());
game.getState().setZone(getSpellCard().getId(), currentZone);
return true;
}
return false;
}
@Override
public boolean removeFromZone(Game game, Zone fromZone, Ability source) {
// zone contains only one main card
return super.removeFromZone(game, fromZone, source);
}
@Override
public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) {
if (isCopy()) { // same as meld cards
super.updateZoneChangeCounter(game, event);
return;
}
super.updateZoneChangeCounter(game, event);
getSpellCard().updateZoneChangeCounter(game, event);
spellCard.finalizeSpell();
}
@Override
@ -103,45 +34,4 @@ public abstract class AdventureCard extends CardImpl {
this.getSpellCard().getSpellAbility().setControllerId(controllerId);
return super.cast(game, fromZone, ability, controllerId);
}
@Override
public Abilities<Ability> getAbilities() {
Abilities<Ability> allAbilities = new AbilitiesImpl<>();
allAbilities.addAll(spellCard.getAbilities());
allAbilities.addAll(super.getAbilities());
return allAbilities;
}
@Override
public Abilities<Ability> getInitAbilities() {
// must init only parent related abilities, spell card must be init separately
return super.getAbilities();
}
@Override
public Abilities<Ability> getAbilities(Game game) {
Abilities<Ability> allAbilities = new AbilitiesImpl<>();
allAbilities.addAll(spellCard.getAbilities(game));
allAbilities.addAll(super.getAbilities(game));
return allAbilities;
}
public Abilities<Ability> getSharedAbilities(Game game) {
// abilities without spellcard
return super.getAbilities(game);
}
public List<String> getSharedRules(Game game) {
// rules without spellcard
Abilities<Ability> sourceAbilities = this.getSharedAbilities(game);
return CardUtil.getCardRulesWithAdditionalInfo(game, this, sourceAbilities, sourceAbilities);
}
@Override
public void setOwnerId(UUID ownerId) {
super.setOwnerId(ownerId);
abilities.setControllerId(ownerId);
spellCard.getAbilities().setControllerId(ownerId);
spellCard.setOwnerId(ownerId);
}
}

View file

@ -1,12 +0,0 @@
package mage.cards;
/**
* @author phulin
*/
public interface AdventureCardSpell extends SubCard<AdventureCard> {
@Override
AdventureCardSpell copy();
void finalizeAdventure();
}

View file

@ -19,11 +19,11 @@ import java.util.stream.Collectors;
/**
* @author phulin
*/
public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpell {
public class AdventureSpellCard extends CardImpl implements SpellOptionCard {
private AdventureCard adventureCardParent;
public AdventureCardSpellImpl(UUID ownerId, CardSetInfo setInfo, String adventureName, CardType[] cardTypes, String costs, AdventureCard adventureCardParent) {
public AdventureSpellCard(UUID ownerId, CardSetInfo setInfo, String adventureName, CardType[] cardTypes, String costs, AdventureCard adventureCardParent) {
super(ownerId, setInfo, cardTypes, costs, SpellAbilityType.ADVENTURE_SPELL);
this.subtype.add(SubType.ADVENTURE);
@ -35,13 +35,13 @@ public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpe
this.adventureCardParent = adventureCardParent;
}
public void finalizeAdventure() {
public void finalizeSpell() {
if (spellAbility instanceof AdventureCardSpellAbility) {
((AdventureCardSpellAbility) spellAbility).finalizeAdventure();
}
}
protected AdventureCardSpellImpl(final AdventureCardSpellImpl card) {
protected AdventureSpellCard(final AdventureSpellCard card) {
super(card);
this.adventureCardParent = card.adventureCardParent;
}
@ -83,13 +83,13 @@ public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpe
}
@Override
public AdventureCardSpellImpl copy() {
return new AdventureCardSpellImpl(this);
public AdventureSpellCard copy() {
return new AdventureSpellCard(this);
}
@Override
public void setParentCard(AdventureCard card) {
this.adventureCardParent = card;
public void setParentCard(CardWithSpellOption card) {
this.adventureCardParent = (AdventureCard) card;
}
@Override
@ -102,6 +102,11 @@ public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpe
// id must send to main card (popup card hint in game logs)
return getName() + " [" + adventureCardParent.getId().toString().substring(0, 3) + ']';
}
@Override
public String getSpellType() {
return "Adventure";
}
}
class AdventureCardSpellAbility extends SpellAbility {
@ -141,8 +146,8 @@ class AdventureCardSpellAbility extends SpellAbility {
public ActivationStatus canActivate(UUID playerId, Game game) {
ExileZone adventureExileZone = game.getExile().getExileZone(ExileAdventureSpellEffect.adventureExileId(playerId, game));
Card spellCard = game.getCard(this.getSourceId());
if (spellCard instanceof AdventureCardSpell) {
Card card = ((AdventureCardSpell) spellCard).getParentCard();
if (spellCard instanceof AdventureSpellCard) {
Card card = ((AdventureSpellCard) spellCard).getParentCard();
if (adventureExileZone != null && adventureExileZone.contains(card.getId())) {
return ActivationStatus.getFalse();
}

View file

@ -530,8 +530,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
}
}
if (stackObject == null && (this instanceof AdventureCard)) {
stackObject = game.getStack().getSpell(((AdventureCard) this).getSpellCard().getId(), false);
if (stackObject == null && (this instanceof CardWithSpellOption)) {
stackObject = game.getStack().getSpell(((CardWithSpellOption) this).getSpellCard().getId(), false);
}
if (stackObject == null) {

View file

@ -0,0 +1,131 @@
package mage.cards;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
import mage.constants.CardType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.ZoneChangeEvent;
import mage.util.CardUtil;
import java.util.List;
import java.util.UUID;
/**
* @author phulin, jmlundeen
*/
public abstract class CardWithSpellOption extends CardImpl {
/* The adventure/omen spell card, i.e. Swift End. */
protected SpellOptionCard spellCard;
public CardWithSpellOption(UUID ownerId, CardSetInfo setInfo, CardType[] types, String costs) {
super(ownerId, setInfo, types, costs);
}
public CardWithSpellOption(CardWithSpellOption card) {
super(card);
this.spellCard = card.getSpellCard().copy();
this.spellCard.setParentCard(this);
}
public SpellOptionCard getSpellCard() {
return spellCard;
}
public void setParts(SpellOptionCard cardSpell) {
// for card copy only - set new parts
this.spellCard = cardSpell;
cardSpell.setParentCard(this);
}
@Override
public void assignNewId() {
super.assignNewId();
spellCard.assignNewId();
}
@Override
public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List<UUID> appliedEffects) {
if (super.moveToZone(toZone, source, game, flag, appliedEffects)) {
Zone currentZone = game.getState().getZone(getId());
game.getState().setZone(getSpellCard().getId(), currentZone);
return true;
}
return false;
}
@Override
public void setZone(Zone zone, Game game) {
super.setZone(zone, game);
game.setZone(getSpellCard().getId(), zone);
}
@Override
public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List<UUID> appliedEffects) {
if (super.moveToExile(exileId, name, source, game, appliedEffects)) {
Zone currentZone = game.getState().getZone(getId());
game.getState().setZone(getSpellCard().getId(), currentZone);
return true;
}
return false;
}
@Override
public boolean removeFromZone(Game game, Zone fromZone, Ability source) {
// zone contains only one main card
return super.removeFromZone(game, fromZone, source);
}
@Override
public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) {
if (isCopy()) { // same as meld cards
super.updateZoneChangeCounter(game, event);
return;
}
super.updateZoneChangeCounter(game, event);
getSpellCard().updateZoneChangeCounter(game, event);
}
@Override
public Abilities<Ability> getAbilities() {
Abilities<Ability> allAbilities = new AbilitiesImpl<>();
allAbilities.addAll(spellCard.getAbilities());
allAbilities.addAll(super.getAbilities());
return allAbilities;
}
@Override
public Abilities<Ability> getInitAbilities() {
// must init only parent related abilities, spell card must be init separately
return super.getAbilities();
}
@Override
public Abilities<Ability> getAbilities(Game game) {
Abilities<Ability> allAbilities = new AbilitiesImpl<>();
allAbilities.addAll(spellCard.getAbilities(game));
allAbilities.addAll(super.getAbilities(game));
return allAbilities;
}
public Abilities<Ability> getSharedAbilities(Game game) {
// abilities without spellCard
return super.getAbilities(game);
}
public List<String> getSharedRules(Game game) {
// rules without spellCard
Abilities<Ability> sourceAbilities = this.getSharedAbilities(game);
return CardUtil.getCardRulesWithAdditionalInfo(game, this, sourceAbilities, sourceAbilities);
}
@Override
public void setOwnerId(UUID ownerId) {
super.setOwnerId(ownerId);
abilities.setControllerId(ownerId);
spellCard.getAbilities().setControllerId(ownerId);
spellCard.setOwnerId(ownerId);
}
}

View file

@ -0,0 +1,34 @@
package mage.cards;
import mage.abilities.SpellAbility;
import mage.constants.CardType;
import mage.constants.SpellAbilityType;
import mage.constants.Zone;
import mage.game.Game;
import java.util.UUID;
public abstract class OmenCard extends CardWithSpellOption {
public OmenCard(UUID ownerId, CardSetInfo setInfo, CardType[] types, CardType[] typesSpell, String costs, String omenName, String costsSpell) {
super(ownerId, setInfo, types, costs);
this.spellCard = new OmenSpellCard(ownerId, setInfo, omenName, typesSpell, costsSpell, this);
}
public OmenCard(OmenCard card) {
super(card);
}
public void finalizeOmen() {
spellCard.finalizeSpell();
}
@Override
public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) {
if (ability.getSpellAbilityType() == SpellAbilityType.OMEN_SPELL) {
return this.getSpellCard().cast(game, fromZone, ability, controllerId);
}
this.getSpellCard().getSpellAbility().setControllerId(controllerId);
return super.cast(game, fromZone, ability, controllerId);
}
}

View file

@ -0,0 +1,174 @@
package mage.cards;
import mage.abilities.Ability;
import mage.abilities.Modes;
import mage.abilities.SpellAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.ShuffleIntoLibrarySourceEffect;
import mage.constants.CardType;
import mage.constants.SpellAbilityType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
public class OmenSpellCard extends CardImpl implements SpellOptionCard {
private OmenCard omenCardParent;
public OmenSpellCard(UUID ownerId, CardSetInfo setInfo, String omenName, CardType[] cardTypes, String costs, OmenCard omenCard) {
super(ownerId, setInfo, cardTypes, costs, SpellAbilityType.OMEN_SPELL);
this.subtype.add(SubType.OMEN);
OmenCardSpellAbility newSpellAbility = new OmenCardSpellAbility(getSpellAbility(), omenName, cardTypes, costs);
this.replaceSpellAbility(newSpellAbility);
spellAbility = newSpellAbility;
this.setName(omenName);
this.omenCardParent = omenCard;
}
public void finalizeSpell() {
if (spellAbility instanceof OmenCardSpellAbility) {
((OmenCardSpellAbility) spellAbility).finalizeOmen();
}
}
protected OmenSpellCard(final OmenSpellCard card) {
super(card);
this.omenCardParent = card.omenCardParent;
}
@Override
public UUID getOwnerId() {
return omenCardParent.getOwnerId();
}
@Override
public String getExpansionSetCode() {
return omenCardParent.getExpansionSetCode();
}
@Override
public String getCardNumber() {
return omenCardParent.getCardNumber();
}
@Override
public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List<UUID> appliedEffects) {
return omenCardParent.moveToZone(toZone, source, game, flag, appliedEffects);
}
@Override
public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List<UUID> appliedEffects) {
return omenCardParent.moveToExile(exileId, name, source, game, appliedEffects);
}
@Override
public OmenCard getMainCard() {
return omenCardParent;
}
@Override
public void setZone(Zone zone, Game game) {
game.setZone(omenCardParent.getId(), zone);
game.setZone(omenCardParent.getSpellCard().getId(), zone);
}
@Override
public OmenSpellCard copy() {
return new OmenSpellCard(this);
}
@Override
public void setParentCard(CardWithSpellOption card) {
this.omenCardParent = (OmenCard) card;
}
@Override
public OmenCard getParentCard() {
return this.omenCardParent;
}
@Override
public String getIdName() {
// id must send to main card (popup card hint in game logs)
return getName() + " [" + omenCardParent.getId().toString().substring(0, 3) + ']';
}
@Override
public String getSpellType() {
return "Omen";
}
}
class OmenCardSpellAbility extends SpellAbility {
private String nameFull;
private boolean finalized = false;
public OmenCardSpellAbility(final SpellAbility baseSpellAbility, String omenName, CardType[] cardTypes, String costs) {
super(baseSpellAbility);
this.setName(cardTypes, omenName, costs);
this.setCardName(omenName);
}
public void finalizeOmen() {
if (finalized) {
throw new IllegalStateException("Wrong code usage. "
+ "Omen (" + cardName + ") "
+ "need to call finalizeOmen() exactly once.");
}
Effect effect = new ShuffleIntoLibrarySourceEffect();
effect.setText("");
this.addEffect(effect);
this.finalized = true;
}
protected OmenCardSpellAbility(final OmenCardSpellAbility ability) {
super(ability);
this.nameFull = ability.nameFull;
if (!ability.finalized) {
throw new IllegalStateException("Wrong code usage. "
+ "Omen (" + cardName + ") "
+ "need to call finalizeOmen() at the very end of the card's constructor.");
}
this.finalized = true;
}
public void setName(CardType[] cardTypes, String omenName, String costs) {
this.nameFull = "Omen " + Arrays.stream(cardTypes).map(CardType::toString).collect(Collectors.joining(" ")) + " &mdash; " + omenName;
this.name = this.nameFull + " " + costs;
}
@Override
public String getRule(boolean all) {
return this.getRule();
}
@Override
public String getRule() {
StringBuilder sbRule = new StringBuilder();
sbRule.append(this.nameFull);
sbRule.append(" ");
sbRule.append(getManaCosts().getText());
sbRule.append(" &mdash; ");
Modes modes = this.getModes();
if (modes.size() <= 1) {
sbRule.append(modes.getMode().getEffects().getTextStartingUpperCase(modes.getMode()));
} else {
sbRule.append(getModes().getText());
}
sbRule.append(" <i>(Then shuffle this card into its owner's library.)<i>");
return sbRule.toString();
}
@Override
public OmenCardSpellAbility copy() {
return new OmenCardSpellAbility(this);
}
}

View file

@ -0,0 +1,18 @@
package mage.cards;
public interface SpellOptionCard extends SubCard<CardWithSpellOption> {
@Override
SpellOptionCard copy();
/**
* Adds the final shared ability to the card. e.g. Adventure exile effect / Omen shuffle effect
*/
void finalizeSpell();
/**
* Used to get the card type text such as Adventure. Currently only used in {@link mage.game.stack.Spell#getSpellCastText Spell} for logging the spell
* being cast as part of the two part card.
*/
String getSpellType();
}

View file

@ -20,7 +20,7 @@ import java.util.List;
*/
public class MockCard extends CardImpl implements MockableCard {
public static String ADVENTURE_NAME_SEPARATOR = " // ";
public static String CARD_WITH_SPELL_OPTION_NAME_SEPARATOR = " // ";
public static String MODAL_DOUBLE_FACES_NAME_SEPARATOR = " // ";
// Needs to be here, as it is normally calculated from the
@ -34,7 +34,7 @@ public class MockCard extends CardImpl implements MockableCard {
protected List<String> manaCostLeftStr;
protected List<String> manaCostRightStr;
protected List<String> manaCostStr;
protected String adventureSpellName;
protected String spellOptionName; // adventure/omen spell name
protected boolean isModalDoubleFacedCard;
protected int manaValue;
@ -71,8 +71,8 @@ public class MockCard extends CardImpl implements MockableCard {
this.secondSideCard = new MockCard(CardRepository.instance.findCardWithPreferredSetAndNumber(card.getSecondSideName(), card.getSetCode(), card.getCardNumber()));
}
if (card.isAdventureCard()) {
this.adventureSpellName = card.getAdventureSpellName();
if (card.isCardWithSpellOption()) {
this.spellOptionName = card.getSpellOptionCardName();
}
if (card.isModalDoubleFacedCard()) {
@ -101,7 +101,7 @@ public class MockCard extends CardImpl implements MockableCard {
this.manaCostLeftStr = new ArrayList<>(card.manaCostLeftStr);
this.manaCostRightStr = new ArrayList<>(card.manaCostRightStr);
this.manaCostStr = new ArrayList<>(card.manaCostStr);
this.adventureSpellName = card.adventureSpellName;
this.spellOptionName = card.spellOptionName;
this.isModalDoubleFacedCard = card.isModalDoubleFacedCard;
this.manaValue = card.manaValue;
}
@ -155,8 +155,8 @@ public class MockCard extends CardImpl implements MockableCard {
return getName();
}
if (adventureSpellName != null) {
return getName() + ADVENTURE_NAME_SEPARATOR + adventureSpellName;
if (spellOptionName != null) {
return getName() + CARD_WITH_SPELL_OPTION_NAME_SEPARATOR + spellOptionName;
} else if (isModalDoubleFacedCard) {
return getName() + MODAL_DOUBLE_FACES_NAME_SEPARATOR + this.getSecondCardFace().getName();
} else {

View file

@ -106,9 +106,9 @@ public class CardInfo {
@DatabaseField
protected String secondSideName;
@DatabaseField
protected boolean adventureCard;
protected boolean cardWithSpellOption;
@DatabaseField
protected String adventureSpellName;
protected String spellOptionCardName;
@DatabaseField
protected boolean modalDoubleFacedCard;
@DatabaseField
@ -157,9 +157,9 @@ public class CardInfo {
this.secondSideName = secondSide.getName();
}
if (card instanceof AdventureCard) {
this.adventureCard = true;
this.adventureSpellName = ((AdventureCard) card).getSpellCard().getName();
if (card instanceof CardWithSpellOption) {
this.cardWithSpellOption = true;
this.spellOptionCardName = ((CardWithSpellOption) card).getSpellCard().getName();
}
if (card instanceof ModalDoubleFacedCard) {
@ -189,8 +189,8 @@ public class CardInfo {
List<String> manaCostLeft = ((ModalDoubleFacedCard) card).getLeftHalfCard().getManaCostSymbols();
List<String> manaCostRight = ((ModalDoubleFacedCard) card).getRightHalfCard().getManaCostSymbols();
this.setManaCosts(CardUtil.concatManaSymbols(SPLIT_MANA_SEPARATOR_FULL, manaCostLeft, manaCostRight));
} else if (card instanceof AdventureCard) {
List<String> manaCostLeft = ((AdventureCard) card).getSpellCard().getManaCostSymbols();
} else if (card instanceof CardWithSpellOption) {
List<String> manaCostLeft = ((CardWithSpellOption) card).getSpellCard().getManaCostSymbols();
List<String> manaCostRight = card.getManaCostSymbols();
this.setManaCosts(CardUtil.concatManaSymbols(SPLIT_MANA_SEPARATOR_FULL, manaCostLeft, manaCostRight));
} else {
@ -469,12 +469,16 @@ public class CardInfo {
return secondSideName;
}
public boolean isAdventureCard() {
return adventureCard;
public boolean isCardWithSpellOption() {
return cardWithSpellOption;
}
public String getAdventureSpellName() {
return adventureSpellName;
/**
* used for spell card portion of adventure/omen cards
* @return name of the spell
*/
public String getSpellOptionCardName() {
return spellOptionCardName;
}
public boolean isModalDoubleFacedCard() {

View file

@ -147,8 +147,8 @@ public enum CardRepository {
if (card.getMeldsToCardName() != null && !card.getMeldsToCardName().isEmpty()) {
namesList.add(card.getMeldsToCardName());
}
if (card.getAdventureSpellName() != null && !card.getAdventureSpellName().isEmpty()) {
namesList.add(card.getAdventureSpellName());
if (card.getSpellOptionCardName() != null && !card.getSpellOptionCardName().isEmpty()) {
namesList.add(card.getSpellOptionCardName());
}
}
@ -160,7 +160,7 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName");
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
List<CardInfo> results = cardsDao.query(qb.prepare());
for (CardInfo card : results) {
addNewNames(card, names);
@ -176,7 +176,7 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName");
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
qb.where().not().like("types", new SelectArg('%' + CardType.LAND.name() + '%'));
List<CardInfo> results = cardsDao.query(qb.prepare());
for (CardInfo card : results) {
@ -193,7 +193,7 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName");
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
Where<CardInfo, Object> where = qb.where();
where.and(
where.not().like("supertypes", '%' + SuperType.BASIC.name() + '%'),
@ -214,7 +214,7 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName");
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
qb.where().not().like("supertypes", new SelectArg('%' + SuperType.BASIC.name() + '%'));
List<CardInfo> results = cardsDao.query(qb.prepare());
for (CardInfo card : results) {
@ -231,7 +231,7 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName");
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
qb.where().like("types", new SelectArg('%' + CardType.CREATURE.name() + '%'));
List<CardInfo> results = cardsDao.query(qb.prepare());
for (CardInfo card : results) {
@ -248,7 +248,7 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName");
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
qb.where().like("types", new SelectArg('%' + CardType.ARTIFACT.name() + '%'));
List<CardInfo> results = cardsDao.query(qb.prepare());
for (CardInfo card : results) {
@ -265,7 +265,7 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName");
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
Where<CardInfo, Object> where = qb.where();
where.and(
where.not().like("types", '%' + CardType.CREATURE.name() + '%'),
@ -286,7 +286,7 @@ public enum CardRepository {
Set<String> names = new TreeSet<>();
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName");
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
Where<CardInfo, Object> where = qb.where();
where.and(
where.not().like("types", '%' + CardType.ARTIFACT.name() + '%'),
@ -511,7 +511,7 @@ public enum CardRepository {
queryBuilder.where()
.eq("flipCardName", new SelectArg(name)).or()
.eq("secondSideName", new SelectArg(name)).or()
.eq("adventureSpellName", new SelectArg(name)).or()
.eq("spellOptionCardName", new SelectArg(name)).or()
.eq("modalDoubleFacedSecondSideName", new SelectArg(name));
results = cardsDao.query(queryBuilder.prepare());
} else {

View file

@ -15,7 +15,8 @@ public enum SpellAbilityType {
MODAL_LEFT("LeftModal SpellAbility"),
MODAL_RIGHT("RightModal SpellAbility"),
SPLICE("Spliced SpellAbility"),
ADVENTURE_SPELL("Adventure SpellAbility");
ADVENTURE_SPELL("Adventure SpellAbility"),
OMEN_SPELL("Omen SpellAbility");
private final String text;

View file

@ -13,6 +13,7 @@ public enum SubType {
ADVENTURE("Adventure", SubTypeSet.SpellType),
ARCANE("Arcane", SubTypeSet.SpellType),
LESSON("Lesson", SubTypeSet.SpellType),
OMEN("Omen", SubTypeSet.SpellType),
TRAP("Trap", SubTypeSet.SpellType),
// Battle subtypes
@ -274,6 +275,7 @@ public enum SubType {
MONGOOSE("Mongoose", SubTypeSet.CreatureType),
MONK("Monk", SubTypeSet.CreatureType),
MONKEY("Monkey", SubTypeSet.CreatureType),
MOOGLE("Moogle", SubTypeSet.CreatureType),
MOONFOLK("Moonfolk", SubTypeSet.CreatureType),
MOUNT("Mount", SubTypeSet.CreatureType),
MOUSE("Mouse", SubTypeSet.CreatureType),

View file

@ -1,9 +1,6 @@
package mage.filter.predicate.card;
import mage.cards.AdventureCard;
import mage.cards.Card;
import mage.cards.ModalDoubleFacedCard;
import mage.cards.SplitCard;
import mage.cards.*;
import mage.cards.mock.MockCard;
import mage.constants.SubType;
import mage.constants.SuperType;
@ -55,8 +52,8 @@ public class CardTextPredicate implements Predicate<Card> {
fullName = ((MockCard) input).getFullName(true);
} else if (input instanceof ModalDoubleFacedCard) {
fullName = input.getName() + MockCard.MODAL_DOUBLE_FACES_NAME_SEPARATOR + ((ModalDoubleFacedCard) input).getRightHalfCard().getName();
} else if (input instanceof AdventureCard) {
fullName = input.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + ((AdventureCard) input).getSpellCard().getName();
} else if (input instanceof CardWithSpellOption) {
fullName = input.getName() + MockCard.CARD_WITH_SPELL_OPTION_NAME_SEPARATOR + ((CardWithSpellOption) input).getSpellCard().getName();
}
if (fullName.toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH))) {
@ -107,8 +104,8 @@ public class CardTextPredicate implements Predicate<Card> {
}
}
if (input instanceof AdventureCard) {
for (String rule : ((AdventureCard) input).getSpellCard().getRules(game)) {
if (input instanceof CardWithSpellOption) {
for (String rule : ((CardWithSpellOption) input).getSpellCard().getRules(game)) {
if (rule.toLowerCase(Locale.ENGLISH).contains(token)) {
found = true;
break;

View file

@ -334,8 +334,8 @@ public abstract class GameImpl implements Game {
Card rightCard = ((ModalDoubleFacedCard) card).getRightHalfCard();
rightCard.setOwnerId(ownerId);
addCardToState(rightCard);
} else if (card instanceof AdventureCard) {
Card spellCard = ((AdventureCard) card).getSpellCard();
} else if (card instanceof CardWithSpellOption) {
Card spellCard = ((CardWithSpellOption) card).getSpellCard();
spellCard.setOwnerId(ownerId);
addCardToState(spellCard);
} else if (card.isTransformable() && card.getSecondCardFace() != null) {

View file

@ -1639,14 +1639,14 @@ public class GameState implements Serializable, Copyable<GameState> {
copiedParts.add(rightCopied);
// sync parts
((ModalDoubleFacedCard) copiedCard).setParts(leftCopied, rightCopied);
} else if (copiedCard instanceof AdventureCard) {
} else if (copiedCard instanceof CardWithSpellOption) {
// right
AdventureCardSpell rightOriginal = ((AdventureCard) copiedCard).getSpellCard();
AdventureCardSpell rightCopied = rightOriginal.copy();
SpellOptionCard rightOriginal = ((CardWithSpellOption) copiedCard).getSpellCard();
SpellOptionCard rightCopied = rightOriginal.copy();
prepareCardForCopy(rightOriginal, rightCopied, newController);
copiedParts.add(rightCopied);
// sync parts
((AdventureCard) copiedCard).setParts(rightCopied);
((CardWithSpellOption) copiedCard).setParts(rightCopied);
}
// main part prepare (must be called after other parts cause it change ids for all)

View file

@ -0,0 +1,34 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.PreventDamageToSourceEffect;
import mage.abilities.keyword.VanishingAbility;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Duration;
/**
* @author padfoothelix
*/
public final class AlienRhinoToken extends TokenImpl {
public AlienRhinoToken() {
super("Alien Rhino Token", "4/4 white Alien Rhino creature token");
cardType.add(CardType.CREATURE);
color.setWhite(true);
subtype.add(SubType.ALIEN);
subtype.add(SubType.RHINO);
power = new MageInt(4);
toughness = new MageInt(4);
}
private AlienRhinoToken(final AlienRhinoToken token) {
super(token);
}
@Override
public AlienRhinoToken copy() {
return new AlienRhinoToken(this);
}
}

View file

@ -1,29 +0,0 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author spjspj
*/
public final class CrushOfTentaclesToken extends TokenImpl {
public CrushOfTentaclesToken() {
super("Octopus Token", "8/8 blue Octopus creature");
this.cardType.add(CardType.CREATURE);
this.color.setBlue(true);
this.subtype.add(SubType.OCTOPUS);
this.power = new MageInt(8);
this.toughness = new MageInt(8);
}
private CrushOfTentaclesToken(final CrushOfTentaclesToken token) {
super(token);
}
public CrushOfTentaclesToken copy() {
return new CrushOfTentaclesToken(this);
}
}

View file

@ -11,7 +11,7 @@ import mage.constants.SubType;
public final class DinDragonToken extends TokenImpl {
public DinDragonToken() {
super("Dragon Token", "4/4 red Dinosaur Dragon creature token with flying");
super("Dinosaur Dragon Token", "4/4 red Dinosaur Dragon creature token with flying");
cardType.add(CardType.CREATURE);
color.setRed(true);
subtype.add(SubType.DINOSAUR);

View file

@ -7,9 +7,9 @@ import mage.constants.SubType;
/**
* @author spjspj
*/
public final class EyesOfTheWisentElementalToken extends TokenImpl {
public final class Elemental44GreenToken extends TokenImpl {
public EyesOfTheWisentElementalToken() {
public Elemental44GreenToken() {
super("Elemental Token", "4/4 green Elemental creature token");
cardType.add(CardType.CREATURE);
color.setGreen(true);
@ -18,11 +18,11 @@ public final class EyesOfTheWisentElementalToken extends TokenImpl {
toughness = new MageInt(4);
}
private EyesOfTheWisentElementalToken(final EyesOfTheWisentElementalToken token) {
private Elemental44GreenToken(final Elemental44GreenToken token) {
super(token);
}
public EyesOfTheWisentElementalToken copy() {
return new EyesOfTheWisentElementalToken(this);
public Elemental44GreenToken copy() {
return new Elemental44GreenToken(this);
}
}

View file

@ -7,13 +7,13 @@ import mage.constants.SubType;
/**
* @author spjspj
*/
public final class SeedGuardianToken extends TokenImpl {
public final class ElementalXXGreenToken extends TokenImpl {
public SeedGuardianToken() {
public ElementalXXGreenToken() {
this(1);
}
public SeedGuardianToken(int xValue) {
public ElementalXXGreenToken(int xValue) {
super("Elemental Token", "X/X green Elemental creature token");
cardType.add(CardType.CREATURE);
color.setGreen(true);
@ -22,11 +22,11 @@ public final class SeedGuardianToken extends TokenImpl {
toughness = new MageInt(xValue);
}
private SeedGuardianToken(final SeedGuardianToken token) {
private ElementalXXGreenToken(final ElementalXXGreenToken token) {
super(token);
}
public SeedGuardianToken copy() {
return new SeedGuardianToken(this);
public ElementalXXGreenToken copy() {
return new ElementalXXGreenToken(this);
}
}

View file

@ -1,28 +0,0 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author spjspj
*/
public final class GrovetenderDruidsPlantToken extends TokenImpl {
public GrovetenderDruidsPlantToken() {
super("Plant Token", "1/1 green Plant creature token");
cardType.add(CardType.CREATURE);
color.setGreen(true);
subtype.add(SubType.PLANT);
power = new MageInt(1);
toughness = new MageInt(1);
}
private GrovetenderDruidsPlantToken(final GrovetenderDruidsPlantToken token) {
super(token);
}
public GrovetenderDruidsPlantToken copy() {
return new GrovetenderDruidsPlantToken(this);
}
}

View file

@ -7,13 +7,13 @@ import mage.constants.SubType;
/**
* @author spjspj
*/
public final class FleshCarverHorrorToken extends TokenImpl {
public final class HorrorXXBlackToken extends TokenImpl {
public FleshCarverHorrorToken() {
public HorrorXXBlackToken() {
this(1);
}
public FleshCarverHorrorToken(int xValue) {
public HorrorXXBlackToken(int xValue) {
super("Horror Token", "X/X black Horror creature token");
cardType.add(CardType.CREATURE);
color.setBlack(true);
@ -22,11 +22,11 @@ public final class FleshCarverHorrorToken extends TokenImpl {
toughness = new MageInt(xValue);
}
private FleshCarverHorrorToken(final FleshCarverHorrorToken token) {
private HorrorXXBlackToken(final HorrorXXBlackToken token) {
super(token);
}
public FleshCarverHorrorToken copy() {
return new FleshCarverHorrorToken(this);
public HorrorXXBlackToken copy() {
return new HorrorXXBlackToken(this);
}
}

View file

@ -0,0 +1,33 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.keyword.WardAbility;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Duration;
/**
* @author padfoothelix
*/
public final class Human11WithWard2Token extends TokenImpl {
public Human11WithWard2Token() {
super("Human Token", "1/1 white Human creature token with ward {2}");
cardType.add(CardType.CREATURE);
color.setWhite(true);
subtype.add(SubType.HUMAN);
power = new MageInt(1);
toughness = new MageInt(1);
this.addAbility(new WardAbility(new GenericManaCost(2)));
}
private Human11WithWard2Token(final Human11WithWard2Token token) {
super(token);
}
@Override
public Human11WithWard2Token copy() {
return new Human11WithWard2Token(this);
}
}

View file

@ -10,7 +10,7 @@ import mage.constants.SubType;
public final class HumanRogueToken extends TokenImpl {
public HumanRogueToken() {
super("Human Token", "1/1 white Human Rogue creature token");
super("Human Rogue Token", "1/1 white Human Rogue creature token");
cardType.add(CardType.CREATURE);
color.setWhite(true);
subtype.add(SubType.HUMAN);

View file

@ -1,30 +0,0 @@
package mage.game.permanent.token;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.MageInt;
/**
* @author spjspj
*/
public final class MarathWillOfTheWildElementalToken extends TokenImpl {
public MarathWillOfTheWildElementalToken() {
super("Elemental Token", "X/X green Elemental creature token");
cardType.add(CardType.CREATURE);
subtype.add(SubType.ELEMENTAL);
color.setGreen(true);
power = new MageInt(0);
toughness = new MageInt(0);
}
private MarathWillOfTheWildElementalToken(final MarathWillOfTheWildElementalToken token) {
super(token);
}
public MarathWillOfTheWildElementalToken copy() {
return new MarathWillOfTheWildElementalToken(this);
}
}

View file

@ -5,25 +5,20 @@ import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author FenrisulfrX
* @author Quercitron
*/
public final class MinionToken extends TokenImpl {
public MinionToken() {
this("DDE");
}
public MinionToken(String setCode) {
super("Phyrexian Minion Token", "X/X black Phyrexian Minion creature token");
super("Minion Token", "1/1 black Minion creature token");
cardType.add(CardType.CREATURE);
subtype.add(SubType.PHYREXIAN);
subtype.add(SubType.MINION);
color.setBlack(true);
power = new MageInt(0);
toughness = new MageInt(0);
power = new MageInt(1);
toughness = new MageInt(1);
}
private MinionToken(final MinionToken token) {
protected MinionToken(final MinionToken token) {
super(token);
}

View file

@ -1,28 +0,0 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author Quercitron
*/
public final class MinionToken2 extends TokenImpl {
public MinionToken2() {
super("Minion Token", "1/1 black Minion creature token");
cardType.add(CardType.CREATURE);
subtype.add(SubType.MINION);
color.setBlack(true);
power = new MageInt(1);
toughness = new MageInt(1);
}
protected MinionToken2(final MinionToken2 token) {
super(token);
}
public MinionToken2 copy() {
return new MinionToken2(this);
}
}

View file

@ -1,44 +0,0 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.target.common.TargetCardInGraveyard;
/**
* @author spjspj
*/
public final class NighteyesTheDesecratorToken extends TokenImpl {
public NighteyesTheDesecratorToken() {
super("Nighteyes the Desecrator Token", "");
this.supertype.add(SuperType.LEGENDARY);
cardType.add(CardType.CREATURE);
color.setBlack(true);
subtype.add(SubType.RAT);
subtype.add(SubType.WIZARD);
power = new MageInt(4);
toughness = new MageInt(2);
// {4}{B}: Put target creature card from a graveyard onto the battlefield under your control.
Ability ability = new SimpleActivatedAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(), new ManaCostsImpl<>("{4}{B}"));
ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD));
this.addAbility(ability);
}
private NighteyesTheDesecratorToken(final NighteyesTheDesecratorToken token) {
super(token);
}
public NighteyesTheDesecratorToken copy() {
return new NighteyesTheDesecratorToken(this);
}
}

View file

@ -0,0 +1,33 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author FenrisulfrX
*/
public final class PhyrexianMinionToken extends TokenImpl {
public PhyrexianMinionToken() {
this(1);
}
public PhyrexianMinionToken(int xValue) {
super("Phyrexian Minion Token", "X/X black Phyrexian Minion creature token");
cardType.add(CardType.CREATURE);
subtype.add(SubType.PHYREXIAN);
subtype.add(SubType.MINION);
color.setBlack(true);
power = new MageInt(xValue);
toughness = new MageInt(xValue);
}
private PhyrexianMinionToken(final PhyrexianMinionToken token) {
super(token);
}
public PhyrexianMinionToken copy() {
return new PhyrexianMinionToken(this);
}
}

View file

@ -4,9 +4,12 @@ import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
public final class GrismoldPlantToken extends TokenImpl {
/**
* @author spjspj
*/
public final class Plant11Token extends TokenImpl {
public GrismoldPlantToken() {
public Plant11Token() {
super("Plant Token", "1/1 green Plant creature token");
cardType.add(CardType.CREATURE);
color.setGreen(true);
@ -15,11 +18,11 @@ public final class GrismoldPlantToken extends TokenImpl {
toughness = new MageInt(1);
}
private GrismoldPlantToken(final GrismoldPlantToken token) {
private Plant11Token(final Plant11Token token) {
super(token);
}
public GrismoldPlantToken copy() {
return new GrismoldPlantToken(this);
public Plant11Token copy() {
return new Plant11Token(this);
}
}

View file

@ -1,29 +0,0 @@
package mage.game.permanent.token;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.MageInt;
/**
* @author spjspj
*/
public final class RallyTheHordeWarriorToken extends TokenImpl {
public RallyTheHordeWarriorToken() {
super("Warrior Token", "1/1 red Warrior creature token");
cardType.add(CardType.CREATURE);
color.setRed(true);
subtype.add(SubType.WARRIOR);
power = new MageInt(1);
toughness = new MageInt(1);
}
private RallyTheHordeWarriorToken(final RallyTheHordeWarriorToken token) {
super(token);
}
public RallyTheHordeWarriorToken copy() {
return new RallyTheHordeWarriorToken(this);
}
}

View file

@ -1,30 +0,0 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.abilities.keyword.FlyingAbility;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author spjspj
*/
public final class SorinSolemnVisitorVampireToken extends TokenImpl {
public SorinSolemnVisitorVampireToken() {
super("Vampire Token", "2/2 black Vampire creature token with flying");
cardType.add(CardType.CREATURE);
color.setBlack(true);
subtype.add(SubType.VAMPIRE);
power = new MageInt(2);
toughness = new MageInt(2);
addAbility(FlyingAbility.getInstance());
}
private SorinSolemnVisitorVampireToken(final SorinSolemnVisitorVampireToken token) {
super(token);
}
public SorinSolemnVisitorVampireToken copy() {
return new SorinSolemnVisitorVampireToken(this);
}
}

View file

@ -1,32 +0,0 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author spjspj
*/
public final class SpoilsOfBloodHorrorToken extends TokenImpl {
public SpoilsOfBloodHorrorToken() {
this(1);
}
public SpoilsOfBloodHorrorToken(int xValue) {
super("Horror Token", "X/X black Horror creature token");
cardType.add(CardType.CREATURE);
color.setBlack(true);
subtype.add(SubType.HORROR);
power = new MageInt(xValue);
toughness = new MageInt(xValue);
}
private SpoilsOfBloodHorrorToken(final SpoilsOfBloodHorrorToken token) {
super(token);
}
public SpoilsOfBloodHorrorToken copy() {
return new SpoilsOfBloodHorrorToken(this);
}
}

View file

@ -1,29 +0,0 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author spjspj
*/
public final class WalkerOfTheGroveToken extends TokenImpl {
public WalkerOfTheGroveToken() {
super("Elemental Token", "4/4 green Elemental creature token");
cardType.add(CardType.CREATURE);
this.subtype.add(SubType.ELEMENTAL);
this.color.setGreen(true);
power = new MageInt(4);
toughness = new MageInt(4);
}
private WalkerOfTheGroveToken(final WalkerOfTheGroveToken token) {
super(token);
}
public WalkerOfTheGroveToken copy() {
return new WalkerOfTheGroveToken(this);
}
}

View file

@ -209,10 +209,11 @@ public class Spell extends StackObjectImpl implements Card {
+ " using " + this.getSpellAbility().getSpellAbilityCastMode();
}
if (card instanceof AdventureCardSpell) {
AdventureCard adventureCard = ((AdventureCardSpell) card).getParentCard();
return GameLog.replaceNameByColoredName(card, getSpellAbility().toString(), adventureCard)
+ " as Adventure spell of " + GameLog.getColoredObjectIdName(adventureCard);
if (card instanceof SpellOptionCard) {
CardWithSpellOption parentCard = ((SpellOptionCard) card).getParentCard();
String type = ((SpellOptionCard) card).getSpellType();
return GameLog.replaceNameByColoredName(card, getSpellAbility().toString(), parentCard)
+ " as " + type + " spell of " + GameLog.getColoredObjectIdName(parentCard);
}
if (card instanceof ModalDoubleFacedCardHalf) {
@ -539,8 +540,8 @@ public class Spell extends StackObjectImpl implements Card {
public String getIdName() {
String idName;
if (card != null) {
if (card instanceof AdventureCardSpell) {
idName = ((AdventureCardSpell) card).getParentCard().getId().toString().substring(0, 3);
if (card instanceof SpellOptionCard) {
idName = ((SpellOptionCard) card).getParentCard().getId().toString().substring(0, 3);
} else {
idName = card.getId().toString().substring(0, 3);
}

View file

@ -4068,11 +4068,11 @@ public abstract class PlayerImpl implements Player, Serializable {
getPlayableFromObjectSingle(game, fromZone, mainCard.getLeftHalfCard(), mainCard.getLeftHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, mainCard.getRightHalfCard(), mainCard.getRightHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, mainCard, mainCard.getSharedAbilities(game), availableMana, output);
} else if (object instanceof AdventureCard) {
} else if (object instanceof CardWithSpellOption) {
// adventure must use different card characteristics for different spells (main or adventure)
AdventureCard adventureCard = (AdventureCard) object;
getPlayableFromObjectSingle(game, fromZone, adventureCard.getSpellCard(), adventureCard.getSpellCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, adventureCard, adventureCard.getSharedAbilities(game), availableMana, output);
CardWithSpellOption cardWithSpellOption = (CardWithSpellOption) object;
getPlayableFromObjectSingle(game, fromZone, cardWithSpellOption.getSpellCard(), cardWithSpellOption.getSpellCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, cardWithSpellOption, cardWithSpellOption.getSharedAbilities(game), availableMana, output);
} else if (object instanceof Card) {
getPlayableFromObjectSingle(game, fromZone, object, ((Card) object).getAbilities(game), availableMana, output);
} else if (object instanceof StackObject) {

View file

@ -50,7 +50,7 @@ public class FixedTargets extends TargetPointerImpl {
.collect(Collectors.toList()), game);
}
public FixedTargets(List<MageObjectReference> morList) {
public FixedTargets(Collection<MageObjectReference> morList) {
super();
targets.addAll(morList);
this.setInitialized(); // no need dynamic init

View file

@ -1086,13 +1086,22 @@ public final class CardUtil {
* @param game the Game from checkTrigger() or watch()
* @return the StackObject which targeted the source, or null if not found
*/
public static StackObject getTargetingStackObject(GameEvent event, Game game) {
public static StackObject getTargetingStackObject(String checkingReference, GameEvent event, Game game) {
// In case of multiple simultaneous triggered abilities from the same source,
// need to get the actual one that targeted, see #8026, #8378
// Also avoids triggering on cancelled selections, see #8802
String stateKey = "targetedMap" + checkingReference;
Map<UUID, Set<UUID>> targetMap = (Map<UUID, Set<UUID>>) game.getState().getValue(stateKey);
// targetMap: key - targetId; value - Set of stackObject Ids
if (targetMap == null) {
targetMap = new HashMap<>();
} else {
targetMap = new HashMap<>(targetMap); // must have new object reference if saved back to game state
}
Set<UUID> targetingObjects = targetMap.computeIfAbsent(event.getTargetId(), k -> new HashSet<>());
for (StackObject stackObject : game.getStack()) {
Ability stackAbility = stackObject.getStackAbility();
if (stackAbility == null || !stackAbility.getSourceId().equals(event.getSourceId())) {
if (stackAbility == null || !stackAbility.getSourceId().equals(event.getSourceId()) || targetingObjects.contains(stackObject.getId())) {
continue;
}
if (CardUtil.getAllSelectedTargets(stackAbility, game).contains(event.getTargetId())) {
@ -1263,7 +1272,7 @@ public final class CardUtil {
Card permCard;
if (card instanceof SplitCard) {
permCard = card;
} else if (card instanceof AdventureCard) {
} else if (card instanceof CardWithSpellOption) {
permCard = card;
} else if (card instanceof ModalDoubleFacedCard) {
permCard = ((ModalDoubleFacedCard) card).getLeftHalfCard();
@ -1451,9 +1460,9 @@ public final class CardUtil {
if (cardToCast instanceof CardWithHalves) {
cards.add(((CardWithHalves) cardToCast).getLeftHalfCard());
cards.add(((CardWithHalves) cardToCast).getRightHalfCard());
} else if (cardToCast instanceof AdventureCard) {
} else if (cardToCast instanceof CardWithSpellOption) {
cards.add(cardToCast);
cards.add(((AdventureCard) cardToCast).getSpellCard());
cards.add(((CardWithSpellOption) cardToCast).getSpellCard());
} else {
cards.add(cardToCast);
}
@ -1642,9 +1651,9 @@ public final class CardUtil {
}
// handle adventure cards
if (card instanceof AdventureCard) {
if (card instanceof CardWithSpellOption) {
Card creatureCard = card.getMainCard();
Card spellCard = ((AdventureCard) card).getSpellCard();
Card spellCard = ((CardWithSpellOption) card).getSpellCard();
if (manaCost != null) {
// get additional cost if any
Costs<Cost> additionalCostsCreature = creatureCard.getSpellAbility().getCosts();
@ -1682,9 +1691,9 @@ public final class CardUtil {
game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), null);
game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), null);
}
if (card instanceof AdventureCard) {
if (card instanceof CardWithSpellOption) {
Card creatureCard = card.getMainCard();
Card spellCard = ((AdventureCard) card).getSpellCard();
Card spellCard = ((CardWithSpellOption) card).getSpellCard();
game.getState().setValue("PlayFromNotOwnHandZone" + creatureCard.getId(), null);
game.getState().setValue("PlayFromNotOwnHandZone" + spellCard.getId(), null);
}
@ -2069,8 +2078,8 @@ public final class CardUtil {
res.add(mainCard);
res.add(mainCard.getLeftHalfCard());
res.add(mainCard.getRightHalfCard());
} else if (object instanceof AdventureCard || object instanceof AdventureCardSpell) {
AdventureCard mainCard = (AdventureCard) ((Card) object).getMainCard();
} else if (object instanceof CardWithSpellOption || object instanceof SpellOptionCard) {
CardWithSpellOption mainCard = (CardWithSpellOption) ((Card) object).getMainCard();
res.add(mainCard);
res.add(mainCard.getSpellCard());
} else if (object instanceof Spell) {

View file

@ -13,10 +13,7 @@ import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.GetXValue;
import mage.abilities.effects.Effect;
import mage.abilities.mana.*;
import mage.cards.AdventureCard;
import mage.cards.Card;
import mage.cards.ModalDoubleFacedCard;
import mage.cards.SplitCard;
import mage.cards.*;
import mage.choices.Choice;
import mage.constants.ColoredManaSymbol;
import mage.constants.ManaType;
@ -644,8 +641,8 @@ public final class ManaUtil {
Card secondSide;
if (card instanceof SplitCard) {
secondSide = ((SplitCard) card).getRightHalfCard();
} else if (card instanceof AdventureCard) {
secondSide = ((AdventureCard) card).getSpellCard();
} else if (card instanceof CardWithSpellOption) {
secondSide = ((CardWithSpellOption) card).getSpellCard();
} else if (card instanceof ModalDoubleFacedCard) {
secondSide = ((ModalDoubleFacedCard) card).getRightHalfCard();
} else {

View file

@ -29,7 +29,7 @@ public class NumberOfTimesPermanentTargetedATurnWatcher extends Watcher {
if (event.getType() != GameEvent.EventType.TARGETED) {
return;
}
StackObject targetingObject = CardUtil.getTargetingStackObject(event, game);
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getKey(), event, game);
if (targetingObject == null || CardUtil.checkTargetedEventAlreadyUsed(this.getKey(), targetingObject, event, game)) {
return;
}