From a75835f6647096c70386a22c7d9228332c8912d6 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Fri, 21 Jun 2013 21:42:26 +0000 Subject: [PATCH] CantBeBlockedByAmount + expression used to specify number or creatures that can block given attacker --- res/cardsfolder/a/alpha_authority.txt | 2 +- res/cardsfolder/c/caterwauling_boggart.txt | 4 +- res/cardsfolder/c/charging_rhino.txt | 2 +- res/cardsfolder/d/demoralize.txt | 2 +- res/cardsfolder/f/familiar_ground.txt | 2 +- res/cardsfolder/g/goblin_war_drums.txt | 2 +- res/cardsfolder/g/gorilla_war_cry.txt | 2 +- res/cardsfolder/g/gruul_nodorog.txt | 2 +- res/cardsfolder/g/gruul_war_chant.txt | 2 +- res/cardsfolder/h/horde_of_boggarts.txt | 2 +- res/cardsfolder/h/huang_zhong_shu_general.txt | 2 +- res/cardsfolder/i/imposing_visage.txt | 2 +- res/cardsfolder/i/ironhoof_ox.txt | 2 +- res/cardsfolder/k/kederekt_creeper.txt | 2 +- res/cardsfolder/k/krosan_vorine.txt | 2 +- .../k/kruin_outlaw_terror_of_kruin_pass.txt | 2 +- res/cardsfolder/m/madcap_skills.txt | 2 +- res/cardsfolder/n/norwood_riders.txt | 2 +- res/cardsfolder/p/pyreheart_wolf.txt | 2 +- res/cardsfolder/r/ripscale_predator.txt | 2 +- res/cardsfolder/s/searing_spear_askari.txt | 2 +- res/cardsfolder/s/stalking_tiger.txt | 2 +- res/cardsfolder/s/stormblood_berserker.txt | 2 +- res/cardsfolder/s/summit_apes.txt | 2 +- res/cardsfolder/t/two_headed_dragon.txt | 2 +- .../t/two_headed_giant_of_foriys_avatar.txt | 2 +- res/cardsfolder/t/two_headed_sliver.txt | 2 +- res/cardsfolder/v/viashino_runner.txt | 2 +- res/cardsfolder/v/vine_kami.txt | 2 +- res/cardsfolder/v/vorrac_battlehorns.txt | 2 +- res/cardsfolder/w/wind_spirit.txt | 2 +- .../y/yuan_shao_the_indecisive.txt | 2 +- res/lists/NonStackingKWList.txt | 3 +- src/main/java/forge/Card.java | 215 +++++++++--------- .../java/forge/card/ability/ai/AttachAi.java | 5 +- .../forge/card/ability/ai/PumpAiBase.java | 3 +- .../forge/game/ai/AiAttackController.java | 4 +- .../java/forge/game/ai/ComputerUtilBlock.java | 12 +- .../java/forge/game/ai/ComputerUtilCard.java | 7 +- .../java/forge/game/phase/CombatUtil.java | 108 ++++----- src/main/java/forge/util/Lang.java | 24 +- 41 files changed, 230 insertions(+), 217 deletions(-) diff --git a/res/cardsfolder/a/alpha_authority.txt b/res/cardsfolder/a/alpha_authority.txt index bdd4cab547b..6bbfbb7d48a 100644 --- a/res/cardsfolder/a/alpha_authority.txt +++ b/res/cardsfolder/a/alpha_authority.txt @@ -3,7 +3,7 @@ ManaCost:1 G Types:Enchantment Aura K:Enchant creature A:SP$ Attach | Cost$ 1 G | ValidTgts$ Creature | AILogic$ Pump -S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Hexproof | AddHiddenKeyword$ CARDNAME can't be blocked by more than one creature. | Description$ Enchanted creature has hexproof and can't be blocked by more than one creature. +S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Hexproof & CantBeBlockedByAmount GT1 | Description$ Enchanted creature has hexproof and can't be blocked by more than one creature. SVar:Picture:http://www.wizards.com/global/images/magic/general/alpha_authority.jpg Oracle:Enchant creature\nEnchanted creature has hexproof and can't be blocked by more than one creature. SetInfo:GTC Uncommon \ No newline at end of file diff --git a/res/cardsfolder/c/caterwauling_boggart.txt b/res/cardsfolder/c/caterwauling_boggart.txt index 3d247eb0232..411317a49c1 100644 --- a/res/cardsfolder/c/caterwauling_boggart.txt +++ b/res/cardsfolder/c/caterwauling_boggart.txt @@ -2,8 +2,8 @@ Name:Caterwauling Boggart ManaCost:3 R Types:Creature Goblin Shaman PT:2/2 -S:Mode$ Continuous | Affected$ Goblin.YouCtrl | AddHiddenKeyword$ CARDNAME can't be blocked except by two or more creatures. | Description$ Each Goblin you control can't be blocked except by two or more creatures. -S:Mode$ Continuous | Affected$ Elemental.YouCtrl | AddHiddenKeyword$ CARDNAME can't be blocked except by two or more creatures. | Description$ Each Elemental you control can't be blocked except by two or more creatures. +S:Mode$ Continuous | Affected$ Goblin.YouCtrl | AddHiddenKeyword$ CantBeBlockedByAmount LT2 | Description$ Each Goblin you control can't be blocked except by two or more creatures. +S:Mode$ Continuous | Affected$ Elemental.YouCtrl | AddHiddenKeyword$ CantBeBlockedByAmount LT2 | Description$ Each Elemental you control can't be blocked except by two or more creatures. SVar:PlayMain1:TRUE SVar:Picture:http://www.wizards.com/global/images/magic/general/caterwauling_boggart.jpg Oracle:Each Goblin you control can't be blocked except by two or more creatures.\nEach Elemental you control can't be blocked except by two or more creatures. diff --git a/res/cardsfolder/c/charging_rhino.txt b/res/cardsfolder/c/charging_rhino.txt index 42ed77a6d61..aee23003b9d 100644 --- a/res/cardsfolder/c/charging_rhino.txt +++ b/res/cardsfolder/c/charging_rhino.txt @@ -2,7 +2,7 @@ Name:Charging Rhino ManaCost:3 G G Types:Creature Rhino PT:4/4 -K:CARDNAME can't be blocked by more than one creature. +K:CantBeBlockedByAmount GT1 SVar:Picture:http://www.wizards.com/global/images/magic/general/charging_rhino.jpg Oracle:Charging Rhino can't be blocked by more than one creature. SetInfo:TMP Uncommon diff --git a/res/cardsfolder/d/demoralize.txt b/res/cardsfolder/d/demoralize.txt index d3ce8e0e502..a691fdcc588 100644 --- a/res/cardsfolder/d/demoralize.txt +++ b/res/cardsfolder/d/demoralize.txt @@ -3,7 +3,7 @@ ManaCost:2 R Types:Instant A:SP$ Effect | Cost$ 2 R | Name$ Demoralize Effect | StaticAbilities$ KWPump | SubAbility$ DBEffect2 | SpellDescription$ Each creature can't be blocked this turn except by two or more creatures. SVar:DBEffect2:DB$ Effect | Name$ Demoralize Effect 2 | StaticAbilities$ KWPump2 | Condition$ Threshold | SpellDescription$ Threshold - If seven or more cards are in your graveyard, creatures can't block this turn. -SVar:KWPump:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature | AddKeyword$ CARDNAME can't be blocked except by two or more creatures. | Description$ Each creature can't be blocked this turn except by two or more creatures. +SVar:KWPump:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature | AddKeyword$ CantBeBlockedByAmount LT2 | Description$ Each creature can't be blocked this turn except by two or more creatures. SVar:KWPump2:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature | AddKeyword$ CARDNAME can't block. | Description$ Threshold - If seven or more cards are in your graveyard, creatures can't block this turn. SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/demoralize.jpg diff --git a/res/cardsfolder/f/familiar_ground.txt b/res/cardsfolder/f/familiar_ground.txt index 2a38703f86f..3430e178be5 100644 --- a/res/cardsfolder/f/familiar_ground.txt +++ b/res/cardsfolder/f/familiar_ground.txt @@ -1,7 +1,7 @@ Name:Familiar Ground ManaCost:2 G Types:Enchantment -S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CARDNAME can't be blocked by more than one creature. | Description$ Each creature you control can't be blocked by more than one creature. +S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control can't be blocked by more than one creature. SVar:NonStackingEffect:True SVar:PlayMain1:TRUE SVar:Picture:http://www.wizards.com/global/images/magic/general/familiar_ground.jpg diff --git a/res/cardsfolder/g/goblin_war_drums.txt b/res/cardsfolder/g/goblin_war_drums.txt index 031f51d8a15..ee431775716 100644 --- a/res/cardsfolder/g/goblin_war_drums.txt +++ b/res/cardsfolder/g/goblin_war_drums.txt @@ -1,7 +1,7 @@ Name:Goblin War Drums ManaCost:2 R Types:Enchantment -S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CARDNAME can't be blocked except by two or more creatures. | Description$ Each creature you control can't be blocked except by two or more creatures. +S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CantBeBlockedByAmount LT2 | Description$ Each creature you control can't be blocked except by two or more creatures. SVar:NonStackingEffect:True SVar:PlayMain1:TRUE SVar:Picture:http://www.wizards.com/global/images/magic/general/goblin_war_drums.jpg diff --git a/res/cardsfolder/g/gorilla_war_cry.txt b/res/cardsfolder/g/gorilla_war_cry.txt index 5672960126f..f0f362404d4 100644 --- a/res/cardsfolder/g/gorilla_war_cry.txt +++ b/res/cardsfolder/g/gorilla_war_cry.txt @@ -4,7 +4,7 @@ Types:Instant A:SP$ Effect | Cost$ 1 R | Name$ Gorilla War Cry Effect | StaticAbilities$ Blocking | ActivationPhases$ BeginCombat->Declare Attackers | AILogic$ Evasion | SubAbility$ DelTrigSlowtrip | SpellDescription$ Cast CARDNAME only during combat before blockers are declared. Creatures can't be blocked this turn except by two or more creatures. Draw a card at the beginning of next turn's upkeep. SVar:DelTrigSlowtrip:DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | Execute$ DrawSlowtrip | TriggerDescription$ Draw a card. SVar:DrawSlowtrip:DB$Draw | NumCards$ 1 | Defined$ You -SVar:Blocking:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature | AddHiddenKeyword$ CARDNAME can't be blocked except by two or more creatures. | Description$ Creatures can't be blocked this turn except by two or more creatures. +SVar:Blocking:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature | AddKeyword$ CantBeBlockedByAmount LT2 | Description$ Creatures can't be blocked this turn except by two or more creatures. SVar:Picture:http://www.wizards.com/global/images/magic/general/gorilla_war_cry.jpg Oracle:Cast Gorilla War Cry only during combat before blockers are declared.\nCreatures can't be blocked this turn except by two or more creatures.\nDraw a card at the beginning of the next turn's upkeep. SetInfo:ALL Common x2 \ No newline at end of file diff --git a/res/cardsfolder/g/gruul_nodorog.txt b/res/cardsfolder/g/gruul_nodorog.txt index 03feed41bcc..5b2967973dd 100644 --- a/res/cardsfolder/g/gruul_nodorog.txt +++ b/res/cardsfolder/g/gruul_nodorog.txt @@ -2,7 +2,7 @@ Name:Gruul Nodorog ManaCost:4 G G Types:Creature Beast PT:4/4 -A:AB$ Pump | Cost$ R | KW$ HIDDEN CARDNAME can't be blocked except by two or more creatures. | SpellDescription$ CARDNAME can't be blocked this turn except by two or more creatures. +A:AB$ Pump | Cost$ R | KW$ CantBeBlockedByAmount LT2 | SpellDescription$ CARDNAME can't be blocked this turn except by two or more creatures. SVar:Picture:http://www.wizards.com/global/images/magic/general/gruul_nodorog.jpg Oracle:{R}: Gruul Nodorog can't be blocked this turn except by two or more creatures. SetInfo:GPT Common \ No newline at end of file diff --git a/res/cardsfolder/g/gruul_war_chant.txt b/res/cardsfolder/g/gruul_war_chant.txt index 3c915618605..1c350611ffd 100644 --- a/res/cardsfolder/g/gruul_war_chant.txt +++ b/res/cardsfolder/g/gruul_war_chant.txt @@ -1,7 +1,7 @@ Name:Gruul War Chant ManaCost:2 R G Types:Enchantment -S:Mode$ Continuous | Affected$ Creature.YouCtrl+attacking | AddPower$ 1 | AddHiddenKeyword$ CARDNAME can't be blocked except by two or more creatures. | Description$ Each attacking creature you control gets +1/+0 and can't be blocked except by two or more creatures. +S:Mode$ Continuous | Affected$ Creature.YouCtrl+attacking | AddPower$ 1 | AddKeyword$ CantBeBlockedByAmount LT2 | Description$ Each attacking creature you control gets +1/+0 and can't be blocked except by two or more creatures. SVar:PlayMain1:TRUE SVar:Picture:http://www.wizards.com/global/images/magic/general/gruul_war_chant.jpg Oracle:Each attacking creature you control gets +1/+0 and can't be blocked except by two or more creatures. diff --git a/res/cardsfolder/h/horde_of_boggarts.txt b/res/cardsfolder/h/horde_of_boggarts.txt index 2d4a6b8f6bf..f79ee8dcb4d 100644 --- a/res/cardsfolder/h/horde_of_boggarts.txt +++ b/res/cardsfolder/h/horde_of_boggarts.txt @@ -2,7 +2,7 @@ Name:Horde of Boggarts ManaCost:3 R Types:Creature Goblin PT:*/* -K:CARDNAME can't be blocked except by two or more creatures. +K:CantBeBlockedByAmount LT2 S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of red permanents you control. SVar:X:Count$Valid Permanent.Red+YouCtrl SVar:BuffedBy:Permanent.Red diff --git a/res/cardsfolder/h/huang_zhong_shu_general.txt b/res/cardsfolder/h/huang_zhong_shu_general.txt index a3ea24b5bdb..98cde98e43a 100644 --- a/res/cardsfolder/h/huang_zhong_shu_general.txt +++ b/res/cardsfolder/h/huang_zhong_shu_general.txt @@ -2,7 +2,7 @@ Name:Huang Zhong, Shu General ManaCost:2 W W Types:Legendary Creature Human Soldier PT:2/3 -K:CARDNAME can't be blocked by more than one creature. +K:CantBeBlockedByAmount GT1 SVar:Picture:http://www.wizards.com/global/images/magic/general/huang_zhong_shu_general.jpg Oracle:Huang Zhong, Shu General can't be blocked by more than one creature. SetInfo:PTK Rare \ No newline at end of file diff --git a/res/cardsfolder/i/imposing_visage.txt b/res/cardsfolder/i/imposing_visage.txt index 9a1aa88f74e..057297b3184 100644 --- a/res/cardsfolder/i/imposing_visage.txt +++ b/res/cardsfolder/i/imposing_visage.txt @@ -3,7 +3,7 @@ ManaCost:R Types:Enchantment Aura K:Enchant creature A:SP$ Attach | Cost$ R | ValidTgts$ Creature | AILogic$ Pump -S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ CARDNAME can't be blocked except by two or more creatures. | Description$ Enchanted creature can't be blocked except by two or more creatures. +S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ CantBeBlockedByAmount LT2 | Description$ Enchanted creature can't be blocked except by two or more creatures. SVar:Picture:http://www.wizards.com/global/images/magic/general/imposing_visage.jpg Oracle:Enchant creature\nEnchanted creature can't be blocked except by two or more creatures. SetInfo:ICE Common diff --git a/res/cardsfolder/i/ironhoof_ox.txt b/res/cardsfolder/i/ironhoof_ox.txt index d7207591929..41ba1c64b87 100644 --- a/res/cardsfolder/i/ironhoof_ox.txt +++ b/res/cardsfolder/i/ironhoof_ox.txt @@ -2,7 +2,7 @@ Name:Ironhoof Ox ManaCost:3 G G Types:Creature Ox PT:4/4 -K:CARDNAME can't be blocked by more than one creature. +K:CantBeBlockedByAmount GT1 SVar:Picture:http://resources.wizards.com/magic/cards/p2/en-us/card6628.jpg Oracle:Ironhoof Ox can't be blocked by more than one creature. SetInfo:PO2 Uncommon \ No newline at end of file diff --git a/res/cardsfolder/k/kederekt_creeper.txt b/res/cardsfolder/k/kederekt_creeper.txt index f4e0809eeaa..272b1423695 100644 --- a/res/cardsfolder/k/kederekt_creeper.txt +++ b/res/cardsfolder/k/kederekt_creeper.txt @@ -3,7 +3,7 @@ ManaCost:U B R Types:Creature Horror PT:2/3 K:Deathtouch -K:CARDNAME can't be blocked except by two or more creatures. +K:CantBeBlockedByAmount LT2 SVar:Picture:http://www.wizards.com/global/images/magic/general/kederekt_creeper.jpg Oracle:Deathtouch (Any amount of damage this deals to a creature is enough to destroy it.)\nKederekt Creeper can't be blocked except by two or more creatures. SetInfo:ALA Common \ No newline at end of file diff --git a/res/cardsfolder/k/krosan_vorine.txt b/res/cardsfolder/k/krosan_vorine.txt index ce6483d9c73..a67a0a54db2 100644 --- a/res/cardsfolder/k/krosan_vorine.txt +++ b/res/cardsfolder/k/krosan_vorine.txt @@ -3,7 +3,7 @@ ManaCost:3 G Types:Creature Cat Beast PT:3/2 K:Provoke -K:CARDNAME can't be blocked by more than one creature. +K:CantBeBlockedByAmount GT1 SVar:Picture:http://www.wizards.com/global/images/magic/general/krosan_vorine.jpg Oracle:Provoke (When this attacks, you may have target creature defending player controls untap and block it if able.)\nKrosan Vorine can't be blocked by more than one creature. SetInfo:LGN Common \ No newline at end of file diff --git a/res/cardsfolder/k/kruin_outlaw_terror_of_kruin_pass.txt b/res/cardsfolder/k/kruin_outlaw_terror_of_kruin_pass.txt index f3547160d59..f4c602ead16 100644 --- a/res/cardsfolder/k/kruin_outlaw_terror_of_kruin_pass.txt +++ b/res/cardsfolder/k/kruin_outlaw_terror_of_kruin_pass.txt @@ -17,7 +17,7 @@ Colors:red Types:Creature Werewolf PT:3/3 K:Double Strike -S:Mode$ Continuous | Affected$ Werewolf.YouCtrl | AddHiddenKeyword$ CARDNAME can't be blocked except by two or more creatures. | Description$ Each Werewolf you control can't be blocked except by two or more creatures. +S:Mode$ Continuous | Affected$ Werewolf.YouCtrl | AddKeyword$ CantBeBlockedByAmount LT2 | Description$ Each Werewolf you control can't be blocked except by two or more creatures. T:Mode$Phase | Phase$ Upkeep | WerewolfUntransformCondition$ True | TriggerZones$ Battlefield | Execute$ TrigTransform | TriggerDescription$ At the beginning of each upkeep, if a player cast two or more spells last turn, transform CARDNAME. SVar:TrigTransform:AB$SetState | Cost$ 0 | Defined$ Self | Mode$ Transform SVar:Picture:http://www.wizards.com/global/images/magic/general/terror_of_kruin_pass.jpg diff --git a/res/cardsfolder/m/madcap_skills.txt b/res/cardsfolder/m/madcap_skills.txt index c906118e0b6..6472fee4a46 100644 --- a/res/cardsfolder/m/madcap_skills.txt +++ b/res/cardsfolder/m/madcap_skills.txt @@ -3,7 +3,7 @@ ManaCost:1 R Types:Enchantment Aura K:Enchant creature A:SP$ Attach | Cost$ 1 R | ValidTgts$ Creature | AILogic$ Pump -S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 3 | AddHiddenKeyword$ CARDNAME can't be blocked except by two or more creatures. | Description$ Enchanted creature gets +3/+0 and can't be blocked except by two or more creatures. +S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 3 | AddKeyword$ CantBeBlockedByAmount LT2 | Description$ Enchanted creature gets +3/+0 and can't be blocked except by two or more creatures. SVar:Picture:http://www.wizards.com/global/images/magic/general/madcap_skills.jpg Oracle:Enchant creature\nEnchanted creature gets +3/+0 and can't be blocked except by two or more creatures. SetInfo:GTC Common \ No newline at end of file diff --git a/res/cardsfolder/n/norwood_riders.txt b/res/cardsfolder/n/norwood_riders.txt index ba01fe1f7ff..c9a2d3d7fbe 100644 --- a/res/cardsfolder/n/norwood_riders.txt +++ b/res/cardsfolder/n/norwood_riders.txt @@ -2,7 +2,7 @@ Name:Norwood Riders ManaCost:3 G Types:Creature Elf PT:3/3 -K:CARDNAME can't be blocked by more than one creature. +K:CantBeBlockedByAmount GT1 SVar:Picture:http://resources.wizards.com/magic/cards/p2/en-us/card6615.jpg Oracle:Norwood Riders can't be blocked by more than one creature. SetInfo:PO2 Common \ No newline at end of file diff --git a/res/cardsfolder/p/pyreheart_wolf.txt b/res/cardsfolder/p/pyreheart_wolf.txt index f31ebb67387..f8f8e673ad3 100644 --- a/res/cardsfolder/p/pyreheart_wolf.txt +++ b/res/cardsfolder/p/pyreheart_wolf.txt @@ -4,7 +4,7 @@ Types:Creature Wolf PT:1/1 K:Undying T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks, each creature you control can't be blocked this turn except by two or more creatures. -SVar:TrigPump:AB$ PumpAll | Cost$ 0 | ValidCards$ Creature.YouCtrl | KW$ HIDDEN CARDNAME can't be blocked except by two or more creatures. +SVar:TrigPump:AB$ PumpAll | Cost$ 0 | ValidCards$ Creature.YouCtrl | KW$ CantBeBlockedByAmount LT2 SVar:Picture:http://www.wizards.com/global/images/magic/general/pyreheart_wolf.jpg Oracle:Whenever Pyreheart Wolf attacks, each creature you control can't be blocked this turn except by two or more creatures.\nUndying (When this creature dies, if it had no +1/+1 counters on it, return it to the battlefield under its owner's control with a +1/+1 counter on it.) SetInfo:DKA Uncommon \ No newline at end of file diff --git a/res/cardsfolder/r/ripscale_predator.txt b/res/cardsfolder/r/ripscale_predator.txt index 0819fccc773..61d1774d483 100644 --- a/res/cardsfolder/r/ripscale_predator.txt +++ b/res/cardsfolder/r/ripscale_predator.txt @@ -2,7 +2,7 @@ Name:Ripscale Predator ManaCost:4 R R Types:Creature Lizard PT:6/5 -K:CARDNAME can't be blocked except by two or more creatures. +K:CantBeBlockedByAmount LT2 SVar:Picture:http://www.wizards.com/global/images/magic/general/ripscale_predator.jpg Oracle:Ripscale Predator can't be blocked except by two or more creatures. SetInfo:GTC Uncommon \ No newline at end of file diff --git a/res/cardsfolder/s/searing_spear_askari.txt b/res/cardsfolder/s/searing_spear_askari.txt index 521073b271a..f9e37a07de9 100644 --- a/res/cardsfolder/s/searing_spear_askari.txt +++ b/res/cardsfolder/s/searing_spear_askari.txt @@ -3,7 +3,7 @@ ManaCost:2 R Types:Creature Human Knight PT:2/2 K:Flanking -A:AB$ Pump | Cost$ 1 R | KW$ HIDDEN CARDNAME can't be blocked except by two or more creatures. | SpellDescription$ CARDNAME can't be blocked except by two or more creatures this turn. +A:AB$ Pump | Cost$ 1 R | KW$ CantBeBlockedByAmount LT2 | SpellDescription$ CARDNAME can't be blocked except by two or more creatures this turn. SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/searing_spear_askari.jpg Oracle:Flanking (Whenever a creature without flanking blocks this creature, the blocking creature gets -1/-1 until end of turn.)\n{1}{R}: Searing Spear Askari can't be blocked except by two or more creatures this turn. diff --git a/res/cardsfolder/s/stalking_tiger.txt b/res/cardsfolder/s/stalking_tiger.txt index 0f23d90ab98..31c25a7f95b 100644 --- a/res/cardsfolder/s/stalking_tiger.txt +++ b/res/cardsfolder/s/stalking_tiger.txt @@ -2,7 +2,7 @@ Name:Stalking Tiger ManaCost:3 G Types:Creature Cat PT:3/3 -K:CARDNAME can't be blocked by more than one creature. +K:CantBeBlockedByAmount GT1 SVar:Picture:http://www.wizards.com/global/images/magic/general/stalking_tiger.jpg Oracle:Stalking Tiger can't be blocked by more than one creature. SetInfo:PTK Common diff --git a/res/cardsfolder/s/stormblood_berserker.txt b/res/cardsfolder/s/stormblood_berserker.txt index f2db8f42202..04c21367208 100644 --- a/res/cardsfolder/s/stormblood_berserker.txt +++ b/res/cardsfolder/s/stormblood_berserker.txt @@ -3,7 +3,7 @@ ManaCost:1 R Types:Creature Human Berserker PT:1/1 K:Bloodthirst 2 -K:CARDNAME can't be blocked except by two or more creatures. +K:CantBeBlockedByAmount LT2 SVar:Picture:http://www.wizards.com/global/images/magic/general/stormblood_berserker.jpg Oracle:Bloodthirst 2 (If an opponent was dealt damage this turn, this creature enters the battlefield with two +1/+1 counters on it.)\nStormblood Berserker can't be blocked except by two or more creatures. SetInfo:M12 Uncommon \ No newline at end of file diff --git a/res/cardsfolder/s/summit_apes.txt b/res/cardsfolder/s/summit_apes.txt index 9ada4316745..49b39ab183c 100644 --- a/res/cardsfolder/s/summit_apes.txt +++ b/res/cardsfolder/s/summit_apes.txt @@ -2,7 +2,7 @@ Name:Summit Apes ManaCost:3 G Types:Creature Ape PT:5/2 -S:Mode$ Continuous | Affected$ Card.Self | AddHiddenKeyword$ CARDNAME can't be blocked except by two or more creatures. | CheckSVar$ X | SVarCompare$ GE1 | Description$ As long as you control a Mountain, CARDNAME can't be blocked except by two or more creatures. +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ CantBeBlockedByAmount LT2 | CheckSVar$ X | SVarCompare$ GE1 | Description$ As long as you control a Mountain, CARDNAME can't be blocked except by two or more creatures. SVar:X:Count$Valid Mountain.YouCtrl SVar:BuffedBy:Mountain SVar:Picture:http://www.wizards.com/global/images/magic/general/summit_apes.jpg diff --git a/res/cardsfolder/t/two_headed_dragon.txt b/res/cardsfolder/t/two_headed_dragon.txt index 72217d51815..0a9b69f7f93 100644 --- a/res/cardsfolder/t/two_headed_dragon.txt +++ b/res/cardsfolder/t/two_headed_dragon.txt @@ -4,7 +4,7 @@ Types:Creature Dragon PT:4/4 K:Flying A:AB$ Pump | Cost$ 1 R | NumAtt$ +2 | NumDef$ +0 | SpellDescription$ CARDNAME gets +2/+0 until end of turn. -K:CARDNAME can't be blocked except by two or more creatures. +K:CantBeBlockedByAmount LT2 K:CARDNAME can block an additional creature. SVar:Picture:http://www.wizards.com/global/images/magic/general/two_headed_dragon.jpg Oracle:Flying\n{1}{R}: Two-Headed Dragon gets +2/+0 until end of turn.\nTwo-Headed Dragon can't be blocked except by two or more creatures.\nTwo-Headed Dragon can block an additional creature. diff --git a/res/cardsfolder/t/two_headed_giant_of_foriys_avatar.txt b/res/cardsfolder/t/two_headed_giant_of_foriys_avatar.txt index aa515d1e61f..2059609b621 100644 --- a/res/cardsfolder/t/two_headed_giant_of_foriys_avatar.txt +++ b/res/cardsfolder/t/two_headed_giant_of_foriys_avatar.txt @@ -3,7 +3,7 @@ ManaCost:no cost Types:Vanguard HandLifeModifier:+1/-4 S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CARDNAME can block an additional creature. | Description$ Each creature you control can block an additional creature each combat. -S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CARDNAME can't be blocked except by two or more creatures. | Description$ Each creature you control can't be blocked except by two or more creatures. +S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.YouCtrl | AddKeyword$ CantBeBlockedByAmount LT2 | Description$ Each creature you control can't be blocked except by two or more creatures. SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/two_headed_giant_of_foriys_avatar.jpg Oracle:Each creature you control can block an additional creature each combat.\nEach creature you control can't be blocked except by two or more creatures. SetInfo:VAN Special \ No newline at end of file diff --git a/res/cardsfolder/t/two_headed_sliver.txt b/res/cardsfolder/t/two_headed_sliver.txt index 0f8183d3fc1..42385528ec1 100644 --- a/res/cardsfolder/t/two_headed_sliver.txt +++ b/res/cardsfolder/t/two_headed_sliver.txt @@ -2,7 +2,7 @@ Name:Two-Headed Sliver ManaCost:1 R Types:Creature Sliver PT:1/1 -S:Mode$ Continuous | Affected$ Creature.Sliver | AddKeyword$ CARDNAME can't be blocked except by two or more creatures. | Description$ All Sliver creatures have "This creature can't be blocked except by two or more creatures." +S:Mode$ Continuous | Affected$ Creature.Sliver | AddKeyword$ CantBeBlockedByAmount LT2 | Description$ All Sliver creatures have "This creature can't be blocked except by two or more creatures." SVar:PlayMain1:TRUE SVar:Picture:http://www.wizards.com/global/images/magic/general/two_headed_sliver.jpg Oracle:All Sliver creatures have "This creature can't be blocked except by two or more creatures." diff --git a/res/cardsfolder/v/viashino_runner.txt b/res/cardsfolder/v/viashino_runner.txt index 4803b4c7953..bb42173c54f 100644 --- a/res/cardsfolder/v/viashino_runner.txt +++ b/res/cardsfolder/v/viashino_runner.txt @@ -2,7 +2,7 @@ Name:Viashino Runner ManaCost:3 R Types:Creature Viashino PT:3/2 -K:CARDNAME can't be blocked except by two or more creatures. +K:CantBeBlockedByAmount LT2 SVar:Picture:http://www.wizards.com/global/images/magic/general/viashino_runner.jpg Oracle:Viashino Runner can't be blocked except by two or more creatures. SetInfo:10E Common diff --git a/res/cardsfolder/v/vine_kami.txt b/res/cardsfolder/v/vine_kami.txt index e5bc0ee98e1..2deb0174ea5 100644 --- a/res/cardsfolder/v/vine_kami.txt +++ b/res/cardsfolder/v/vine_kami.txt @@ -2,7 +2,7 @@ Name:Vine Kami ManaCost:6 G Types:Creature Spirit PT:4/4 -K:CARDNAME can't be blocked except by two or more creatures. +K:CantBeBlockedByAmount LT2 K:Soulshift 6 SVar:Picture:http://www.wizards.com/global/images/magic/general/vine_kami.jpg Oracle:Vine Kami can't be blocked except by two or more creatures.\nSoulshift 6 (When this creature dies, you may return target Spirit card with converted mana cost 6 or less from your graveyard to your hand.) diff --git a/res/cardsfolder/v/vorrac_battlehorns.txt b/res/cardsfolder/v/vorrac_battlehorns.txt index 4f6d0cdef4e..af5e69e0243 100644 --- a/res/cardsfolder/v/vorrac_battlehorns.txt +++ b/res/cardsfolder/v/vorrac_battlehorns.txt @@ -2,7 +2,7 @@ Name:Vorrac Battlehorns ManaCost:2 Types:Artifact Equipment K:Equip 1 -S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddKeyword$ Trample | AddHiddenKeyword$ CARDNAME can't be blocked by more than one creature. | Description$ Equipped creature has trample and can't be blocked by more than one creature. +S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddKeyword$ Trample | AddKeyword$ CantBeBlockedByAmount GT1 | Description$ Equipped creature has trample and can't be blocked by more than one creature. SVar:Picture:http://www.wizards.com/global/images/magic/general/vorrac_battlehorns.jpg Oracle:Equipped creature has trample and can't be blocked by more than one creature.\nEquip {1} ({1}: Attach to target creature you control. Equip only as a sorcery. This card enters the battlefield unattached and stays on the battlefield if the creature leaves.) SetInfo:MRD Common \ No newline at end of file diff --git a/res/cardsfolder/w/wind_spirit.txt b/res/cardsfolder/w/wind_spirit.txt index 8f096b671b6..a9f709cc5ed 100644 --- a/res/cardsfolder/w/wind_spirit.txt +++ b/res/cardsfolder/w/wind_spirit.txt @@ -3,7 +3,7 @@ ManaCost:4 U Types:Creature Spirit PT:3/2 K:Flying -K:CARDNAME can't be blocked except by two or more creatures. +K:CantBeBlockedByAmount LT2 SVar:Picture:http://www.wizards.com/global/images/magic/general/wind_spirit.jpg Oracle:Flying\nWind Spirit can't be blocked except by two or more creatures. SetInfo:ICE Uncommon diff --git a/res/cardsfolder/y/yuan_shao_the_indecisive.txt b/res/cardsfolder/y/yuan_shao_the_indecisive.txt index 96af2e31bbb..fb4d5b8740f 100644 --- a/res/cardsfolder/y/yuan_shao_the_indecisive.txt +++ b/res/cardsfolder/y/yuan_shao_the_indecisive.txt @@ -3,7 +3,7 @@ ManaCost:4 R Types:Legendary Creature Human Soldier PT:2/3 K:Horsemanship -S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CARDNAME can't be blocked by more than one creature. | Description$ Each creature you control can't be blocked by more than one creature. +S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control can't be blocked by more than one creature. SVar:Picture:http://www.wizards.com/global/images/magic/general/yuan_shao_the_indecisive.jpg Oracle:Horsemanship (This creature can't be blocked except by creatures with horsemanship.)\nEach creature you control can't be blocked by more than one creature. SetInfo:PTK Rare \ No newline at end of file diff --git a/res/lists/NonStackingKWList.txt b/res/lists/NonStackingKWList.txt index 78efc2aebaa..23351a63b20 100644 --- a/res/lists/NonStackingKWList.txt +++ b/res/lists/NonStackingKWList.txt @@ -1,7 +1,8 @@ All creatures able to block CARDNAME do so. CARDNAME attacks each turn if able. CARDNAME can't be regenerated. -CARDNAME can't be blocked except by two or more creatures. +CantBeBlockedByAmount LT2 +CantBeBlockedByAmount LT3 CARDNAME doesn't untap during your untap step. Changeling Convoke diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index 7c6f0125e30..c0dcc8ae3aa 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -42,7 +42,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import forge.CardPredicates.Presets; -import forge.Constant.CardTypes; import forge.card.CardCharacteristics; import forge.card.CardDb; import forge.card.CardRarity; @@ -2128,106 +2127,10 @@ public class Card extends GameEntity implements Comparable { continue; } else if (keyword.startsWith("CantBeBlockedBy")) { sbLong.append(this.getName()).append(" can't be blocked "); - - boolean negative = true; - List subs = Lists.newArrayList(TextUtil.split(keyword.split(" ", 2)[1], ',')); - List> subsAnd = Lists.newArrayList(); - List orClauses = new ArrayList(); - for( int iOr = 0; iOr < subs.size(); iOr++ ) { - String expession = subs.get(iOr); - List parts = Lists.newArrayList(expession.split("[.+]")); - for(int p = 0; p < parts.size(); p++) { - String part = parts.get(p); - if( part.equalsIgnoreCase("creature")) { - parts.remove(p--); - continue; - } - // based on suppossition that each expression has at least 1 predicate except 'creature' - negative &= part.contains("non") || part.contains("without"); - } - subsAnd.add(parts); - } - - final boolean allNegative = negative; - if( allNegative ) sbLong.append("except "); - sbLong.append("by "); - - final Function, String> withToString = new Function, String>() { - @Override - public String apply(Pair inp) { - boolean useNon = inp.getKey().booleanValue() == allNegative; - return (useNon ? "*NO* " : "" ) + inp.getRight(); - } - }; - - for( int iOr = 0; iOr < subsAnd.size(); iOr++ ) { - List andOperands = subsAnd.get(iOr); - List> prependedAdjectives = Lists.newArrayList(); - List> postponedAdjectives = Lists.newArrayList(); - String creatures = null; - - for(String part : andOperands) { - boolean positive = true; - if (part.startsWith("non")) { - part = part.substring(3); - positive = false; - } - if(part.startsWith("with")) { - positive = !part.startsWith("without"); - postponedAdjectives.add(Pair.of(positive, part.substring(positive ? 4 : 7))); - } else if ( part.startsWith("power") ) { - int kwLength = 5; - String opName = Expressions.operatorName(part.substring(kwLength, kwLength + 2)); - String operand = part.substring(kwLength + 2); - postponedAdjectives.add(Pair.of(true, "power" + opName + operand)); - } else if ( forge.card.CardType.isACreatureType(part)) { - creatures = StringUtils.capitalize(Lang.getPlural(part)) + ( creatures == null ? "" : " or " + creatures ); - } else { - prependedAdjectives.add(Pair.of(positive, part.toLowerCase())); - } - } - - StringBuilder sbShort = new StringBuilder(); - if ( allNegative ) { - boolean isFirst = true; - for(Pair pre : prependedAdjectives) { - if ( isFirst ) isFirst = false; - else sbShort.append(" and/or "); - - boolean useNon = pre.getKey().booleanValue() == allNegative; - if( useNon ) sbShort.append("non-"); - sbShort.append(pre.getValue()).append(" ").append(creatures == null ? "creatures" : creatures); - } - if (prependedAdjectives.isEmpty()) - sbShort.append(creatures == null ? "creatures" : creatures); - - if(!postponedAdjectives.isEmpty()) { - if( !prependedAdjectives.isEmpty() ) { - sbShort.append(" and/or creatures"); - } - - sbShort.append(" with "); - sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and")); - } - - } else { - for(Pair pre : prependedAdjectives) { - boolean useNon = pre.getKey().booleanValue() == allNegative; - if( useNon ) sbShort.append("non-"); - sbShort.append(pre.getValue()).append(" "); - } - sbShort.append(creatures == null ? "creatures" : creatures); - - if(!postponedAdjectives.isEmpty()) { - sbShort.append(" with "); - sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and")); - } - - } - orClauses.add(sbShort.toString()); - } - - sbLong.append(StringUtils.join(orClauses, " or ")); + if( keyword.startsWith("CantBeBlockedByAmount")) + sbLong.append(getTextForKwCantBeBlockedByAmount(keyword)); + else + sbLong.append(getTextForKwCantBeBlockedByType(keyword)); } else { @@ -2252,6 +2155,114 @@ public class Card extends GameEntity implements Comparable { return sb.toString(); } + private String getTextForKwCantBeBlockedByAmount(String keyword) { + String restriction = keyword.split(" ", 2)[1]; + boolean isLT = "LT".equals(restriction.substring(0,2)); + final String byClause = isLT ? "except by " : "by more than"; + int cnt = Integer.parseInt(restriction.substring(2)); + return byClause + Lang.nounWithNumeral(cnt, isLT ? "or more creature" : "creature"); + } + + private String getTextForKwCantBeBlockedByType(String keyword) { + boolean negative = true; + List subs = Lists.newArrayList(TextUtil.split(keyword.split(" ", 2)[1], ',')); + List> subsAnd = Lists.newArrayList(); + List orClauses = new ArrayList(); + for( int iOr = 0; iOr < subs.size(); iOr++ ) { + String expession = subs.get(iOr); + List parts = Lists.newArrayList(expession.split("[.+]")); + for(int p = 0; p < parts.size(); p++) { + String part = parts.get(p); + if( part.equalsIgnoreCase("creature")) { + parts.remove(p--); + continue; + } + // based on suppossition that each expression has at least 1 predicate except 'creature' + negative &= part.contains("non") || part.contains("without"); + } + subsAnd.add(parts); + } + + final boolean allNegative = negative; + final String byClause = allNegative ? "except by " : "by "; + + final Function, String> withToString = new Function, String>() { + @Override + public String apply(Pair inp) { + boolean useNon = inp.getKey().booleanValue() == allNegative; + return (useNon ? "*NO* " : "" ) + inp.getRight(); + } + }; + + for( int iOr = 0; iOr < subsAnd.size(); iOr++ ) { + List andOperands = subsAnd.get(iOr); + List> prependedAdjectives = Lists.newArrayList(); + List> postponedAdjectives = Lists.newArrayList(); + String creatures = null; + + for(String part : andOperands) { + boolean positive = true; + if (part.startsWith("non")) { + part = part.substring(3); + positive = false; + } + if(part.startsWith("with")) { + positive = !part.startsWith("without"); + postponedAdjectives.add(Pair.of(positive, part.substring(positive ? 4 : 7))); + } else if ( part.startsWith("power") ) { + int kwLength = 5; + String opName = Expressions.operatorName(part.substring(kwLength, kwLength + 2)); + String operand = part.substring(kwLength + 2); + postponedAdjectives.add(Pair.of(true, "power" + opName + operand)); + } else if ( forge.card.CardType.isACreatureType(part)) { + creatures = StringUtils.capitalize(Lang.getPlural(part)) + ( creatures == null ? "" : " or " + creatures ); + } else { + prependedAdjectives.add(Pair.of(positive, part.toLowerCase())); + } + } + + StringBuilder sbShort = new StringBuilder(); + if ( allNegative ) { + boolean isFirst = true; + for(Pair pre : prependedAdjectives) { + if ( isFirst ) isFirst = false; + else sbShort.append(" and/or "); + + boolean useNon = pre.getKey().booleanValue() == allNegative; + if( useNon ) sbShort.append("non-"); + sbShort.append(pre.getValue()).append(" ").append(creatures == null ? "creatures" : creatures); + } + if (prependedAdjectives.isEmpty()) + sbShort.append(creatures == null ? "creatures" : creatures); + + if(!postponedAdjectives.isEmpty()) { + if( !prependedAdjectives.isEmpty() ) { + sbShort.append(" and/or creatures"); + } + + sbShort.append(" with "); + sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and")); + } + + } else { + for(Pair pre : prependedAdjectives) { + boolean useNon = pre.getKey().booleanValue() == allNegative; + if( useNon ) sbShort.append("non-"); + sbShort.append(pre.getValue()).append(" "); + } + sbShort.append(creatures == null ? "creatures" : creatures); + + if(!postponedAdjectives.isEmpty()) { + sbShort.append(" with "); + sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and")); + } + + } + orClauses.add(sbShort.toString()); + } + return byClause + StringUtils.join(orClauses, " or "); + } + // get the text of the abilities of a card /** *

@@ -5141,7 +5152,7 @@ public class Card extends GameEntity implements Comparable { * @return a boolean. */ public final boolean hasStartOfUnHiddenKeyword(final String keyword) { - final ArrayList a = this.getUnhiddenKeyword(); + final List a = this.getUnhiddenKeyword(); for (int i = 0; i < a.size(); i++) { if (a.get(i).toString().startsWith(keyword)) { return true; diff --git a/src/main/java/forge/card/ability/ai/AttachAi.java b/src/main/java/forge/card/ability/ai/AttachAi.java index bf69547c68b..f6883ea6f31 100644 --- a/src/main/java/forge/card/ability/ai/AttachAi.java +++ b/src/main/java/forge/card/ability/ai/AttachAi.java @@ -1047,9 +1047,8 @@ public class AttachAi extends SpellAbilityAi { final boolean evasive = (keyword.equals("Unblockable") || keyword.equals("Fear") || keyword.equals("Intimidate") || keyword.equals("Shadow") || keyword.equals("Flying") || keyword.equals("Horsemanship") - || keyword.endsWith("walk") || keyword.equals("CARDNAME can't be blocked except by Walls.") - || keyword.equals("All creatures able to block CARDNAME do so.") - || keyword.equals("CARDNAME can't be blocked by more than one creature.")); + || keyword.endsWith("walk") || keyword.startsWith("CantBeBlockedBy") + || keyword.equals("All creatures able to block CARDNAME do so.")); // give evasive keywords to creatures that can attack and deal damage if (evasive) { if (card.getNetCombatDamage() + powerBonus <= 0 diff --git a/src/main/java/forge/card/ability/ai/PumpAiBase.java b/src/main/java/forge/card/ability/ai/PumpAiBase.java index f03a5a67d58..c97bff1e437 100644 --- a/src/main/java/forge/card/ability/ai/PumpAiBase.java +++ b/src/main/java/forge/card/ability/ai/PumpAiBase.java @@ -167,8 +167,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { final boolean evasive = (keyword.endsWith("Unblockable") || keyword.endsWith("Fear") || keyword.endsWith("Intimidate") || keyword.endsWith("Shadow") - || keyword.contains("CantBeBlockedBy") || keyword.endsWith("CARDNAME can't be blocked except by Walls.") - || keyword.endsWith("CARDNAME can't be blocked except by two or more creatures.")); + || keyword.startsWith("CantBeBlockedBy")); final boolean combatRelevant = (keyword.endsWith("First Strike") || keyword.contains("Bushido")); // give evasive keywords to creatures that can or do attack if (evasive) { diff --git a/src/main/java/forge/game/ai/AiAttackController.java b/src/main/java/forge/game/ai/AiAttackController.java index 756a4c7d4c5..c4a85b10d0e 100644 --- a/src/main/java/forge/game/ai/AiAttackController.java +++ b/src/main/java/forge/game/ai/AiAttackController.java @@ -951,9 +951,7 @@ public class AiAttackController { return true; } - if (numberOfPossibleBlockers > 1 - || (numberOfPossibleBlockers == 1 - && !attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures."))) { + if (numberOfPossibleBlockers > 1 || (numberOfPossibleBlockers == 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1))) { canBeBlocked = true; } /*System.out.println(attacker + " canBeKilledByOne: " + canBeKilledByOne + " canKillAll: " diff --git a/src/main/java/forge/game/ai/ComputerUtilBlock.java b/src/main/java/forge/game/ai/ComputerUtilBlock.java index 9090bad64ed..95abbd8c95c 100644 --- a/src/main/java/forge/game/ai/ComputerUtilBlock.java +++ b/src/main/java/forge/game/ai/ComputerUtilBlock.java @@ -343,7 +343,7 @@ public class ComputerUtilBlock { for (final Card attacker : ComputerUtilBlock.getAttackersLeft()) { - if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) { + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { continue; } @@ -405,7 +405,7 @@ public class ComputerUtilBlock { * a {@link forge.game.phase.Combat} object. * @return a {@link forge.game.phase.Combat} object. */ - static final Predicate rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CARDNAME can't be blocked by more than one creature.")); + static final Predicate rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT")); private static Combat makeGangBlocks(final Player ai, final Combat combat) { List currentAttackers = CardLists.filter(ComputerUtilBlock.getAttackersLeft(), Predicates.not(rampagesOrNeedsManyToBlock)); @@ -537,7 +537,7 @@ public class ComputerUtilBlock { for (final Card attacker : ComputerUtilBlock.getAttackersLeft()) { - if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) { + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { continue; } @@ -579,8 +579,7 @@ public class ComputerUtilBlock { Card attacker = attackers.get(0); - if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.") - || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { attackers.remove(0); return makeChumpBlocks(ai, combat, attackers); } @@ -643,8 +642,7 @@ public class ComputerUtilBlock { for (final Card attacker : tramplingAttackers) { - if ((attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.") - && !combat.isBlocked(attacker)) + if ((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") && !combat.isBlocked(attacker)) || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { continue; } diff --git a/src/main/java/forge/game/ai/ComputerUtilCard.java b/src/main/java/forge/game/ai/ComputerUtilCard.java index b5d92b8770e..11b006d102f 100644 --- a/src/main/java/forge/game/ai/ComputerUtilCard.java +++ b/src/main/java/forge/game/ai/ComputerUtilCard.java @@ -417,11 +417,8 @@ public class ComputerUtilCard { if (c.hasKeyword("Intimidate")) { value += power * 6; } - if (c.hasStartOfKeyword("CARDNAME can't be blocked except by")) { - value += power * 5; - } - if (c.hasStartOfKeyword("CARDNAME can't be blocked by")) { - value += power * 2; + if (c.hasStartOfKeyword("CantBeBlockedBy")) { + value += power * 3; } } diff --git a/src/main/java/forge/game/phase/CombatUtil.java b/src/main/java/forge/game/phase/CombatUtil.java index dc178c3dc04..7565ec181bb 100644 --- a/src/main/java/forge/game/phase/CombatUtil.java +++ b/src/main/java/forge/game/phase/CombatUtil.java @@ -28,6 +28,7 @@ import org.apache.commons.lang3.StringUtils; import com.esotericsoftware.minlog.Log; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import forge.Card; import forge.CardLists; @@ -50,6 +51,8 @@ import forge.game.combat.AttackingBand; import forge.game.player.Player; import forge.game.player.PlayerController.ManaPaymentPurpose; import forge.game.zone.ZoneType; +import forge.util.Expressions; +import forge.util.TextUtil; /** @@ -179,8 +182,7 @@ public class CombatUtil { return true; } - if (attacker.hasKeyword("CARDNAME can't be blocked by more than one creature.") && - !combat.getBlockers(attacker).isEmpty()) { + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount GT") && !combat.getBlockers(attacker).isEmpty()) { return false; } return CombatUtil.canBeBlocked(attacker); @@ -321,24 +323,14 @@ public class CombatUtil { return false; } - if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) { - int blocks = 0; - for (final Card blocker : blockers) { - if (CombatUtil.canBlock(attacker, blocker)) { - blocks += 1; - if (blocks > 1) { - return true; - } - } - } - } else { - for (final Card blocker : blockers) { - if (CombatUtil.canBlock(attacker, blocker)) { - return true; - } + int blocks = 0; + for (final Card blocker : blockers) { + if (CombatUtil.canBlock(attacker, blocker)) { + blocks++; } } - return false; + + return canAttackerBeBlockedWithAmount(attacker, blocks); } /** @@ -356,11 +348,12 @@ public class CombatUtil { return 0; } - if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) { + if (attacker.hasKeyword("CantBeBlockedByAmount LT2")) { return 2; - } - - return 1; + } else if (attacker.hasKeyword("CantBeBlockedByAmount LT3")) { + return 3; + } else + return 1; } // Has the human player chosen all mandatory blocks? @@ -390,7 +383,7 @@ public class CombatUtil { for (final Card attacker : attackers) { if (CombatUtil.canBlock(attacker, blocker, combat)) { boolean must = true; - if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) { + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { final List possibleBlockers = CardLists.filter(defending.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); possibleBlockers.remove(blocker); if (!CombatUtil.canBeBlocked(attacker, possibleBlockers)) { @@ -406,11 +399,9 @@ public class CombatUtil { } for (final Card attacker : attackers) { - // don't accept one blocker for attackers with this keyword - if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.") - && (combat.getBlockers(attacker).size() == 1)) { + // don't accept blocker amount for attackers with keyword defining valid blockers amount + if (!canAttackerBeBlockedWithAmount(attacker, combat.getBlockers(attacker).size())) return false; - } } return true; @@ -497,7 +488,7 @@ public class CombatUtil { for (final Card attacker : attackersWithLure) { if (CombatUtil.canBeBlocked(attacker, combat) && CombatUtil.canBlock(attacker, blocker)) { boolean canBe = true; - if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) { + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { final List blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay(); blockers.remove(blocker); if (!CombatUtil.canBeBlocked(attacker, blockers)) { @@ -515,7 +506,7 @@ public class CombatUtil { if (CombatUtil.canBeBlocked(attacker, combat) && CombatUtil.canBlock(attacker, blocker) && combat.isAttacking(attacker)) { boolean canBe = true; - if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) { + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { final List blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay(); blockers.remove(blocker); if (!CombatUtil.canBeBlocked(attacker, blockers)) { @@ -645,23 +636,13 @@ public class CombatUtil { return false; } - if (attacker.hasKeyword("Creatures with power less than CARDNAME's power can't block it.") - && (attacker.getNetAttack() > blocker.getNetAttack())) { - return false; - } - if ((blocker.getNetAttack() > attacker.getNetAttack()) - && blocker.hasKeyword("CARDNAME can't be blocked by creatures " - + "with power greater than CARDNAME's power.")) { - return false; - } - if ((blocker.getNetAttack() >= attacker.getNetDefense()) - && blocker.hasKeyword("CARDNAME can't be blocked by creatures with " - + "power equal to or greater than CARDNAME's toughness.")) { + if (attacker.hasKeyword("Creatures with power less than CARDNAME's power can't block it.") && attacker.getNetAttack() > blocker.getNetAttack()) { return false; } - if (attacker.hasStartOfKeyword("CantBeBlockedBy")) { - final int keywordPosition = attacker.getKeywordPosition("CantBeBlockedBy"); + + if (attacker.hasStartOfKeyword("CantBeBlockedBy ")) { + final int keywordPosition = attacker.getKeywordPosition("CantBeBlockedBy "); final String parse = attacker.getKeyword().get(keywordPosition).toString(); final String[] k = parse.split(" ", 2); final String[] restrictions = k[1].split(","); @@ -684,29 +665,20 @@ public class CombatUtil { return false; } - if (attacker.hasKeyword("Flying") - || attacker.hasKeyword("CARDNAME can't be blocked except by creatures with flying or reach.")) { - if (!blocker.hasKeyword("Flying") && !blocker.hasKeyword("Reach")) { - return false; - } + if (attacker.hasKeyword("Flying") && !blocker.hasKeyword("Flying") && !blocker.hasKeyword("Reach")) { + return false; } - if (attacker.hasKeyword("Horsemanship")) { - if (!blocker.hasKeyword("Horsemanship")) { - return false; - } + if (attacker.hasKeyword("Horsemanship") && !blocker.hasKeyword("Horsemanship")) { + return false; } - if (attacker.hasKeyword("Fear")) { - if (!blocker.isArtifact() && !blocker.isBlack()) { - return false; - } + if (attacker.hasKeyword("Fear") && !blocker.isArtifact() && !blocker.isBlack()) { + return false; } - if (attacker.hasKeyword("Intimidate")) { - if (!blocker.isArtifact() && !blocker.sharesColorWith(attacker)) { - return false; - } + if (attacker.hasKeyword("Intimidate") && !blocker.isArtifact() && !blocker.sharesColorWith(attacker)) { + return false; } return true; @@ -788,6 +760,22 @@ public class CombatUtil { } return true; } + + public static boolean canAttackerBeBlockedWithAmount(Card attacker, int amount) { + List restrictions = Lists.newArrayList(); + for ( String kw : attacker.getKeyword() ) { + if ( kw.startsWith("CantBeBlockedByAmount") ) + restrictions.add(TextUtil.split(kw, ' ', 2)[1]); + } + for ( String res : restrictions ) { + int operand = Integer.parseInt(res.substring(2)); + String operator = res.substring(0,2); + if (Expressions.compare(amount, operator, operand) ) + return false; + } + + return true; + } // can a creature attack if untapped and without summoning sickness? /** diff --git a/src/main/java/forge/util/Lang.java b/src/main/java/forge/util/Lang.java index bcbfe512e56..291fa9b78d7 100644 --- a/src/main/java/forge/util/Lang.java +++ b/src/main/java/forge/util/Lang.java @@ -73,8 +73,12 @@ public class Lang { else strCount = String.valueOf(cnt) + " "; return strCount + countedForm; - } + } + public static String nounWithNumeral(int cnt, String noun) { + String countedForm = cnt <= 1 ? noun : getPlural(noun); + return getNumeral(cnt) + " " + countedForm; + } /** * TODO: Write javadoc for this method. * @param name @@ -96,4 +100,22 @@ public class Lang { return false; } + + public final static String[] numbers0 = new String[] { + "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", + "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eightteen", "nineteen" }; + public final static String[] numbers20 = new String[] {"twenty", "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety" }; + + public static String getNumeral(int n) { + String prefix = n < 0 ? "minus " : ""; + n = Math.abs(n); + if ( n >= 0 && n < 20 ) + return prefix + numbers0[n]; + if ( n < 100 ) { + int n1 = n % 10; + String ones = n1 == 0 ? "" : numbers0[n1]; + return prefix + numbers20[(n / 10) - 2] + " " + ones; + } + return Integer.toString(n); + } }