From ce34bebc52b78fe3385d786a21a4f8888b1f72ff Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sun, 24 Apr 2022 14:01:59 +0200 Subject: [PATCH] Goodbye MustAttackEffect --- .../java/forge/ai/AiAttackController.java | 79 ++++++++-------- .../java/forge/ai/ComputerUtilCombat.java | 9 +- .../src/main/java/forge/ai/SpellApiToAi.java | 1 - .../main/java/forge/ai/ability/AttachAi.java | 2 - .../java/forge/ai/ability/MustAttackAi.java | 35 ------- .../java/forge/ai/ability/PumpAiBase.java | 4 - .../src/main/java/forge/game/GameAction.java | 1 - .../main/java/forge/game/ability/ApiType.java | 1 - .../ability/effects/MustAttackEffect.java | 91 ------------------- .../ability/effects/RestartGameEffect.java | 1 - .../src/main/java/forge/game/card/Card.java | 19 ---- .../forge/game/combat/AttackRequirement.java | 24 ++--- .../java/forge/game/phase/PhaseHandler.java | 3 - .../main/java/forge/game/player/Player.java | 18 ---- forge-gui/res/lists/NonStackingKWList.txt | 1 - 15 files changed, 49 insertions(+), 240 deletions(-) delete mode 100644 forge-ai/src/main/java/forge/ai/ability/MustAttackAi.java delete mode 100644 forge-game/src/main/java/forge/game/ability/effects/MustAttackEffect.java diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index a13a08f4e92..7a08a093c0c 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -58,6 +58,9 @@ import forge.util.Expressions; import forge.util.MyRandom; import forge.util.collect.FCollection; import forge.util.collect.FCollectionView; +import forge.util.maps.LinkedHashMapToAmount; +import forge.util.maps.MapToAmount; +import forge.util.maps.MapToAmountUtil; /** @@ -689,33 +692,18 @@ public class AiAttackController { } GameEntity prefDefender = defs.contains(defendingOpponent) ? defendingOpponent : defs.get(0); - // Attempt to see if there's a defined entity that must be attacked strictly this turn... - GameEntity entity = ai.getMustAttackEntityThisTurn(); - if (nextTurn || entity == null) { - // ...or during the attacking creature controller's turn - entity = ai.getMustAttackEntity(); + // 1. assault the opponent if you can kill him + if (bAssault) { + return prefDefender; } - if (null != entity) { - int n = defs.indexOf(entity); - if (-1 == n) { - System.out.println("getMustAttackEntity() or getMustAttackEntityThisTurn() returned something not in defenders."); - return prefDefender; - } - return entity; - } else { - // 1. assault the opponent if you can kill him - if (bAssault) { - return prefDefender; - } - // 2. attack planeswalkers - List pwDefending = c.getDefendingPlaneswalkers(); - if (!pwDefending.isEmpty()) { - final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending); - return pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending); - } else { - return prefDefender; - } + // 2. attack planeswalkers + List pwDefending = c.getDefendingPlaneswalkers(); + if (!pwDefending.isEmpty()) { + final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending); + return pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending); } + + return prefDefender; } final boolean LOG_AI_ATTACKS = false; @@ -786,30 +774,41 @@ public class AiAttackController { // because creatures not chosen can't attack. if (!nextTurn) { for (final Card attacker : this.attackers) { - boolean mustAttack = false; - // TODO this might result into trying to attack the wrong player + GameEntity mustAttackDef = null; if (attacker.isGoaded()) { - mustAttack = true; + // TODO this might result into trying to attack the wrong player + mustAttackDef = defender; } else if (attacker.getSVar("MustAttack").equals("True")) { - mustAttack = true; + mustAttackDef = defender; } else if (attacker.hasSVar("EndOfTurnLeavePlay") && isEffectiveAttacker(ai, attacker, combat, defender)) { - mustAttack = true; + mustAttackDef = defender; } else if (seasonOfTheWitch) { //TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack - mustAttack = true; + mustAttackDef = defender; } else { - final List e = StaticAbilityMustAttack.entitiesMustAttack(attacker); - if (!e.isEmpty()) { - mustAttack = true; - // TODO switch defender if there's one without a cost or it's not the specific player - } else if (attacker.getController().getMustAttackEntityThisTurn() != null && - CombatUtil.getAttackCost(ai.getGame(), attacker, defender) == null) { - mustAttack = true; + final List attackRequirements = StaticAbilityMustAttack.entitiesMustAttack(attacker); + if (attackRequirements.contains(attacker)) { + // TODO add cost check here and switch defender if there's one without a cost + // must attack anything + mustAttackDef = defender; + // next check if there's also a specific defender to attack, so don't count them + attackRequirements.removeAll(new CardCollection(attacker)); + } + final MapToAmount amounts = new LinkedHashMapToAmount<>(); + amounts.addAll(attackRequirements); + while (!amounts.isEmpty()) { + // check defenders in order of maximum requirements + GameEntity mustAttackDefMaybe = MapToAmountUtil.max(amounts).getKey(); + if (canAttackWrapper(attacker, mustAttackDefMaybe) && CombatUtil.getAttackCost(ai.getGame(), attacker, mustAttackDefMaybe) == null) { + mustAttackDef = mustAttackDefMaybe; + break; + } + amounts.remove(mustAttackDefMaybe); } } - if (mustAttack) { - combat.addAttacker(attacker, defender); + if (mustAttackDef != null) { + combat.addAttacker(attacker, mustAttackDef); attackersLeft.remove(attacker); numForcedAttackers++; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 5ce236749b1..73094a7d4e6 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -114,12 +114,11 @@ public class ComputerUtilCombat { return false; } - final List mustAttackEnts = StaticAbilityMustAttack.entitiesMustAttack(attacker); + final List mustAttack = StaticAbilityMustAttack.entitiesMustAttack(attacker); //if it contains only attacker, it only has a non-specific must attack - if (mustAttackEnts.size() > 1 || (mustAttackEnts.size() == 1 && mustAttackEnts.get(0) != attacker)) { - if (!mustAttackEnts.contains(defender)) { - return false; - } + mustAttack.removeAll(new CardCollection(attacker)); + if (!mustAttack.isEmpty() && !mustAttack.contains(defender)) { + return false; } // TODO this should be a factor but needs some alignment with AttachAi diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index cef2e20599d..796ad4fcf16 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -111,7 +111,6 @@ public enum SpellApiToAi { .put(ApiType.MoveCounter, CountersMoveAi.class) .put(ApiType.MultiplePiles, CannotPlayAi.class) .put(ApiType.MultiplyCounter, CountersMultiplyAi.class) - .put(ApiType.MustAttack, MustAttackAi.class) .put(ApiType.MustBlock, MustBlockAi.class) .put(ApiType.Mutate, MutateAi.class) .put(ApiType.NameCard, ChooseCardNameAi.class) diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java index f64d41366bf..dd2868cb976 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java @@ -1635,8 +1635,6 @@ public class AttachAi extends SpellAbilityAi { if (keyword.endsWith("CARDNAME can't attack.") || keyword.equals("Defender") || keyword.endsWith("CARDNAME can't attack or block.")) { return card.getNetCombatDamage() >= 1 && ComputerUtilCombat.canAttackNextTurn(card); - } else if (keyword.endsWith("CARDNAME attacks each turn if able.") || keyword.endsWith("CARDNAME attacks each combat if able.")) { - return !ai.getCreaturesInPlay().isEmpty() && ComputerUtilCombat.canAttackNextTurn(card) && CombatUtil.canBlock(card, true); } else if (keyword.endsWith("CARDNAME can't block.")) { return CombatUtil.canBlock(card, true); } else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) { diff --git a/forge-ai/src/main/java/forge/ai/ability/MustAttackAi.java b/forge-ai/src/main/java/forge/ai/ability/MustAttackAi.java deleted file mode 100644 index 000a25ded94..00000000000 --- a/forge-ai/src/main/java/forge/ai/ability/MustAttackAi.java +++ /dev/null @@ -1,35 +0,0 @@ -package forge.ai.ability; - - -import forge.ai.SpellAbilityAi; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; - -public class MustAttackAi extends SpellAbilityAi { - - @Override - protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { - // disabled for the AI for now. Only for Gideon Jura at this time. - return false; - } - - @Override - public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { - // AI should only activate this during Human's turn - // TODO - implement AI - return false; - } - - /* (non-Javadoc) - * @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean) - */ - @Override - protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { - boolean chance; - - // TODO - implement AI - chance = false; - - return chance; - } -} diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java index 0d88c421653..2d0fcd29403 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java @@ -166,10 +166,6 @@ public abstract class PumpAiBase extends SpellAbilityAi { return false; } return ph.isPlayerTurn(ai) || (combat != null && combat.isAttacking(card) && card.getNetCombatDamage() > 0); - } else if (keyword.endsWith("CARDNAME attacks each turn if able.") - || keyword.endsWith("CARDNAME attacks each combat if able.")) { - return !ph.isPlayerTurn(ai) && CombatUtil.canAttack(card, ai) && CombatUtil.canBeBlocked(card, ai) - && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS); } else if (keyword.endsWith("CARDNAME can't be regenerated.")) { if (card.getShieldCount() > 0) { return true; diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 63147953b58..60947347d98 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -575,7 +575,6 @@ public class GameAction { copied.setTapped(false); //untap card after it leaves the battlefield if needed game.fireEvent(new GameEventCardTapped(c, false)); } - copied.setMustAttackEntity(null); } // Need to apply any static effects to produce correct triggers 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 9c9684da385..ef64fc75305 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -112,7 +112,6 @@ public enum ApiType { MoveCounter (CountersMoveEffect.class), MultiplePiles (MultiplePilesEffect.class), MultiplyCounter (CountersMultiplyEffect.class), - MustAttack (MustAttackEffect.class), MustBlock (MustBlockEffect.class), Mutate (MutateEffect.class), NameCard (ChooseCardNameEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/MustAttackEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MustAttackEffect.java deleted file mode 100644 index cf5450c3c4b..00000000000 --- a/forge-game/src/main/java/forge/game/ability/effects/MustAttackEffect.java +++ /dev/null @@ -1,91 +0,0 @@ -package forge.game.ability.effects; - -import java.util.List; - -import forge.game.GameEntity; -import forge.game.ability.AbilityUtils; -import forge.game.ability.SpellAbilityEffect; -import forge.game.card.Card; -import forge.game.card.CardCollection; -import forge.game.player.Player; -import forge.game.player.PlayerCollection; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; - -public class MustAttackEffect extends SpellAbilityEffect { - - /* (non-Javadoc) - * @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility) - */ - @Override - protected String getStackDescription(SpellAbility sa) { - final Card host = sa.getHostCard(); - final StringBuilder sb = new StringBuilder(); - - // end standard pre- - - final List tgtPlayers = getTargetPlayers(sa); - - String defender = null; - if (sa.getParam("Defender").equals("Self")) { - defender = host.toString(); - } else { - defender = host.getController().toString(); - } - - for (final Player player : tgtPlayers) { - sb.append("Creatures ").append(player).append(" controls attack "); - sb.append(defender).append(" during their next turn."); - } - for (final Card c : getTargetCards(sa)) { - sb.append(c).append(" must attack "); - sb.append(defender).append(" during its controller's next turn if able."); - } - - return sb.toString(); - } - - @Override - public void resolve(SpellAbility sa) { - final List tgtPlayers = getTargetPlayers(sa); - final TargetRestrictions tgt = sa.getTargetRestrictions(); - final String defender = sa.getParam("Defender"); - final boolean thisTurn = sa.hasParam("ThisTurn"); - GameEntity entity = null; - if (defender.equals("Self")) { - entity = sa.getHostCard(); - } else { - PlayerCollection defPlayers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), defender, sa); - CardCollection defPWs = AbilityUtils.getDefinedCards(sa.getHostCard(), defender, sa); - if ((defPlayers.isEmpty() && defPWs.isEmpty()) || defPlayers.size() > 1 || defPWs.size() > 1) { - throw new RuntimeException("Illegal (nonexistent or not uniquely defined) defender " + defender + " for MustAttackEffect in card " + sa.getHostCard()); - } - if (!defPlayers.isEmpty()) { - entity = defPlayers.getFirst(); - } else if (!defPWs.isEmpty()) { - entity = defPWs.getFirst(); - } - } - - // TODO these should not override but add another requirement - for (final Player p : tgtPlayers) { - if ((tgt == null) || p.canBeTargetedBy(sa)) { - if (thisTurn || !p.getGame().getPhaseHandler().isPlayerTurn(p)) { - p.setMustAttackEntityThisTurn(entity); - } else { - p.setMustAttackEntity(entity); - } - } - } - for (final Card c : getTargetCards(sa)) { - if ((tgt == null) || c.canBeTargetedBy(sa)) { - if (thisTurn) { - c.setMustAttackEntityThisTurn(entity); - } else { - c.setMustAttackEntity(entity); - } - } - } - } // mustAttackResolve() - -} diff --git a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java index e988f073e19..f2361496188 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java @@ -72,7 +72,6 @@ public class RestartGameEffect extends SpellAbilityEffect { p.resetCompletedDungeons(); p.setBlessing(false); p.clearController(); - p.setMustAttackEntity(null); CardCollection newLibrary = new CardCollection(p.getCardsIn(restartZones, false)); List filteredCards = null; 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 e14b0ede6b8..f4058a6cd54 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -126,9 +126,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private GameEntity entityAttachedTo; - private GameEntity mustAttackEntity; - private GameEntity mustAttackEntityThisTurn; - private final Map mayPlay = Maps.newHashMap(); // changes by AF animate and continuous static effects @@ -1337,21 +1334,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { view.updateMustBlockCards(this); } - public final void setMustAttackEntity(final GameEntity e) { - mustAttackEntity = e; - } - public final GameEntity getMustAttackEntity() { - return mustAttackEntity; - } - public final void clearMustAttackEntity(final Player playerturn) { - if (getController().equals(playerturn)) { - mustAttackEntity = null; - } - mustAttackEntityThisTurn = mustAttackEntity; - } - public final GameEntity getMustAttackEntityThisTurn() { return mustAttackEntityThisTurn; } - public final void setMustAttackEntityThisTurn(GameEntity entThisTurn) { mustAttackEntityThisTurn = entThisTurn; } - public final Card getCloneOrigin() { return cloneOrigin; } @@ -6196,7 +6178,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { setRegeneratedThisTurn(0); resetShield(); setBecameTargetThisTurn(false); - clearMustAttackEntity(turn); clearMustBlockCards(); getDamageHistory().newTurn(); getDamageHistory().setCreatureAttackedLastTurnOf(turn, getDamageHistory().getCreatureAttackedThisTurn()); 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 55e8681241b..234453a2fa7 100644 --- a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java +++ b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java @@ -1,5 +1,6 @@ package forge.game.combat; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -37,12 +38,6 @@ public class AttackRequirement { this.causesToAttack = causesToAttack; - final GameEntity mustAttackThisTurn = attacker.getController().getMustAttackEntityThisTurn(); - // TODO check if this always illegal (e.g. Taunt cast on self) - if (mustAttackThisTurn != null) { - defenderSpecific.add(mustAttackThisTurn); - } - int nAttackAnything = 0; if (attacker.isGoaded()) { @@ -51,18 +46,11 @@ public class AttackRequirement { } //MustAttack static check - final List e = StaticAbilityMustAttack.entitiesMustAttack(attacker); - if (e.contains(attacker)) { - nAttackAnything++; - } else if (!e.isEmpty()) { - for (GameEntity mustAtt : e) { - defenderSpecific.add(mustAtt); - } - } - - final GameEntity mustAttackThisTurn3 = attacker.getMustAttackEntityThisTurn(); - if (mustAttackThisTurn3 != null) { - defenderSpecific.add(mustAttackThisTurn3); + final List mustAttack = StaticAbilityMustAttack.entitiesMustAttack(attacker); + nAttackAnything += Collections.frequency(mustAttack, attacker); + for (GameEntity e : mustAttack) { + if (e.equals(attacker)) continue; + defenderSpecific.add(e); } final Game game = attacker.getGame(); diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index 57dc7c26539..3c83da404bb 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -844,9 +844,6 @@ public class PhaseHandler implements java.io.Serializable { private Player handleNextTurn() { game.getStack().onNextTurn(); - // reset mustAttackEntity - playerTurn.setMustAttackEntityThisTurn(playerTurn.getMustAttackEntity()); - playerTurn.setMustAttackEntity(null); game.getTriggerHandler().clearThisTurnDelayedTrigger(); game.getTriggerHandler().resetTurnTriggerState(); diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 1de78be7d8c..6e5ce7ba96c 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -203,8 +203,6 @@ public class Player extends GameEntity implements Comparable { private Table changedKeywords = TreeBasedTable.create(); private ManaPool manaPool = new ManaPool(this); - private GameEntity mustAttackEntity; - private GameEntity mustAttackEntityThisTurn; private List creatureAttackedThisTurn = new ArrayList<>(); private boolean activateLoyaltyAbilityThisTurn = false; private boolean tappedLandForManaThisTurn = false; @@ -2296,22 +2294,6 @@ public class Player extends GameEntity implements Comparable { lifeLostLastTurn = n; } - /** - * get the Player object or Card (Planeswalker) object that this Player must - * attack this combat. - * - * @return the Player or Card (Planeswalker) - * @since 1.1.01 - */ - public final GameEntity getMustAttackEntity() { - return mustAttackEntity; - } - public final void setMustAttackEntity(final GameEntity o) { - mustAttackEntity = o; - } - public final GameEntity getMustAttackEntityThisTurn() { return mustAttackEntityThisTurn; } - public final void setMustAttackEntityThisTurn(GameEntity entThisTurn) { mustAttackEntityThisTurn = entThisTurn; } - @Override public int compareTo(Player o) { if (o == null) { diff --git a/forge-gui/res/lists/NonStackingKWList.txt b/forge-gui/res/lists/NonStackingKWList.txt index 1e9efe0a5fe..ec142eaa6ac 100644 --- a/forge-gui/res/lists/NonStackingKWList.txt +++ b/forge-gui/res/lists/NonStackingKWList.txt @@ -1,7 +1,6 @@ All creatures able to block CARDNAME do so. Banding CARDNAME's activated abilities can't be activated. -CARDNAME attacks each combat if able. CARDNAME blocks each combat if able. CARDNAME can attack as though it didn't have defender. CARDNAME can block any number of creatures.