package magic.model; import magic.ai.ArtificialScoringSystem; import magic.data.CardDefinitions; import magic.data.CardProperty; import magic.model.event.MagicActivation; import magic.model.event.MagicActivationHints; import magic.model.event.MagicHandCastActivation; import magic.model.event.MagicCardEvent; import magic.model.event.MagicEvent; import magic.model.event.MagicEventSource; import magic.model.event.MagicManaActivation; import magic.model.event.MagicPayManaCostEvent; import magic.model.event.MagicPermanentActivation; import magic.model.event.MagicPlayCardEvent; import magic.model.event.MagicTiming; import magic.model.mstatic.MagicCDA; import magic.model.mstatic.MagicStatic; import magic.model.trigger.MagicTrigger; import magic.model.trigger.MagicWhenComesIntoPlayTrigger; import magic.model.trigger.MagicWhenDrawnTrigger; import magic.model.trigger.MagicWhenPutIntoGraveyardTrigger; import magic.model.trigger.MagicWhenSpellIsCastTrigger; import magic.model.trigger.MagicWhenCycleTrigger; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.Date; public class MagicCardDefinition implements MagicAbilityStore { public static final MagicCardDefinition UNKNOWN = new MagicCardDefinition() { //definition for unknown cards @Override protected void initialize() { setName("Unknown"); setDistinctName("Unknown"); setToken(); setValue(1); addType(MagicType.Creature); setCost(MagicManaCost.create("{15}")); setPowerToughness(1,1); addAbility(MagicAbility.Defender); addAbility(MagicAbility.CannotBeCountered); addAbility(MagicAbility.Shroud); setTiming(MagicTiming.Main); setIndex(1000000); } }; public static final MagicCardDefinition MORPH = new MagicCardDefinition() { //definition for face down cards @Override protected void initialize() { setName(""); setDistinctName("2/2 face-down creature"); setValue(1); addType(MagicType.Creature); setCost(MagicManaCost.create("{0}")); setPowerToughness(2, 2); setTiming(MagicTiming.Main); setIndex(1000001); } }; // name displayed in UI, may be repeated in tokens private String name; // name used for mapping and persistence, must be unique private String distinctName; private boolean isValid = true; private String imageURL; private int imageCount = 1; private Date imageUpdated; private int index=-1; private double value; private int removal; private int score=-1; // not initialized private MagicRarity rarity; private boolean token = false; private boolean hidden = false; private boolean excludeManaOrCombat = false; private int typeFlags; private EnumSet cardType = EnumSet.noneOf(MagicType.class); private EnumSet subTypeFlags = EnumSet.noneOf(MagicSubType.class); private EnumSet abilityFlags = EnumSet.noneOf(MagicAbility.class); private int colorFlags = -1; private MagicManaCost cost=MagicManaCost.ZERO; private String manaSourceText=""; private final int[] manaSource=new int[MagicColor.NR_COLORS]; private int power; private int toughness; private String text = ""; private MagicStaticType staticType=MagicStaticType.None; private MagicTiming timing=MagicTiming.None; private MagicCardEvent cardEvent=MagicPlayCardEvent.create(); private final Collection> permActivations=new ArrayList>(); private final Collection> morphActivations=new ArrayList>(); private final LinkedList> handActivations = new LinkedList>(); private final LinkedList> graveyardActivations = new LinkedList>(); private final Collection CDAs = new ArrayList(); private final Collection> triggers = new ArrayList>(); private final Collection statics=new ArrayList(); private final LinkedList comeIntoPlayTriggers = new LinkedList(); private final Collection spellIsCastTriggers = new ArrayList(); private final Collection cycleTriggers = new ArrayList(); private final Collection drawnTriggers = new ArrayList(); private final Collection putIntoGraveyardTriggers = new ArrayList(); private final Collection manaActivations=new ArrayList(); private final Collection costEventSources=new ArrayList(); private MagicCardDefinition flipCardDefinition; private MagicCardDefinition transformCardDefinition; private String abilityProperty; private String requiresGroovy; private String effectProperty; private String flipCardName; private String transformCardName; public MagicCardDefinition() { initialize(); } public static MagicCardDefinition create(final MagicCardDefinitionInit init) { final MagicCardDefinition cdef = new MagicCardDefinition(); init.initialize(cdef); cdef.validate(); return cdef; } protected void initialize() {} public void setAbilityProperty(final String value) { abilityProperty = value; } public void setRequiresGroovy(final String value) { requiresGroovy = value; } public void setEffectProperty(final String value) { effectProperty = value; } public void setFlipCardName(final String value) { flipCardName = value; } public void setTransformCardName(final String value) { transformCardName = value; } public void setHidden() { hidden = true; } public boolean isHidden() { return hidden; } public void loadAbilities() { if (requiresGroovy != null) { CardProperty.LOAD_GROOVY_CODE.setProperty(this, requiresGroovy); requiresGroovy = null; } if (abilityProperty != null) { CardProperty.LOAD_ABILITY.setProperty(this, abilityProperty); abilityProperty = null; } if (effectProperty != null) { CardProperty.LOAD_EFFECT.setProperty(this, effectProperty); effectProperty = null; } if (getFlippedDefinition().isHidden()) { flipCardDefinition.loadAbilities(); } if (getTransformedDefinition().isHidden()) { transformCardDefinition.loadAbilities(); } } public boolean isValid() { return isValid; } public boolean isInvalid() { return isValid == false; } public void setInvalid() { isValid = false; } public void setImageUpdated(final Date d) { imageUpdated = d; } public boolean isImageUpdatedAfter(final Date d) { return imageUpdated != null && imageUpdated.after(d); } /** * Returns the name of the card exactly as it appears on the printed card. *

* Note that in the case of token cards this means it may return the * same name (eg. five different Wurm tokens would all return "Wurm"). * * @see getDistinctName() */ public String getName() { return name; } public void setName(final String name) { this.name = name; } /** * Returns a guaranteed distinct card name. *

* In most cases this will be the same as {@link getName()} but for tokens * of the same type (eg. Wurm) this will return a name that clearly identifies * the card (eg. 5/5 green Wurm creature token with trample). * */ public String getDistinctName() { return distinctName; } public void setDistinctName(String aName) { assert (this.name.equals(aName) ? this.name == aName : true) : "Same name but using two separate strings. Should reference same string for efficiency."; distinctName = aName; } /** * Returns the name of the card containing only ASCII characters. */ public String getAsciiName() { return CardDefinitions.getASCII(distinctName); } /** * Returns the name of the script/groovy file without extension */ public String getFilename() { return CardDefinitions.getCanonicalName(distinctName); } public void setIndex(final int index) { this.index=index; } public int getIndex() { return index; } public String getImageName() { return token ? CardDefinitions.getCanonicalName(distinctName): distinctName.replaceAll("[<>:\"/\\\\|?*\\x00-\\x1F]", "_"); } public void setImageCount(final int count) { this.imageCount = count; } public int getImageCount() { return imageCount; } public void setImageURL(final String imageURL) { this.imageURL = imageURL; } public String getImageURL() { return imageURL; } public String getCardTextName() { return getImageName(); } public void setValue(final double aValue) { value = aValue; } public double getValue() { return value; } public void setRemoval(final int removal) { this.removal=removal; } public int getRemoval() { return removal; } public int getScore() { if (score<0) { score=ArtificialScoringSystem.getCardDefinitionScore(this); } return score; } public int getFreeScore() { if (score<0) { score=ArtificialScoringSystem.getFreeCardDefinitionScore(this); } return score; } public void setRarity(final char c) { this.rarity = MagicRarity.getRarity(c); } public boolean isRarity(final MagicRarity r) { return this.rarity == r; } public int getRarity() { return rarity.ordinal(); } public String getRarityString() { return (rarity == null ? "" : rarity.getName()); } public void setToken() { token=true; } public boolean isToken() { return token; } int getTypeFlags() { return typeFlags; } public void addType(final MagicType type) { typeFlags |= type.getMask(); if (type == MagicType.Land) { if (colorFlags == -1) { // Lands default to colorless colorFlags = 0; } else { assert colorFlags != 0 : "redundant color declaration: " + colorFlags; } } cardType.add(type); } public Set getCardType() { return cardType; } public boolean hasType(final MagicType type) { return (typeFlags&type.getMask())!=0; } public MagicCardDefinition getFlippedDefinition() { if (flipCardDefinition == null) { flipCardDefinition = isFlipCard() ? CardDefinitions.getCard(flipCardName) : MagicCardDefinition.UNKNOWN; } return flipCardDefinition; } public MagicCardDefinition getTransformedDefinition() { if (transformCardDefinition == null) { transformCardDefinition = isDoubleFaced() ? CardDefinitions.getCard(transformCardName) : MagicCardDefinition.UNKNOWN; } return transformCardDefinition; } public boolean isBasic() { return hasType(MagicType.Basic); } public boolean isLand() { return hasType(MagicType.Land); } public boolean isCreature() { return hasType(MagicType.Creature); } public boolean isArtifact() { return hasType(MagicType.Artifact); } public boolean isEquipment() { return hasSubType(MagicSubType.Equipment); } public boolean isPlaneswalker() { return hasType(MagicType.Planeswalker); } public boolean isEnchantment() { return hasType(MagicType.Enchantment); } public boolean isLegendary() { return hasType(MagicType.Legendary); } public boolean isTribal() { return hasType(MagicType.Tribal); } public boolean isSnow() { return hasType(MagicType.Snow); } public boolean isWorld() { return hasType(MagicType.World); } public boolean isAura() { return isEnchantment() && hasSubType(MagicSubType.Aura); } public boolean isInstant() { return hasType(MagicType.Instant); } public boolean isSorcery() { return hasType(MagicType.Sorcery); } public boolean isSpell() { return isInstant()||isSorcery(); } public boolean isPermanent() { return isSpell() == false; } public boolean isFlipCard() { return flipCardName != null; } public boolean isDoubleFaced() { return transformCardName != null; } public boolean hasMultipleAspects() { return isFlipCard() || isDoubleFaced(); } public String getLongTypeString() { if (isBasic()) { if (isSnow()) { return "Basic Snow " + getTypeString(); } else { return "Basic " + getTypeString(); } } if (isLegendary()) { if (isSnow()) { return "Legendary Snow " + getTypeString(); } else { return "Legendary " + getTypeString(); } } if (isTribal()) { return "Tribal " + getTypeString(); } if (isSnow()) { return "Snow " + getTypeString(); } if (isWorld()) { return "World " + getTypeString(); } return getTypeString(); } public String getTypeString() { final StringBuilder sb = new StringBuilder(); if (isLand()) { sb.append(MagicType.Land.toString()); } if (isArtifact()) { if (sb.length() > 0) { sb.append(" "); } sb.append(MagicType.Artifact.toString()); } if (isCreature()) { if (sb.length() > 0) { sb.append(" "); } sb.append(MagicType.Creature.toString()); } if (isEnchantment()) { if (sb.length() > 0) { sb.append(" "); } sb.append(MagicType.Enchantment.toString()); } if (isInstant()) { if (sb.length() > 0) { sb.append(" "); } sb.append(MagicType.Instant.toString()); } if (isSorcery()) { if (sb.length() > 0) { sb.append(" "); } sb.append(MagicType.Sorcery.toString()); } return sb.toString(); } public boolean usesStack() { return !isLand(); } public void setSubTypes(final String[] subTypeNames) { subTypeFlags = MagicSubType.getSubTypes(subTypeNames); } public void addSubType(final MagicSubType subType) { subTypeFlags.add(subType); } EnumSet genSubTypeFlags() { return subTypeFlags.clone(); } public EnumSet getSubTypeFlags() { final EnumSet subTypes = genSubTypeFlags(); applyCDASubType(null, null, subTypes); return subTypes; } public void applyCDASubType(final MagicGame game, final MagicPlayer player, final Set flags) { for (final MagicCDA lv : CDAs) { lv.getSubTypeFlags(game, player, flags); } } public String getSubTypeString() { final String brackets = getSubTypeFlags().toString(); // [...,...] if (brackets.length() <= 2) { return ""; } return brackets.substring(1, brackets.length() - 1); } public boolean hasSubType(final MagicSubType subType) { return getSubTypeFlags().contains(subType); } public void setColors(final String colors) { colorFlags = MagicColor.getFlags(colors); assert cost == MagicManaCost.ZERO || colorFlags != cost.getColorFlags() : "redundant color declaration: " + colorFlags; } public boolean hasColor(final MagicColor color) { return (colorFlags&color.getMask())!=0; } public boolean isColorless() { return colorFlags == 0; } public int getColorFlags() { return colorFlags; } public int getConvertedCost() { return cost.getConvertedCost(); } public boolean hasConvertedCost(final int c) { return getConvertedCost() == c; } public int getCostBucket() { switch (getConvertedCost()) { case 0: case 1: case 2: return 0; case 3: case 4: return 1; default: return 2; } } public boolean hasX() { return cost.hasX(); } public void setCost(final MagicManaCost aCost) { cost = aCost; if (colorFlags == -1) { // color defaults to follow mana cost colorFlags = cost.getColorFlags(); } else { assert colorFlags != cost.getColorFlags() : "redundant color declaration: " + colorFlags; } } public void validate() { //every card should have a timing hint if (!isToken() && getTiming() == MagicTiming.None) { throw new RuntimeException( getName() + " does not have a timing hint" ); } //check colorFlags is set if (colorFlags == -1) { throw new RuntimeException(name + "'s color is not set"); } //every Aura should have an MagicPlayAuraEvent if (isAura() && cardEvent == MagicPlayCardEvent.create()) { throw new RuntimeException(name + " does not have the enchant property"); } } public MagicManaCost getCost() { return cost; } public Iterable getCostEvent(final MagicCard source) { final List costEvent = new ArrayList(); if (cost != MagicManaCost.ZERO) { costEvent.add(new MagicPayManaCostEvent( source, cost )); } for (final MagicEventSource eventSource : costEventSources) { costEvent.add(eventSource.getEvent(source)); } return costEvent; } public boolean isPlayable(final MagicDeckProfile profile) { if (isLand()) { int source = 0; for (final MagicColor color : profile.getColors()) { source += getManaSource(color); } return source > 4; } else { return cost.getCostScore(profile) > 0; } } public void setManaSourceText(final String sourceText) { manaSourceText=sourceText; for (int index=0;index genAbilityFlags() { return abilityFlags.clone(); } public boolean hasAbility(final MagicAbility ability) { return abilityFlags.contains(ability); } public void setText(final String text) { this.text = text; } public String getText() { return this.text; } public String getFlattenedText() { return this.text.replace("\n", " "); } public void setStaticType(final MagicStaticType staticType) { this.staticType=staticType; } MagicStaticType getStaticType() { return staticType; } public void setTiming(final MagicTiming timing) { this.timing=timing; } public MagicTiming getTiming() { return timing; } public void add(final MagicChangeCardDefinition mod) { mod.change(this); } public void setEvent(final MagicCardEvent aCardEvent) { assert cardEvent == MagicPlayCardEvent.create() : "Attempting to set two MagicCardEvents for " + this; cardEvent = aCardEvent; } public MagicCardEvent getCardEvent() { return cardEvent; } public MagicActivationHints getActivationHints() { return new MagicActivationHints(timing,true); } // cast card activation is the first element of handActivations public MagicActivation getCastActivation() { assert handActivations.size() >= 1 : this + " has no card activations"; return handActivations.getFirst(); } public Collection> getHandActivations() { return handActivations; } public Collection> getGraveyardActivations() { return graveyardActivations; } public void addCDA(final MagicCDA cda) { CDAs.add(cda); } public void addCostEvent(final MagicEventSource eventSource) { costEventSources.add(eventSource); } public void addTrigger(final MagicWhenSpellIsCastTrigger trigger) { spellIsCastTriggers.add(trigger); } public void addTrigger(final MagicWhenCycleTrigger trigger) { cycleTriggers.add(trigger); } public void addTrigger(final MagicWhenComesIntoPlayTrigger trigger) { if (trigger.usesStack()) { comeIntoPlayTriggers.add(trigger); } else { comeIntoPlayTriggers.addFirst(trigger); } } public void addTrigger(final MagicWhenPutIntoGraveyardTrigger trigger) { putIntoGraveyardTriggers.add(trigger); } public void addTrigger(final MagicWhenDrawnTrigger trigger) { drawnTriggers.add(trigger); } public void addTrigger(final MagicTrigger trigger) { triggers.add(trigger); } public void addStatic(final MagicStatic mstatic) { statics.add(mstatic); } public Collection> getTriggers() { return triggers; } public Collection getStatics() { return statics; } public Collection getSpellIsCastTriggers() { return spellIsCastTriggers; } public Collection getCycleTriggers() { return cycleTriggers; } public Collection getComeIntoPlayTriggers() { return comeIntoPlayTriggers; } public Collection getPutIntoGraveyardTriggers() { return putIntoGraveyardTriggers; } public Collection getDrawnTriggers() { return drawnTriggers; } public void addAct(final MagicPermanentActivation activation) { permActivations.add(activation); } public void addMorphAct(final MagicPermanentActivation activation) { morphActivations.add(activation); } public void addHandAct(final MagicHandCastActivation activation) { handActivations.add(activation); } public void addGraveyardAct(final MagicHandCastActivation activation) { graveyardActivations.add(activation); } public void setHandAct(final MagicHandCastActivation activation) { assert handActivations.size() == 1 : "removing multiple (" + handActivations.size() + ") card activations"; handActivations.clear(); handActivations.add(activation); } public Collection> getActivations() { return permActivations; } public Collection> getMorphActivations() { return morphActivations; } public void addManaAct(final MagicManaActivation activation) { manaActivations.add(activation); } public Collection getManaActivations() { return manaActivations; } public void setExcludeManaOrCombat() { excludeManaOrCombat=true; } public boolean hasExcludeManaOrCombat() { return excludeManaOrCombat; } private boolean subTypeHasText(final String s) { final MagicSubType[] subTypeValues = MagicSubType.values(); for (final MagicSubType subtype : subTypeValues) { if (subTypeFlags.contains(subtype) && subtype.toString().toLowerCase().contains(s)) { return true; } } return false; } private boolean abilityHasText(final String s) { for (final MagicAbility ability : MagicAbility.values()) { if (hasAbility(ability) && ability.getName().toLowerCase().contains(s)) { return true; } } return false; } public boolean hasText(String s) { s = s.toLowerCase(); return ( CardDefinitions.getASCII(distinctName).toLowerCase().contains(s) || CardDefinitions.getASCII(name).toLowerCase().contains(s) || subTypeHasText(s) || abilityHasText(s) || CardDefinitions.getASCII(getText()).toLowerCase().contains(s) ); } @Override public String toString() { return getName(); } public static final Comparator NAME_COMPARATOR_DESC=new Comparator() { @Override public int compare(final MagicCardDefinition cardDefinition1,final MagicCardDefinition cardDefinition2) { return cardDefinition1.getName().compareTo(cardDefinition2.getName()); } }; public static final Comparator NAME_COMPARATOR_ASC=new Comparator() { @Override public int compare(final MagicCardDefinition cardDefinition1,final MagicCardDefinition cardDefinition2) { return NAME_COMPARATOR_DESC.compare(cardDefinition2, cardDefinition1); } }; public static final Comparator CONVERTED_COMPARATOR_DESC=new Comparator() { @Override public int compare(final MagicCardDefinition cardDefinition1,final MagicCardDefinition cardDefinition2) { final int cdif=cardDefinition1.getConvertedCost()-cardDefinition2.getConvertedCost(); if (cdif!=0) { return cdif; } return cardDefinition1.getName().compareTo(cardDefinition2.getName()); } }; public static final Comparator CONVERTED_COMPARATOR_ASC=new Comparator() { @Override public int compare(final MagicCardDefinition cardDefinition1,final MagicCardDefinition cardDefinition2) { return CONVERTED_COMPARATOR_DESC.compare(cardDefinition2, cardDefinition1); } }; public static final Comparator TYPE_COMPARATOR_DESC=new Comparator() { @Override public int compare(final MagicCardDefinition cardDefinition1,final MagicCardDefinition cardDefinition2) { final int c = cardDefinition1.getTypeString().compareTo(cardDefinition2.getTypeString()); if (c == 0) { return cardDefinition1.getLongTypeString().compareTo(cardDefinition2.getLongTypeString()); } return c; } }; public static final Comparator TYPE_COMPARATOR_ASC=new Comparator() { @Override public int compare(final MagicCardDefinition cardDefinition1,final MagicCardDefinition cardDefinition2) { return TYPE_COMPARATOR_DESC.compare(cardDefinition2, cardDefinition1); } }; public static final Comparator RARITY_COMPARATOR_DESC=new Comparator() { @Override public int compare(final MagicCardDefinition cardDefinition1,final MagicCardDefinition cardDefinition2) { return cardDefinition1.getRarityString().compareTo(cardDefinition2.getRarityString()); } }; public static final Comparator RARITY_COMPARATOR_ASC=new Comparator() { @Override public int compare(final MagicCardDefinition cardDefinition1,final MagicCardDefinition cardDefinition2) { return RARITY_COMPARATOR_DESC.compare(cardDefinition2, cardDefinition1); } }; public static final Comparator POWER_COMPARATOR_DESC=new Comparator() { @Override public int compare(final MagicCardDefinition cardDefinition1,final MagicCardDefinition cardDefinition2) { final int p1 = cardDefinition1.isCreature() ? cardDefinition1.getCardPower() : -100; final int p2 = cardDefinition2.isCreature() ? cardDefinition2.getCardPower() : -100; if (p1 != p2) { return p1 - p2; } else { return cardDefinition1.getName().compareTo(cardDefinition2.getName()); } } }; public static final Comparator POWER_COMPARATOR_ASC=new Comparator() { @Override public int compare(final MagicCardDefinition cardDefinition1,final MagicCardDefinition cardDefinition2) { return POWER_COMPARATOR_DESC.compare(cardDefinition2, cardDefinition1); } }; public static final Comparator TOUGHNESS_COMPARATOR_DESC=new Comparator() { @Override public int compare(final MagicCardDefinition cardDefinition1,final MagicCardDefinition cardDefinition2) { final int t1 = cardDefinition1.isCreature() ? cardDefinition1.getCardToughness() : -100; final int t2 = cardDefinition2.isCreature() ? cardDefinition2.getCardToughness() : -100; if (t1 != t2) { return t1 - t2; } else { return cardDefinition1.getName().compareTo(cardDefinition2.getName()); } } }; public static final Comparator TOUGHNESS_COMPARATOR_ASC=new Comparator() { @Override public int compare(final MagicCardDefinition cardDefinition1,final MagicCardDefinition cardDefinition2) { return TOUGHNESS_COMPARATOR_DESC.compare(cardDefinition2, cardDefinition1); } }; }