mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
AI: fixed game freeze on cards with combat triggers (close #13342)
This commit is contained in:
parent
04f9ab75ba
commit
0a1bf47434
3 changed files with 78 additions and 32 deletions
|
|
@ -380,9 +380,13 @@ public class HumanPlayer extends PlayerImpl {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean canCallFeedback(Game game) {
|
||||
return !gameInCheckPlayableState(game) && !game.isSimulation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chooseMulligan(Game game) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -509,7 +513,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
@Override
|
||||
public int chooseReplacementEffect(Map<String, String> effectsMap, Map<String, MageObject> objectsMap, Game game) {
|
||||
if (gameInCheckPlayableState(game, true)) { // ignore warning logs until double call for TAPPED_FOR_MANA will be fix
|
||||
if (gameInCheckPlayableState(game, true) || game.isSimulation()) { // TODO: ignore warning logs until double call for TAPPED_FOR_MANA will be fix
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -615,7 +619,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
@Override
|
||||
public boolean choose(Outcome outcome, Choice choice, Game game) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -678,7 +682,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
@Override
|
||||
public boolean choose(Outcome outcome, Target target, Ability source, Game game, Map<String, Serializable> options) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -782,7 +786,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
@Override
|
||||
public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -862,7 +866,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
@Override
|
||||
public boolean choose(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -944,7 +948,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
// choose one or multiple target cards
|
||||
@Override
|
||||
public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1025,7 +1029,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
|
||||
// choose amount
|
||||
// human can choose or un-choose MULTIPLE targets at once
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1486,8 +1490,8 @@ public class HumanPlayer extends PlayerImpl {
|
|||
@Override
|
||||
public TriggeredAbility chooseTriggeredAbility(java.util.List<TriggeredAbility> abilities, Game game) {
|
||||
// choose triggered abilitity from list
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
return null;
|
||||
if (!canCallFeedback(game)) {
|
||||
return abilities.isEmpty() ? null : abilities.get(0);
|
||||
}
|
||||
|
||||
// automatically order triggers with same ability, rules text, and targets
|
||||
|
|
@ -1615,7 +1619,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
protected boolean playManaHandling(Ability abilityToCast, ManaCost unpaid, String promptText, Game game) {
|
||||
// choose mana to pay (from permanents or from pool)
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1661,7 +1665,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
* @return
|
||||
*/
|
||||
public int announceRepetitions(Game game) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1694,8 +1698,8 @@ public class HumanPlayer extends PlayerImpl {
|
|||
*/
|
||||
@Override
|
||||
public int announceXMana(int min, int max, String message, Game game, Ability ability) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
return 0;
|
||||
if (!canCallFeedback(game)) {
|
||||
return min;
|
||||
}
|
||||
|
||||
int xValue = 0;
|
||||
|
|
@ -1721,8 +1725,8 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
@Override
|
||||
public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
return 0;
|
||||
if (!canCallFeedback(game)) {
|
||||
return min;
|
||||
}
|
||||
|
||||
int xValue = 0;
|
||||
|
|
@ -1794,7 +1798,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
@Override
|
||||
public void selectAttackers(Game game, UUID attackingPlayerId) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2064,7 +2068,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
@Override
|
||||
public void selectBlockers(Ability source, Game game, UUID defendingPlayerId) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2126,7 +2130,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
}
|
||||
|
||||
protected void selectCombatGroup(UUID defenderId, UUID blockerId, Game game) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return;
|
||||
}
|
||||
TargetAttackingCreature target = new TargetAttackingCreature();
|
||||
|
|
@ -2180,8 +2184,8 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
@Override
|
||||
public int getAmount(int min, int max, String message, Game game) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
return 0;
|
||||
if (!canCallFeedback(game)) {
|
||||
return min;
|
||||
}
|
||||
|
||||
while (canRespond()) {
|
||||
|
|
@ -2220,7 +2224,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
return defaultList;
|
||||
}
|
||||
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return defaultList;
|
||||
}
|
||||
|
||||
|
|
@ -2288,7 +2292,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
* @param unpaidForManaAction - set unpaid for mana actions like convoke
|
||||
*/
|
||||
protected void activateSpecialAction(Game game, ManaCost unpaidForManaAction) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2319,7 +2323,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
}
|
||||
|
||||
protected void activateAbility(Map<UUID, ? extends ActivatedAbility> abilities, MageObject object, Game game) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2404,7 +2408,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
@Override
|
||||
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -2444,7 +2448,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
@Override
|
||||
public ActivatedAbility chooseLandOrSpellAbility(Card card, Game game, boolean noMana) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -2491,7 +2495,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
@Override
|
||||
public Mode chooseMode(Modes modes, Ability source, Game game) {
|
||||
// choose mode to activate
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -2619,7 +2623,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
@Override
|
||||
public boolean choosePile(Outcome outcome, String message, java.util.List<? extends Card> pile1, java.util.List<? extends Card> pile2, Game game) {
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
if (!canCallFeedback(game)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ package mage.cards.f;
|
|||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.keyword.MenaceAbility;
|
||||
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
|
|
@ -26,8 +26,7 @@ public class FurnacePunisher extends CardImpl {
|
|||
// Menace
|
||||
this.addAbility(new MenaceAbility(false));
|
||||
|
||||
//At the beginning of each player’s upkeep, Furnace Punisher deals 2 damage to that player unless they control
|
||||
//two or more basic lands.
|
||||
// At the beginning of each player’s upkeep, Furnace Punisher deals 2 damage to that player unless they control two or more basic lands.
|
||||
this.addAbility(new BeginningOfUpkeepTriggeredAbility(TargetController.EACH_PLAYER, new FurnacePunisherEffect(),
|
||||
false
|
||||
).setTriggerPhrase("At the beginning of each player's upkeep, "));
|
||||
|
|
|
|||
|
|
@ -309,4 +309,47 @@ public class BlockSimulationAITest extends CardTestPlayerBaseWithAIHelps {
|
|||
assertGraveyardCount(playerA, "Balduvian Bears", 1);
|
||||
assertDamageReceived(playerB, "Graveblade Marauder", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Block_deathtouch_attacker_vs_menace() {
|
||||
// possible bug: AI freeze, see https://github.com/magefree/mage/issues/13342
|
||||
// it's only HumanPlayer related and can't be tested here
|
||||
|
||||
// First strike, Deathtouch
|
||||
// Whenever Glissa Sunslayer deals combat damage to a player, choose one —
|
||||
// • You draw a card and you lose 1 life.
|
||||
// • Destroy target enchantment.
|
||||
// • Remove up to three counters from target permanent.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Glissa Sunslayer", 1); // 3/3
|
||||
// Deathtouch
|
||||
// Whenever a creature you control with deathtouch deals combat damage to a player, that player gets two poison counters.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Fynn, the Fangbearer", 1); // 1/3
|
||||
// Deathtouch
|
||||
// Toxic 1 (Players dealt combat damage by this creature also get a poison counter.)
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Bilious Skulldweller", 1); // 1/1
|
||||
//
|
||||
// Menace
|
||||
// At the beginning of each player’s upkeep, Furnace Punisher deals 2 damage to that player unless they control
|
||||
// two or more basic lands.
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Furnace Punisher", 1); // 3/3
|
||||
|
||||
attack(1, playerA, "Glissa Sunslayer");
|
||||
attack(1, playerA, "Fynn, the Fangbearer");
|
||||
attack(1, playerA, "Bilious Skulldweller");
|
||||
setChoice(playerA, "Whenever a creature you control", 2); // x3 triggers
|
||||
setModeChoice(playerA, "1"); // you draw a card and you lose 1 life
|
||||
|
||||
// ai must not block attacker with Deathtouch
|
||||
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
|
||||
checkBlockers("no blockers", 1, playerB, "");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20 - 1 - 2); // from draw, from furnace damage
|
||||
assertLife(playerB, 20 - 3 - 1 - 1);
|
||||
assertPermanentCount(playerA, "Glissa Sunslayer", 1);
|
||||
assertGraveyardCount(playerB, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue