mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
Goodbye MustAttackEffect
This commit is contained in:
@@ -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++;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.")) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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()
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user