Goodbye MustAttackEffect

This commit is contained in:
tool4EvEr
2022-04-24 14:01:59 +02:00
parent 6ff64a831d
commit ce34bebc52
15 changed files with 49 additions and 240 deletions

View File

@@ -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,20 +692,6 @@ 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();
}
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;
@@ -712,11 +701,10 @@ public class AiAttackController {
if (!pwDefending.isEmpty()) {
final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
return pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
} else {
}
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<GameEntity> 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<GameEntity> 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<GameEntity> 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++;
}

View File

@@ -114,13 +114,12 @@ public class ComputerUtilCombat {
return false;
}
final List<GameEntity> mustAttackEnts = StaticAbilityMustAttack.entitiesMustAttack(attacker);
final List<GameEntity> 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)) {
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
//boolean leavesPlay = !ComputerUtilCard.hasActiveUndyingOrPersist(attacker)

View File

@@ -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)

View File

@@ -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.")) {

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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),

View File

@@ -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<Player> 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<Player> 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()
}

View File

@@ -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<Card> filteredCards = null;

View File

@@ -126,9 +126,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private GameEntity entityAttachedTo;
private GameEntity mustAttackEntity;
private GameEntity mustAttackEntityThisTurn;
private final Map<StaticAbility, CardPlayOption> mayPlay = Maps.newHashMap();
// changes by AF animate and continuous static effects
@@ -1337,21 +1334,6 @@ public class Card extends GameEntity implements Comparable<Card>, 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<Card>, IHasSVars {
setRegeneratedThisTurn(0);
resetShield();
setBecameTargetThisTurn(false);
clearMustAttackEntity(turn);
clearMustBlockCards();
getDamageHistory().newTurn();
getDamageHistory().setCreatureAttackedLastTurnOf(turn, getDamageHistory().getCreatureAttackedThisTurn());

View File

@@ -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<GameEntity> 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<GameEntity> 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();

View File

@@ -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();

View File

@@ -203,8 +203,6 @@ public class Player extends GameEntity implements Comparable<Player> {
private Table<Long, Long, KeywordsChange> changedKeywords = TreeBasedTable.create();
private ManaPool manaPool = new ManaPool(this);
private GameEntity mustAttackEntity;
private GameEntity mustAttackEntityThisTurn;
private List<Card> creatureAttackedThisTurn = new ArrayList<>();
private boolean activateLoyaltyAbilityThisTurn = false;
private boolean tappedLandForManaThisTurn = false;
@@ -2296,22 +2294,6 @@ public class Player extends GameEntity implements Comparable<Player> {
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) {

View File

@@ -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.