diff --git a/.gitattributes b/.gitattributes index 6d53625519c..cf342a07787 100644 --- a/.gitattributes +++ b/.gitattributes @@ -553,6 +553,7 @@ res/cardsfolder/a/aspect_of_wolf.txt svneol=native#text/plain res/cardsfolder/a/assassinate.txt svneol=native#text/plain res/cardsfolder/a/assassins_blade.txt svneol=native#text/plain res/cardsfolder/a/assassins_strike.txt -text +res/cardsfolder/a/assault__battery.txt -text res/cardsfolder/a/assault_griffin.txt svneol=native#text/plain res/cardsfolder/a/assault_strobe.txt svneol=native#text/plain res/cardsfolder/a/assault_zeppelid.txt svneol=native#text/plain @@ -3604,6 +3605,7 @@ res/cardsfolder/f/fire_dragon.txt svneol=native#text/plain res/cardsfolder/f/fire_drake.txt svneol=native#text/plain res/cardsfolder/f/fire_elemental.txt svneol=native#text/plain res/cardsfolder/f/fire_field_ogre.txt svneol=native#text/plain +res/cardsfolder/f/fire_ice.txt -text res/cardsfolder/f/fire_imp.txt svneol=native#text/plain res/cardsfolder/f/fire_juggler.txt -text res/cardsfolder/f/fire_lit_thicket.txt svneol=native#text/plain diff --git a/res/cardsfolder/a/assault__battery.txt b/res/cardsfolder/a/assault__battery.txt new file mode 100644 index 00000000000..fae1d1f4b26 --- /dev/null +++ b/res/cardsfolder/a/assault__battery.txt @@ -0,0 +1,18 @@ +Name:Assault +ManaCost:R +AlternateMode: Split +Types:Sorcery +A:SP$ DealDamage | Cost$ R | NumDmg$ 2 | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player | SpellDescription$ Assault deals 2 damage to target creature or player. +SetInfo:INV Uncommon +SetInfo:TSB Uncommon +SetInfo:HOP Uncommon +Oracle:Assault deals 2 damage to target creature or player. + +ALTERNATE + +Name:Battery +ManaCost:3 G +Types:Sorcery +A:SP$ Token | Cost$ 3 G | TokenAmount$ 1 | TokenName$ Elephant| TokenTypes$ Creature,Elephant| TokenOwner$ You | TokenColors$ Green | TokenPower$ 3 | TokenToughness$ 3 | TokenImage$ g 3 3 elephant | SpellDescription$ Put a 3/3 green Elephant creature token onto the battlefield. +Oracle:Put a 3/3 green Elephant creature token onto the battlefield. +End diff --git a/res/cardsfolder/f/fire_ice.txt b/res/cardsfolder/f/fire_ice.txt new file mode 100644 index 00000000000..f3ef0303faf --- /dev/null +++ b/res/cardsfolder/f/fire_ice.txt @@ -0,0 +1,19 @@ +Name:Fire +ManaCost:1 R +AlternateMode: Split +Types:Instant +A:SP$ DealDamage | Cost$ 1 R | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player to distribute damage to | NumDmg$ 2 | TargetMin$ 1 | TargetMax$ 2 | DividedAsYouChoose$ 2 | SpellDescription$ Fire deals 2 damage divided as you choose among one or two target creatures and/or players. +SetInfo:APC Uncommon +SetInfo:COM Uncommon +SetInfo:DDJ Uncommon +Oracle:Fire deals 2 damage divided as you choose among one or two target creatures and/or players. + +ALTERNATE + +Name:Ice +ManaCost:1 U +Types:Instant +A:SP$ Tap | Cost$ 1 U | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | SubAbility$ DBDraw | SpellDescription$ Tap target permanent. Draw a card. +SVar:DBDraw:DB$ Draw | NumCards$ 1 +Oracle:Tap target permanent.\nDraw a card. +End diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index f7f3f997335..acb185935a5 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -37,6 +37,7 @@ import forge.CardPredicates.Presets; import forge.card.CardCharacteristics; import forge.card.CardRarity; import forge.card.CardRules; +import forge.card.CardSplitType; import forge.card.ability.AbilityUtils; import forge.card.ability.ApiType; import forge.card.cardfactory.CardFactoryUtil; @@ -233,7 +234,13 @@ public class Card extends GameEntity implements Comparable { // Soulbond pairing card private Card pairedWith = null; - + // Enumeration for CMC request types + public enum SplitCMCMode { + CurrentSideCMC, + CombinedCMC, + LeftSplitCMC, + RightSplitCMC + } /** * Instantiates a new card. @@ -7051,15 +7058,29 @@ public class Card extends GameEntity implements Comparable { } else if (property.startsWith("greatestCMC")) { final List list = CardLists.filter(Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield), Presets.CREATURES); for (final Card crd : list) { - if (crd.getCMC() > this.getCMC()) { - return false; + if (crd.getRules() != null && crd.getRules().getSplitType() == CardSplitType.Split) { + if (crd.getCMC(Card.SplitCMCMode.LeftSplitCMC) > this.getCMC() || crd.getCMC(Card.SplitCMCMode.RightSplitCMC) > this.getCMC()) { + return false; + } + } else { + if (crd.getCMC() > this.getCMC()) { + return false; + } } } } else if (property.startsWith("lowestCMC")) { final List list = Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield); for (final Card crd : list) { - if (!crd.isLand() && !crd.isImmutable() && (crd.getCMC() < this.getCMC())) { - return false; + if (!crd.isLand() && !crd.isImmutable()) { + if (crd.getRules() != null && crd.getRules().getSplitType() == CardSplitType.Split) { + if (crd.getCMC(Card.SplitCMCMode.LeftSplitCMC) < this.getCMC() || crd.getCMC(Card.SplitCMCMode.RightSplitCMC) < this.getCMC()) { + return false; + } + } else { + if (crd.getCMC() < this.getCMC()) { + return false; + } + } } } } else if (property.startsWith("enchanted")) { @@ -7110,6 +7131,7 @@ public class Card extends GameEntity implements Comparable { || property.startsWith("cmc") || property.startsWith("totalPT")) { int x = 0; int y = 0; + int y2 = -1; // alternative value for the second split face of a split card String rhs = ""; if (property.startsWith("power")) { @@ -7120,7 +7142,12 @@ public class Card extends GameEntity implements Comparable { y = this.getNetDefense(); } else if (property.startsWith("cmc")) { rhs = property.substring(5); - y = getCMC(); + if (getRules() != null && getRules().getSplitType() == CardSplitType.Split && getCurState() == CardCharacteristicName.Original) { + y = getState(CardCharacteristicName.LeftSplit).getManaCost().getCMC(); + y2 = getState(CardCharacteristicName.RightSplit).getManaCost().getCMC(); + } else { + y = getCMC(); + } } else if (property.startsWith("totalPT")) { rhs = property.substring(10); y = this.getNetAttack() + this.getNetDefense(); @@ -7131,8 +7158,14 @@ public class Card extends GameEntity implements Comparable { x = CardFactoryUtil.xCount(source, source.getSVar(rhs)); } - if (!Expressions.compare(y, property, x)) { - return false; + if (y2 == -1) { + if (!Expressions.compare(y, property, x)) { + return false; + } + } else { + if (!Expressions.compare(y, property, x) || !Expressions.compare(y2, property, x)) { + return false; + } } } @@ -9110,6 +9143,10 @@ public class Card extends GameEntity implements Comparable { * @return a int. */ public int getCMC() { + return getCMC(SplitCMCMode.CurrentSideCMC); + } + + public int getCMC(SplitCMCMode mode) { if (isToken() && !isCopiedToken()) { return 0; } @@ -9120,7 +9157,35 @@ public class Card extends GameEntity implements Comparable { if (Singletons.getModel().getGame().getCardsIn(ZoneType.Stack).contains(this) && getManaCost() != null) { xPaid = getXManaCostPaid() * getManaCost().countX(); } - return getManaCost().getCMC() + xPaid; + + int requestedCMC = 0; + + if (getRules().getSplitType() == CardSplitType.Split) { + switch(mode) { + case CurrentSideCMC: + // TODO: test if this returns combined CMC for the full face (then get rid of CombinedCMC mode?) + requestedCMC = getManaCost().getCMC() + xPaid; + break; + case LeftSplitCMC: + requestedCMC = getState(CardCharacteristicName.LeftSplit).getManaCost().getCMC() + xPaid; + break; + case RightSplitCMC: + requestedCMC = getState(CardCharacteristicName.RightSplit).getManaCost().getCMC() + xPaid; + break; + case CombinedCMC: + requestedCMC += getState(CardCharacteristicName.LeftSplit).getManaCost().getCMC(); + requestedCMC += getState(CardCharacteristicName.RightSplit).getManaCost().getCMC(); + requestedCMC += xPaid; + break; + default: + System.out.println(String.format("Illegal Split Card CMC mode %s passed to getCMC!", mode.toString())); + break; + } + } else { + requestedCMC = getManaCost().getCMC() + xPaid; + } + + return requestedCMC; } public final boolean canBeSacrificedBy(final SpellAbility source) diff --git a/src/main/java/forge/card/cardfactory/CardFactory.java b/src/main/java/forge/card/cardfactory/CardFactory.java index 07749d13efb..16caee3b83f 100644 --- a/src/main/java/forge/card/cardfactory/CardFactory.java +++ b/src/main/java/forge/card/cardfactory/CardFactory.java @@ -31,7 +31,9 @@ import forge.Color; import forge.card.CardRules; import forge.card.CardSplitType; import forge.card.ICardFace; +import forge.card.ability.AbilityFactory; import forge.card.cost.Cost; +import forge.card.mana.ManaCost; import forge.card.replacement.ReplacementHandler; import forge.card.spellability.AbilityActivated; import forge.card.spellability.AbilitySub; @@ -313,6 +315,11 @@ public class CardFactory { card.addStaticAbility(stAbs.get(i)); } } + + if ( state == CardCharacteristicName.LeftSplit || state == CardCharacteristicName.RightSplit ) + { + card.getState(CardCharacteristicName.Original).getSpellAbility().addAll(card.getCharacteristics().getSpellAbility()); + } } card.setState(CardCharacteristicName.Original); @@ -367,9 +374,30 @@ public class CardFactory { if (card.isInAlternateState()) { card.setState(CardCharacteristicName.Original); } + if ( st == CardSplitType.Split ) { card.setName(rules.getName()); - // BUILD COMBINED 'Original' SIDE HERE + + // Combined mana cost + ManaCost combinedManaCost = ManaCost.combine(rules.getMainPart().getManaCost(), rules.getOtherPart().getManaCost()); + card.setManaCost(combinedManaCost); + + // Combined card color + CardColor combinedCardColor = new CardColor(card); + combinedCardColor.addToCardColor(Color.fromColorSet(rules.getMainPart().getColor())); + combinedCardColor.addToCardColor(Color.fromColorSet(rules.getOtherPart().getColor())); + ArrayList combinedCardColorArr = new ArrayList(); + combinedCardColorArr.add(combinedCardColor); + card.setColor(combinedCardColorArr); + + // Super and 'middle' types should use enums. + List coreTypes = rules.getType().getTypesBeforeDash(); + coreTypes.addAll(rules.getType().getSubTypes()); + card.setType(coreTypes); + + // Combined text based on Oracle text - might not be necessary, temporarily disabled. + //String combinedText = String.format("%s: %s\n%s: %s", rules.getMainPart().getName(), rules.getMainPart().getOracleText(), rules.getOtherPart().getName(), rules.getOtherPart().getOracleText()); + //card.setText(combinedText); } return card; diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index 2a572f830b5..a30d9dcd13d 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -2796,16 +2796,15 @@ public class CardFactoryUtil { // ************************************************** // AbilityFactory cards final ArrayList ia = card.getIntrinsicAbilities(); - if (ia.size() > 0) { - for (int i = 0; i < ia.size(); i++) { - // System.out.println(cardName); - final SpellAbility sa = AbilityFactory.getAbility(ia.get(i), card); - if (sa.hasParam("SetAsKicked")) { - sa.addOptionalAdditionalCosts("Kicker"); - } - card.addSpellAbility(sa); + for (int i = 0; i < ia.size(); i++) { + // System.out.println(cardName); + final SpellAbility sa = AbilityFactory.getAbility(ia.get(i), card); + if (sa.hasParam("SetAsKicked")) { + sa.addOptionalAdditionalCosts("Kicker"); } + card.addSpellAbility(sa); } + } /** diff --git a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java index 7dd24060d2f..118116ab4cc 100644 --- a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java +++ b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java @@ -20,7 +20,9 @@ package forge.card.spellability; import java.util.ArrayList; import forge.Card; +import forge.CardCharacteristicName; import forge.Singletons; +import forge.card.CardSplitType; import forge.card.ability.AbilityUtils; import forge.card.cost.CostPayment; import forge.game.zone.Zone; @@ -149,6 +151,14 @@ public class SpellAbilityRequirements { if (this.select.isCanceled()) { // cancel ability during target choosing final Card c = this.ability.getSourceCard(); + + // split cards transform back to full form if targeting is canceled + if (c.getRules() != null) { + if (c.getRules().getSplitType() == CardSplitType.Split) { + c.setState(CardCharacteristicName.Original); + } + } + if (this.bCasting && !c.isCopiedSpell()) { // and not a copy // add back to where it came from Singletons.getModel().getGame().getAction().moveTo(this.fromZone, c, this.zonePosition); @@ -203,6 +213,14 @@ public class SpellAbilityRequirements { Singletons.getModel().getGame().getAction().checkStateEffects(); } else if (this.payment.isCanceled()) { final Card c = this.ability.getSourceCard(); + + // split cards transform back to full form if mana cost is not paid + if (c.getRules() != null) { + if (c.getRules().getSplitType() == CardSplitType.Split) { + c.setState(CardCharacteristicName.Original); + } + } + if (this.bCasting && !c.isCopiedSpell()) { // and not a copy // add back to Previous Zone Singletons.getModel().getGame().getAction().moveTo(this.fromZone, c, this.zonePosition); diff --git a/src/main/java/forge/game/GameAction.java b/src/main/java/forge/game/GameAction.java index d91fe8c86ca..ee587c43a60 100644 --- a/src/main/java/forge/game/GameAction.java +++ b/src/main/java/forge/game/GameAction.java @@ -33,6 +33,7 @@ import forge.CardUtil; import forge.Command; import forge.CounterType; import forge.GameEntity; +import forge.card.CardSplitType; import forge.card.CardType; import forge.card.ability.effects.AttachEffect; import forge.card.cost.Cost; @@ -339,6 +340,13 @@ public class GameAction { * @return a {@link forge.Card} object. */ public final Card moveTo(final Zone zoneTo, Card c) { + // if a split card is moved, convert it back to its full form before moving (unless moving to stack) + if (c.getRules() != null) { + if ((c.getRules().getSplitType() == CardSplitType.Split) && (zoneTo != game.getStackZone())) { + c.setState(CardCharacteristicName.Original); + } + } + return moveTo(zoneTo, c, null); } @@ -704,6 +712,7 @@ public class GameAction { return c; } final PlayerZone removed = c.getOwner().getZone(ZoneType.Exile); + return moveTo(removed, c); } diff --git a/src/main/java/forge/game/GameActionPlay.java b/src/main/java/forge/game/GameActionPlay.java index ade428ec569..49e78212e9d 100644 --- a/src/main/java/forge/game/GameActionPlay.java +++ b/src/main/java/forge/game/GameActionPlay.java @@ -6,9 +6,11 @@ import java.util.List; import com.google.common.collect.Lists; import forge.Card; +import forge.CardCharacteristicName; import forge.CardColor; import forge.CardLists; import forge.CardPredicates; +import forge.card.CardSplitType; import forge.card.MagicColor; import forge.card.ability.AbilityUtils; import forge.card.ability.ApiType; @@ -363,6 +365,11 @@ public class GameActionPlay { public final void playSpellAbility(SpellAbility sa, Player activator) { sa.setActivatingPlayer(activator); + final Card source = sa.getSourceCard(); + + // Split card support + setSplitCardState(source, sa); + if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { CharmEffect.makeChoices(sa); } @@ -402,7 +409,6 @@ public class GameActionPlay { manaCost = this.getSpellCostChange(sa, new ManaCostBeingPaid(sa.getManaCost())); } if (manaCost.isPaid() && (sa.getBeforePayMana() == null)) { - final Card source = sa.getSourceCard(); if (sa.isSpell() && !source.isCopiedSpell()) { sa.setSourceCard(game.getAction().moveToStack(source)); } @@ -532,4 +538,26 @@ public class GameActionPlay { return usableColors; } + + private void setSplitCardState(final Card source, SpellAbility sa) { + // Split card support + if (source.getRules() != null) { + if (source.getRules().getSplitType() == CardSplitType.Split) { + List leftSplitAbilities = source.getState(CardCharacteristicName.LeftSplit).getSpellAbility(); + List rightSplitAbilities = source.getState(CardCharacteristicName.RightSplit).getSpellAbility(); + for (SpellAbility a : leftSplitAbilities) { + if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { + source.setState(CardCharacteristicName.LeftSplit); + break; + } + } + for (SpellAbility a : rightSplitAbilities) { + if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { + source.setState(CardCharacteristicName.RightSplit); + break; + } + } + } + } + } }