From 453c1e7f9fc58b22a1c931ab50b195e501f4af84 Mon Sep 17 00:00:00 2001 From: Hanmac Date: Thu, 25 Aug 2016 12:15:54 +0000 Subject: [PATCH] add Goad Effect, add it to AttackRequirement and CombatUtil also add it to Ai logic --- .gitattributes | 1 + .../java/forge/ai/AiAttackController.java | 6 ++- .../src/main/java/forge/ai/AiController.java | 2 +- .../main/java/forge/ai/ComputerUtilCard.java | 2 +- .../src/main/java/forge/ai/SpellApiToAi.java | 2 + .../main/java/forge/game/ability/ApiType.java | 1 + .../game/ability/effects/GoadEffect.java | 47 +++++++++++++++++++ .../src/main/java/forge/game/card/Card.java | 38 +++++++++++---- .../forge/game/combat/AttackRequirement.java | 5 ++ .../java/forge/game/combat/CombatUtil.java | 19 ++++++++ 10 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/ability/effects/GoadEffect.java diff --git a/.gitattributes b/.gitattributes index f051b7dc6e4..e743def6959 100644 --- a/.gitattributes +++ b/.gitattributes @@ -398,6 +398,7 @@ forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java -text forge-game/src/main/java/forge/game/ability/effects/FogEffect.java -text forge-game/src/main/java/forge/game/ability/effects/GameLossEffect.java -text forge-game/src/main/java/forge/game/ability/effects/GameWinEffect.java -text +forge-game/src/main/java/forge/game/ability/effects/GoadEffect.java -text svneol=unset#text/plain forge-game/src/main/java/forge/game/ability/effects/LifeExchangeEffect.java -text forge-game/src/main/java/forge/game/ability/effects/LifeGainEffect.java -text forge-game/src/main/java/forge/game/ability/effects/LifeLoseEffect.java -text diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index c4f8d7554de..41f75664860 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -125,7 +125,7 @@ public class AiAttackController { if (sa.getApi() == ApiType.Animate) { if (ComputerUtilCost.canPayCost(sa, defender) && sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) { - Card animatedCopy = CardFactory.copyCard(c, true); + Card animatedCopy = CardFactory.copyCard(c, false); AnimateAi.becomeAnimated(animatedCopy, c.hasSickness(), sa); defenders.add(animatedCopy); } @@ -542,7 +542,9 @@ public class AiAttackController { continue; } boolean mustAttack = false; - if (attacker.getSVar("MustAttack").equals("True")) { + if (attacker.isGoaded()) { + mustAttack = true; + } else if (attacker.getSVar("MustAttack").equals("True")) { mustAttack = true; } else if (attacker.getSVar("EndOfTurnLeavePlay").equals("True") && isEffectiveAttacker(ai, attacker, combat)) { diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index bb630f23d2a..98d51102f17 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -1206,7 +1206,7 @@ public class AiController { * 5. needs to be updated to ensure that the net toughness is * still positive after static effects. */ - final Card creature = CardFactory.copyCard(card, true); + final Card creature = CardFactory.copyCard(card, false); ComputerUtilCard.applyStaticContPT(game, creature, null); if (creature.getNetToughness() <= 0 && !creature.hasStartOfKeyword("etbCounter") && mana.countX() == 0 && !creature.hasETBTrigger(false) && !creature.hasETBReplacement() diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 61d8f8e5f27..fcb30c92507 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -1240,7 +1240,7 @@ public class ComputerUtilCard { public static Card getPumpedCreature(final Player ai, final SpellAbility sa, final Card c, final int toughness, final int power, final List keywords) { - Card pumped = CardFactory.copyCard(c, true); + Card pumped = CardFactory.copyCard(c, false); pumped.setSickness(c.hasSickness()); final long timestamp = c.getGame().getNextTimestamp(); final List kws = new ArrayList(); diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index 16555855705..5fcaf1bf90d 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -27,6 +27,7 @@ public enum SpellApiToAi { .put(ApiType.AnimateAll, AnimateAllAi.class) .put(ApiType.Attach, AttachAi.class) .put(ApiType.Balance, BalanceAi.class) + .put(ApiType.BecomeMonarch, AlwaysPlayAi.class) .put(ApiType.BecomesBlocked, BecomesBlockedAi.class) .put(ApiType.BidLife, BidLifeAi.class) .put(ApiType.Bond, BondAi.class) @@ -77,6 +78,7 @@ public enum SpellApiToAi { .put(ApiType.GainLife, LifeGainAi.class) .put(ApiType.GainOwnership, CannotPlayAi.class) .put(ApiType.GenericChoice, ChooseGenericEffectAi.class) + .put(ApiType.Goad, AlwaysPlayAi.class) .put(ApiType.LoseLife, LifeLoseAi.class) .put(ApiType.LosesGame, GameLossAi.class) .put(ApiType.Mana, ManaEffectAi.class) diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index 4f52ae80cbf..08d5be6897a 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -69,6 +69,7 @@ public enum ApiType { Fight (FightEffect.class), FlipACoin (FlipCoinEffect.class), Fog (FogEffect.class), + Goad (GoadEffect.class), GainControl (ControlGainEffect.class), GainLife (LifeGainEffect.class), GainOwnership (OwnershipGainEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/GoadEffect.java b/forge-game/src/main/java/forge/game/ability/effects/GoadEffect.java new file mode 100644 index 00000000000..ae1cdb689ee --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/GoadEffect.java @@ -0,0 +1,47 @@ +package forge.game.ability.effects; + +import forge.GameCommand; +import forge.game.Game; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.TargetRestrictions; +import forge.game.zone.ZoneType; + +public class GoadEffect extends SpellAbilityEffect { + + @Override + public void resolve(SpellAbility sa) { + final TargetRestrictions tgt = sa.getTargetRestrictions(); + final Player player = sa.getActivatingPlayer(); + final Game game = player.getGame(); + final long timestamp = game.getNextTimestamp(); + + for (final Card tgtC : getTargetCards(sa)) { + // only pump things in PumpZone + if (!game.getCardsIn(ZoneType.Battlefield).contains(tgtC)) { + continue; + } + + // if pump is a target, make sure we can still target now + if ((tgt != null) && !tgtC.canBeTargetedBy(sa)) { + continue; + } + + tgtC.addGoad(timestamp, player); + + final GameCommand untilEOT = new GameCommand() { + private static final long serialVersionUID = -1731759226844770852L; + + @Override + public void run() { + tgtC.removeGoad(timestamp); + } + }; + + game.getCleanup().addUntil(player, untilEOT); + } + } + +} diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 54a327cb25d..6e1078e8a79 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -116,9 +116,9 @@ public class Card extends GameEntity implements Comparable { private int mayPlayTurn = 0; // changes by AF animate and continuous static effects - timestamp is the key of maps - private final Map changedCardTypes = new TreeMap<>(); - private final Map changedCardKeywords = new TreeMap<>(); - private final SortedMap changedCardColors = new TreeMap<>(); + private final Map changedCardTypes = Maps.newTreeMap(); + private final Map changedCardKeywords = Maps.newTreeMap(); + private final SortedMap changedCardColors = Maps.newTreeMap(); // changes that say "replace each instance of one [color,type] by another - timestamp is the key of maps private final CardChangedWords changedTextColors = new CardChangedWords(); @@ -132,10 +132,10 @@ public class Card extends GameEntity implements Comparable { private final MapOfLists rememberMap = new HashMapOfLists<>(CollectionSuppliers.arrayLists()); private Map flipResult; - private Map receivedDamageFromThisTurn = new TreeMap<>(); - private Map dealtDamageToThisTurn = new TreeMap<>(); - private Map dealtDamageToPlayerThisTurn = new TreeMap<>(); - private final Map assignedDamageMap = new TreeMap<>(); + private Map receivedDamageFromThisTurn = Maps.newTreeMap(); + private Map dealtDamageToThisTurn = Maps.newTreeMap(); + private Map dealtDamageToPlayerThisTurn = Maps.newTreeMap(); + private final Map assignedDamageMap = Maps.newTreeMap(); private boolean isCommander = false; private boolean startsGameInPlay = false; @@ -209,7 +209,7 @@ public class Card extends GameEntity implements Comparable { private Player owner = null; private Player controller = null; private long controllerTimestamp = 0; - private NavigableMap tempControllers = new TreeMap<>(); + private NavigableMap tempControllers = Maps.newTreeMap(); private String originalText = "", text = ""; private Cost miracleCost = null; @@ -222,6 +222,8 @@ public class Card extends GameEntity implements Comparable { private Card exiledWith = null; + private Map goad = Maps.newTreeMap(); + private final List leavePlayCommandList = new ArrayList<>(); private final List etbCommandList = new ArrayList<>(); private final List untapCommandList = new ArrayList<>(); @@ -6911,4 +6913,24 @@ public class Card extends GameEntity implements Comparable { getZone().remove(this); getGame().getTriggerHandler().clearSuppression(TriggerType.ChangesZone); } + + public final void addGoad(Long timestamp, final Player p) { + goad.put(timestamp, p); + } + + public final void removeGoad(Long timestamp) { + goad.remove(timestamp); + } + + public final boolean isGoaded() { + return !goad.isEmpty(); + } + + public final boolean isGoadedBy(final Player p) { + return goad.containsValue(p); + } + + public final Collection getGoaded() { + return goad.values(); + } } diff --git a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java index ddeacc213d2..eb698d3b7b2 100644 --- a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java +++ b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java @@ -34,6 +34,11 @@ public class AttackRequirement { } int nAttackAnything = 0; + + if (attacker.isGoaded()) { + nAttackAnything += attacker.getGoaded().size(); + } + for (final String keyword : attacker.getKeywords()) { if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) { final String defined = keyword.split(":")[1]; diff --git a/forge-game/src/main/java/forge/game/combat/CombatUtil.java b/forge-game/src/main/java/forge/game/combat/CombatUtil.java index 5bc39a4f196..44ef95e32a6 100644 --- a/forge-game/src/main/java/forge/game/combat/CombatUtil.java +++ b/forge-game/src/main/java/forge/game/combat/CombatUtil.java @@ -188,6 +188,25 @@ public class CombatUtil { return false; } + // Goad logic + // a goaded creature does need to attack a player which does not goaded her + // or if not possible a planeswalker or a player which does goaded her + if (attacker.isGoaded()) { + final boolean goadedByDefender = defender instanceof Player && attacker.isGoadedBy((Player) defender); + // attacker got goaded by defender or defender is not player + if (goadedByDefender || !(defender instanceof Player)) { + for (GameEntity ge : getAllPossibleDefenders(attacker.getController())) { + if (!defender.equals(ge) && ge instanceof Player) { + // found a player which does not goad that creature + // and creature can attack this player or planeswalker + if (!attacker.isGoadedBy((Player) ge) && canAttack(attacker, ge)) { + return false; + } + } + } + } + } + // Keywords final boolean canAttackWithDefender = attacker.hasKeyword("CARDNAME can attack as though it didn't have defender."); for (final String keyword : attacker.getKeywords()) {