diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index f39ef6d5de3..6fda9f51216 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -96,6 +96,7 @@ public class Card extends GameEntity implements Comparable { private boolean dealtDmgToHumanThisTurn = false; private boolean dealtDmgToComputerThisTurn = false; private boolean sirenAttackOrDestroy = false; + private ArrayList mustBlockCards = new ArrayList(); private boolean canMorph = false; private boolean faceDown = false; @@ -650,6 +651,38 @@ public class Card extends GameEntity implements Comparable { public final boolean getSirenAttackOrDestroy() { return sirenAttackOrDestroy; } + + /** + * a Card that this Card must block if able in an upcoming combat. + * This is cleared at the end of each turn. + * + * @param o Card to block + * + * @since 1.1.6 + */ + public void addMustBlockCard(Card c) { + mustBlockCards.add(c); + } + + /** + * get the Card that this Card must block this combat + * + * @return the Cards to block (if able) + * + * @since 1.1.6 + */ + public ArrayList getMustBlockCards() { + return mustBlockCards; + } + + /** + * clear the list of Cards that this Card must block this combat + * + * @since 1.1.6 + */ + public void clearMustBlockCards() { + mustBlockCards.clear(); + } /** *

Getter for the field clones.

@@ -5058,7 +5091,8 @@ public class Card extends GameEntity implements Comparable { if (Property.startsWith("non") && (CardUtil.getColors(this).size() == 1 && !isColorless())) return false; if (!Property.startsWith("non") && (CardUtil.getColors(this).size() > 1 || isColorless())) return false; } else if (Property.equals("ChosenColor")) { - if (!CardUtil.getColors(this).contains(source.getChosenColor())) return false; + //Should this match All chosen colors, or any? Default to first chosen for now until it matters. + if (!CardUtil.getColors(this).contains(source.getChosenColor().get(0))) return false; } else if (Property.startsWith("YouCtrl")) { if (!getController().isPlayer(sourceController)) return false; } else if (Property.startsWith("YouDontCtrl")) { diff --git a/src/main/java/forge/ComputerUtil_Block2.java b/src/main/java/forge/ComputerUtil_Block2.java index c6b4d24b2e6..48a2361b391 100644 --- a/src/main/java/forge/ComputerUtil_Block2.java +++ b/src/main/java/forge/ComputerUtil_Block2.java @@ -560,6 +560,8 @@ public class ComputerUtil_Block2 { setBlockedButUnkilled(new CardList()); //keeps track of all blocked attackers that currently wouldn't be destroyed CardList blockers; CardList chumpBlockers; + + setupForcedBlocks(combat); setDiff(AllZone.getComputerPlayer().getLife() * 2 - 5); //This is the minimal gain for an unnecessary trade @@ -642,4 +644,19 @@ public class ComputerUtil_Block2 { return combat; } + + private static void setupForcedBlocks(Combat combat) { + CardList blockers = AllZoneUtil.getCreaturesInPlay(combat.getDefendingPlayer()); + for (Card blocker : blockers) { + if (!blocker.getMustBlockCards().isEmpty()) { + ArrayList blocks = blocker.getMustBlockCards(); + for (Card attacker : blocks) { + if (attacker.isAttacking() && CombatUtil.canBlock(attacker, blocker)) { + combat.addBlocker(attacker, blocker); + getBlockersLeft().remove(blocker); + } + } + } + } + } } diff --git a/src/main/java/forge/EndOfTurn.java b/src/main/java/forge/EndOfTurn.java index 2f24bdf009e..afd1a94402d 100644 --- a/src/main/java/forge/EndOfTurn.java +++ b/src/main/java/forge/EndOfTurn.java @@ -254,6 +254,7 @@ public class EndOfTurn implements java.io.Serializable { CardList all2 = AllZoneUtil.getCardsIn(Zone.Battlefield); for (Card c : all2) { + c.clearMustBlockCards(); if (c.getCreatureAttackedThisTurn()) { c.setCreatureAttackedThisTurn(false); } diff --git a/src/main/java/forge/card/abilityFactory/AbilityFactory.java b/src/main/java/forge/card/abilityFactory/AbilityFactory.java index 50a732a4f6c..aa2948bc4dd 100644 --- a/src/main/java/forge/card/abilityFactory/AbilityFactory.java +++ b/src/main/java/forge/card/abilityFactory/AbilityFactory.java @@ -1050,6 +1050,16 @@ public class AbilityFactory { } } + else if (API.equals("MustBlock")) { + if (isAb) { + SA = AbilityFactory_Combat.createAbilityMustBlock(this); + } else if (isSp) { + SA = AbilityFactory_Combat.createSpellMustBlock(this); + } else if (isDb) { + SA = AbilityFactory_Combat.createDrawbackMustBlock(this); + } + } + else if (API.equals("Charm")) { if (isAb) { SA = AbilityFactory_Charm.createAbilityCharm(this); diff --git a/src/main/java/forge/card/abilityFactory/AbilityFactory_Combat.java b/src/main/java/forge/card/abilityFactory/AbilityFactory_Combat.java index c9dc233aeb4..87e6102f285 100644 --- a/src/main/java/forge/card/abilityFactory/AbilityFactory_Combat.java +++ b/src/main/java/forge/card/abilityFactory/AbilityFactory_Combat.java @@ -665,5 +665,227 @@ public final class AbilityFactory_Combat { } } //mustAttackResolve() + + //************************************************************** + //*********************** MustBlock **************************** + //************************************************************** + + //AB$ MustBlock | Cost$ R T | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature defending player controls | DefinedAttacker$ Self | SpellDescription$ ... + + /** + *

createAbilityMustBlock.

+ * + * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. + * @return a {@link forge.card.spellability.SpellAbility} object. + * + * @since 1.1.6 + */ + public static SpellAbility createAbilityMustBlock(final AbilityFactory af) { + final SpellAbility abMustBlock = new Ability_Activated(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { + private static final long serialVersionUID = 4237190949098526123L; + + @Override + public String getStackDescription() { + return mustBlockStackDescription(af, this); + } + + @Override + public boolean canPlayAI() { + return mustBlockCanPlayAI(af, this); + } + + @Override + public void resolve() { + mustBlockResolve(af, this); + } + + @Override + public boolean doTrigger(final boolean mandatory) { + return mustBlockDoTriggerAI(af, this, mandatory); + } + + }; + return abMustBlock; + } + + /** + *

createSpellMustBlock.

+ * + * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. + * @return a {@link forge.card.spellability.SpellAbility} object. + * + * @since 1.1.6 + */ + public static SpellAbility createSpellMustBlock(final AbilityFactory af) { + final SpellAbility spMustBlock = new Spell(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { + private static final long serialVersionUID = 6758785067306305860L; + + @Override + public String getStackDescription() { + return mustBlockStackDescription(af, this); + } + + @Override + public boolean canPlayAI() { + return mustBlockCanPlayAI(af, this); + } + + @Override + public void resolve() { + mustBlockResolve(af, this); + } + + }; + return spMustBlock; + } + + /** + *

createDrawbackMustBlock.

+ * + * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. + * @return a {@link forge.card.spellability.SpellAbility} object. + * + * @since 1.1.6 + */ + public static SpellAbility createDrawbackMustBlock(final AbilityFactory af) { + final SpellAbility dbMustBlock = new Ability_Sub(af.getHostCard(), af.getAbTgt()) { + private static final long serialVersionUID = -815813765448972775L; + + @Override + public void resolve() { + mustBlockResolve(af, this); + } + + @Override + public boolean chkAI_Drawback() { + return mustBlockPlayDrawbackAI(af, this); + } + + @Override + public boolean doTrigger(final boolean mandatory) { + return mustBlockDoTriggerAI(af, this, mandatory); + } + + }; + return dbMustBlock; + } + + private static String mustBlockStackDescription(final AbilityFactory af, final SpellAbility sa) { + HashMap params = af.getMapParams(); + Card host = af.getHostCard(); + StringBuilder sb = new StringBuilder(); + + if (sa instanceof Ability_Sub) { + sb.append(" "); + } else { + sb.append(sa.getSourceCard()).append(" - "); + } + + //end standard pre- + + ArrayList tgtCards; + + Target tgt = af.getAbTgt(); + if (tgt != null) { + tgtCards = tgt.getTargetCards(); + } else { + tgtCards = AbilityFactory.getDefinedCards(sa.getSourceCard(), params.get("Defined"), sa); + } + + String attacker = null; + if (params.containsKey("DefinedAttacker")) { + ArrayList cards = AbilityFactory.getDefinedCards(sa.getSourceCard(), params.get("DefinedAttacker"), sa); + attacker = cards.get(0).toString(); + } + else { + attacker = host.toString(); + } + + for (Card c : tgtCards) { + sb.append(c).append(" must block ").append(attacker).append(" if able."); + } + + //begin standard post- + Ability_Sub abSub = sa.getSubAbility(); + if (abSub != null) { + sb.append(abSub.getStackDescription()); + } + + return sb.toString(); + } + + private static boolean mustBlockCanPlayAI(final AbilityFactory af, final SpellAbility sa) { + //disabled for the AI until he/she can make decisions about who to make block + return false; + } + + private static boolean mustBlockPlayDrawbackAI(final AbilityFactory af, final SpellAbility sa) { + // AI should only activate this during Human's turn + boolean chance; + + //TODO - implement AI + chance = false; + + Ability_Sub subAb = sa.getSubAbility(); + if (subAb != null) { + chance &= subAb.chkAI_Drawback(); + } + + return chance; + } + + private static boolean mustBlockDoTriggerAI(final AbilityFactory af, final SpellAbility sa, + final boolean mandatory) + { + // If there is a cost payment it's usually not mandatory + if (!ComputerUtil.canPayCost(sa) && !mandatory) { + return false; + } + + boolean chance; + + //TODO - implement AI + chance = false; + + // check SubAbilities DoTrigger? + Ability_Sub abSub = sa.getSubAbility(); + if (abSub != null) { + return chance && abSub.doTrigger(mandatory); + } + + return chance; + } + + private static void mustBlockResolve(final AbilityFactory af, final SpellAbility sa) { + HashMap params = af.getMapParams(); + Card host = af.getHostCard(); + + ArrayList tgtCards; + + Target tgt = af.getAbTgt(); + if (tgt != null) { + tgtCards = tgt.getTargetCards(); + } else { + tgtCards = AbilityFactory.getDefinedCards(sa.getSourceCard(), params.get("Defined"), sa); + } + + ArrayList cards; + if (params.containsKey("DefinedAttacker")) { + cards = AbilityFactory.getDefinedCards(sa.getSourceCard(), params.get("DefinedAttacker"), sa); + } + else { + cards = new ArrayList(); + cards.add(host); + } + + for (final Card c : tgtCards) { + if (tgt == null || CardFactoryUtil.canTarget(sa, c)) { + Card attacker = cards.get(0); + c.addMustBlockCard(attacker); + System.out.println(c+ " is adding "+attacker+" to mustBlockCards: "+c.getMustBlockCards()); + } + } + + } //mustBlockResolve() } //end class AbilityFactory_Combat