diff --git a/.gitattributes b/.gitattributes index b3bd59e2ae3..ffbac47e602 100644 --- a/.gitattributes +++ b/.gitattributes @@ -122,6 +122,7 @@ res/cardsfolder/ambassador_oak.txt -text svneol=native#text/plain res/cardsfolder/ambitions_cost.txt -text svneol=native#text/plain res/cardsfolder/ambush_party.txt -text svneol=native#text/plain res/cardsfolder/amnesia.txt -text svneol=native#text/plain +res/cardsfolder/amok.txt -text svneol=native#text/plain res/cardsfolder/amrou_kithkin.txt -text svneol=native#text/plain res/cardsfolder/amrou_scout.txt -text svneol=native#text/plain res/cardsfolder/amrou_seekers.txt -text svneol=native#text/plain @@ -1496,6 +1497,7 @@ res/cardsfolder/fruition.txt -text svneol=native#text/plain res/cardsfolder/fugitive_wizard.txt -text svneol=native#text/plain res/cardsfolder/fugue.txt -text svneol=native#text/plain res/cardsfolder/fulminator_mage.txt -text svneol=native#text/plain +res/cardsfolder/fume_spitter.txt -text svneol=native#text/plain res/cardsfolder/funeral_charm.txt -text svneol=native#text/plain res/cardsfolder/fungal_shambler.txt -text svneol=native#text/plain res/cardsfolder/furious_assault.txt -text svneol=native#text/plain @@ -4191,6 +4193,7 @@ res/cardsfolder/trevas_attendant.txt -text svneol=native#text/plain res/cardsfolder/tribal_flames.txt -text svneol=native#text/plain res/cardsfolder/tribal_forcemage.txt -text svneol=native#text/plain res/cardsfolder/trickster_mage.txt -text svneol=native#text/plain +res/cardsfolder/trigon_of_corruption.txt -text svneol=native#text/plain res/cardsfolder/trinket_mage.txt -text svneol=native#text/plain res/cardsfolder/trip_noose.txt -text svneol=native#text/plain res/cardsfolder/trip_wire.txt -text svneol=native#text/plain @@ -5049,6 +5052,7 @@ src/com/cloudgarden/layout/AnchorLayout.java -text svneol=native#text/plain src/com/esotericsoftware/minlog/Log.java svneol=native#text/plain src/forge/Ability.java svneol=native#text/plain src/forge/AbilityFactory.java -text svneol=native#text/plain +src/forge/AbilityFactory_Counters.java -text svneol=native#text/plain src/forge/Ability_Activated.java svneol=native#text/plain src/forge/Ability_Cost.java -text svneol=native#text/plain src/forge/Ability_Hand.java svneol=native#text/plain diff --git a/res/cardsfolder/amok.txt b/res/cardsfolder/amok.txt new file mode 100644 index 00000000000..e088ed4da4f --- /dev/null +++ b/res/cardsfolder/amok.txt @@ -0,0 +1,9 @@ +Name:Amok +ManaCost:1 R +Types:Enchantment +Text:no text +A:AB$PutCounter|Cost$1 Discard<1/Random>|Tgt$TgtC|CounterType$P1P1|CounterNum$1|SpellDescription$Put a +1/+1 counter on target creature. +SVar:Rarity:Rare +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/amok.jpg +End \ No newline at end of file diff --git a/res/cardsfolder/fume_spitter.txt b/res/cardsfolder/fume_spitter.txt new file mode 100644 index 00000000000..9c28634c53f --- /dev/null +++ b/res/cardsfolder/fume_spitter.txt @@ -0,0 +1,10 @@ +Name:Fume Spitter +ManaCost:B +Types:Creature Horror +PT:1/1 +Text:no text +A:AB$PutCounter|Cost$Sac<1/CARDNAME>|Tgt$TgtC|IsCurse$True|CounterType$M1M1|CounterNum$1|SpellDescription$Put a -1/-1 counter on target creature. +SVar:Rarity:Common +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/fume_spitter.jpg +End \ No newline at end of file diff --git a/res/cardsfolder/trigon_of_corruption.txt b/res/cardsfolder/trigon_of_corruption.txt new file mode 100644 index 00000000000..8b783dd7be3 --- /dev/null +++ b/res/cardsfolder/trigon_of_corruption.txt @@ -0,0 +1,12 @@ +Name:Trigon of Corruption +ManaCost:4 +Types:Artifact +Text:no text +K:etbCounter:CHARGE:3 +A:AB$PutCounter|Cost$B B T|CounterType$CHARGE|CounterNum$1|SpellDescription$Put a charge counter on CARDNAME. +A:AB$PutCounter|Cost$2 T SubCounter<1/CHARGE>|Tgt$TgtC|IsCurse$True|CounterType$M1M1|CounterNum$1|SpellDescription$Put a -1/-1 counter on target creature. +SVar:Rarity:Common +# SVar:RemAIDeck:True +# I've run some tests where the AI does use both abilities of this card, but it can be flakey. +SVar:Picture:http://www.wizards.com/global/images/magic/general/trigon_of_corruption.jpg +End \ No newline at end of file diff --git a/src/forge/AbilityFactory.java b/src/forge/AbilityFactory.java index 0bef5114175..ddefbbde1fc 100644 --- a/src/forge/AbilityFactory.java +++ b/src/forge/AbilityFactory.java @@ -1,26 +1,25 @@ package forge; import java.util.HashMap; -import java.util.Random; public class AbilityFactory { - private static Card hostC = null; + private Card hostC = null; public Card getHostCard() { return hostC; } - private static HashMap mapParams = new HashMap(); + private HashMap mapParams = new HashMap(); public HashMap getMapParams() { return mapParams; } - private static boolean isAb = false; - private static boolean isSp = false; + private boolean isAb = false; + private boolean isSp = false; public boolean isAbility() { @@ -32,16 +31,16 @@ public class AbilityFactory { return isSp; } - private static Ability_Cost abCost = null; + private Ability_Cost abCost = null; public Ability_Cost getAbCost() { return abCost; } - private static boolean isTargeted = false; - private static boolean hasValid = false; - private static Target abTgt = null; + private boolean isTargeted = false; + private boolean hasValid = false; + private Target abTgt = null; public boolean isTargeted() { @@ -58,7 +57,12 @@ public class AbilityFactory { return abTgt; } - private static boolean hasSubAb = false; + private boolean isCurse = false; + public boolean isCurse(){ + return isCurse; + } + + private boolean hasSubAb = false; public boolean hasSubAbility() { @@ -75,8 +79,6 @@ public class AbilityFactory { public SpellAbility getAbility(String abString, final Card hostCard){ SpellAbility SA = null; - //final HashMap mapParams = new HashMap(); - hostC = hostCard; if (!(abString.length() > 0)) @@ -97,11 +99,9 @@ public class AbilityFactory { mapParams.put(aa[0], aa[1]); } - - //final boolean isAb[] = {false}; - //final boolean isSp[] = {false}; String abAPI = ""; String spAPI = ""; + // additional ability types here if (mapParams.containsKey("AB")) { @@ -121,17 +121,10 @@ public class AbilityFactory { throw new RuntimeException("AbilityFactory : getAbility -- no Cost in " + hostCard.getName()); abCost = new Ability_Cost(mapParams.get("Cost"), hostCard.getName(), isAb); - - //final boolean isTargeted[] = {false}; - //final boolean hasValid[] = {false}; - //final Target abTgt[] = {null}; if (mapParams.containsKey("ValidTgts")) { hasValid = true; isTargeted = true; - abTgt = new Target("TgtV"); - abTgt.setValidTgts(mapParams.get("ValidTgts").split(",")); - abTgt.setVTSelection(mapParams.get("TgtPrompt")); } if (mapParams.containsKey("ValidCards")) @@ -140,26 +133,30 @@ public class AbilityFactory { if (mapParams.containsKey("Tgt")) { isTargeted = true; - abTgt = new Target(mapParams.get("Tgt")); } + if (isTargeted) + { + if (hasValid) + abTgt = new Target("TgtV", mapParams.get("TgtPrompt"), mapParams.get("ValidTgts").split(",")); + else + abTgt = new Target(mapParams.get("Tgt")); + } + else{ + abTgt = null; + } + + if (mapParams.containsKey("IsCurse")){ + isCurse = true; + } - //final String SubAbility[] = {"none"}; - //final boolean hasSubAb[] = {false}; if (mapParams.containsKey("SubAbility")) hasSubAb = true; - //SubAbility[0] = mapParams; - - //final String spDescription[] = {"none"}; - //final boolean hasSpDesc[] = {false}; - //String tmpSpDesc = mapParams.get("SpellDescription"); + if (mapParams.containsKey("SpellDescription")) - { hasSpDesc = true; - //spDescription[0] = abCost.toString() + mapParams.get("SpellDescription"); - } - + if (abAPI.equals("DealDamage")) { final int NumDmg[] = {-1}; @@ -183,13 +180,23 @@ public class AbilityFactory { } // additional keywords here - + if (abAPI.equals("PutCounter")){ + if (isAb) + SA = AbilityFactory_Counters.createAbilityPutCounters(this); + if (isSp){ + // todo: createSpellPutCounters + } + } // set universal properties of the SpellAbility - if (isTargeted) - SA.setTarget(abTgt); - - SA.setPayCosts(abCost); + if (isSp){ + // Ability_Activated sets abTgt and abCost in the constructor so this only needs to be set for Spells + // Once Spells are more compatible with Tgt and abCost this block should be removed + if (isTargeted) + SA.setTarget(abTgt); + + SA.setPayCosts(abCost); + } if (hasSpDesc) SA.setDescription(abCost.toString() + mapParams.get("SpellDescription")); diff --git a/src/forge/AbilityFactory_Counters.java b/src/forge/AbilityFactory_Counters.java new file mode 100644 index 00000000000..e3fee350692 --- /dev/null +++ b/src/forge/AbilityFactory_Counters.java @@ -0,0 +1,181 @@ +package forge; + +import java.util.HashMap; +import java.util.Random; + +public class AbilityFactory_Counters { + // An AbilityFactory subclass for Putting or Removing Counters on Cards. + + public static SpellAbility createAbilityPutCounters(final AbilityFactory AF){ + + final SpellAbility abPutCounter = new Ability_Activated(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()){ + private static final long serialVersionUID = -1259638699008542484L; + + final AbilityFactory af = AF; + final HashMap params = af.getMapParams(); + final int amount = calculateAmount(af.getHostCard(), params.get("CounterNum")); + final String type = params.get("CounterType"); + + @Override + public String getStackDescription(){ + // when getStackDesc is called, just build exactly what is happening + Counters cType = Counters.valueOf(type); + StringBuilder sb = new StringBuilder(); + String name = af.getHostCard().getName(); + sb.append(name).append(" - Put ").append(amount).append(" ").append(cType.getName()).append(" counter on "); + Card tgt = getTargetCard(); + if (tgt != null) + sb.append(tgt.getName()); + else + sb.append(name); + return sb.toString(); + } + + public boolean canPlay(){ + // super takes care of AdditionalCosts + return (CardFactoryUtil.canUseAbility(af.getHostCard()) && super.canPlay()); + } + + public boolean canPlayAI() + { + return putCanPlayAI(af, this, amount, type); + } + + @Override + public void resolve() { + putResolve(af, this, amount, type); + } + + }; + return abPutCounter; + } + + public static int calculateAmount(Card card, String counterNum){ + int amount; + if (counterNum.matches("X")) + { + String calcX = card.getSVar(counterNum); + if (calcX.startsWith("Count$")) + { + String kk[] = calcX.split("\\$"); + amount = kk[1].equals("none") ? 0 : CardFactoryUtil.xCount(card, kk[1]); + } + else + amount = 0; + } + else + amount = Integer.parseInt(counterNum); + return amount; + } + + public static boolean putCanPlayAI(final AbilityFactory af, final SpellAbility sa, final int amount, final String type){ + // AI needs to be expanded, since this function can be pretty complex based on what the expected targets could be + Random r = new Random(); + Ability_Cost abCost = sa.getPayCosts(); + Target abTgt = sa.getTarget(); + final Card source = sa.getSourceCard(); + CardList list; + Card choice = null; + + String player = af.isCurse() ? Constant.Player.Human : Constant.Player.Computer; + + list = new CardList(AllZone.getZone(Constant.Zone.Play, player).getCards()); + list = list.filter(new CardListFilter() { + public boolean addCard(Card c) { + return AllZone.GameAction.canTarget(c, source); + } + }); + + if (abTgt != null){ + if (abTgt.canTgtCreature()){ + list = list.getType("creature"); + } + else{ + list = list.getValidCards(abTgt.getValidTgts()); + } + if (list.size() == 0) + return false; + } + + if (abCost != null){ + // AI currently disabled for these costs + if (abCost.getSacCost()) return false; + if (abCost.getLifeCost()) return false; + if (abCost.getDiscardCost()) return false; + + if (abCost.getSubCounter()){ + // A card has a 25% chance per counter to be able to pass through here + // 8+ counters will always pass. 0 counters will never + int currentNum = source.getCounters(abCost.getCounterType()); + double percent = .25 * (currentNum / abCost.getCounterNum()); + if (percent <= r.nextFloat()) + return false; + } + } + + if (!ComputerUtil.canPayCost(sa)) + return false; + + // prevent run-away activations - first time will always return true + boolean chance = r.nextFloat() <= Math.pow(.6667, source.getAbilityUsed()); + + // Targeting + if (abTgt != null){ + if (af.isCurse()){ + if (type.equals("M1M1")){ + // try to kill the best killable creature, or reduce the best one + CardList killable = list.filter(new CardListFilter() { + public boolean addCard(Card c) { + return c.getNetDefense() <= amount; + } + }); + if (killable.size() > 0) + choice = CardFactoryUtil.AI_getBestCreature(killable); + else + choice = CardFactoryUtil.AI_getBestCreature(list); + } + else{ + // improve random choice here + list.shuffle(); + choice = list.get(0); + } + } + else{ + if (type.equals("P1P1")){ + choice = CardFactoryUtil.AI_getBestCreature(list); + } + else{ + // The AI really should put counters on cards that can use it. + // Charge counters on things with Charge abilities, etc. Expand these above + list.shuffle(); + choice = list.get(0); + } + } + if (choice == null) + return false; + sa.setTargetCard(choice); + } + else{ + // Placeholder: No targeting necessary + int currCounters = sa.getSourceCard().getCounters(Counters.valueOf(type)); + // each counter on the card is a 10% chance of not activating this ability. + if (r.nextFloat() < .1 * currCounters) + return false; + } + + return ((r.nextFloat() < .6667) && chance); + } + + public static void putResolve(final AbilityFactory af, final SpellAbility sa, int counterAmount, final String type){ + HashMap params = af.getMapParams(); + String DrawBack = params.get("SubAbility"); + Card card = af.getHostCard(); + + Card tgtCard = (sa.getTarget() == null) ? card : sa.getTargetCard(); + tgtCard.addCounter(Counters.valueOf(type), counterAmount); + + if (af.hasSubAbility()) + CardFactoryUtil.doDrawBack(DrawBack, counterAmount, card.getController(), AllZone.GameAction.getOpponent(card.getController()), card.getController(), card, null, sa); + + } +} diff --git a/src/forge/CardFactory.java b/src/forge/CardFactory.java index 080293fddb0..9a331f186f2 100644 --- a/src/forge/CardFactory.java +++ b/src/forge/CardFactory.java @@ -6093,14 +6093,14 @@ public class CardFactory implements NewConstants { // AbilityFactory cards ArrayList IA = card.getIntrinsicAbilities(); if (IA.size() > 0) - { - AbilityFactory AF = new AbilityFactory(); - + { if (card.isInstant() || card.isSorcery()) card.clearSpellAbility(); - for (int i=0; i