mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 18:58:00 +00:00
Merge branch 'simulation' into 'master'
Simulation fixes! See merge request core-developers/forge!5890
This commit is contained in:
@@ -358,7 +358,7 @@ public class AiAttackController {
|
||||
opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return ComputerUtilCombat.canAttackNextTurn(c) && c.getNetCombatDamage() > 0;
|
||||
return c.getNetCombatDamage() > 0 && ComputerUtilCombat.canAttackNextTurn(c);
|
||||
}
|
||||
});
|
||||
for (final Card c : this.myList) {
|
||||
@@ -422,7 +422,7 @@ public class AiAttackController {
|
||||
int humanBasePower = ComputerUtilCombat.getAttack(this.oppList.get(0)) + humanExaltedBonus;
|
||||
if (finestHour) {
|
||||
// For Finest Hour, one creature could attack and get the bonus TWICE
|
||||
humanBasePower = humanBasePower + humanExaltedBonus;
|
||||
humanBasePower += humanExaltedBonus;
|
||||
}
|
||||
final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBasePower
|
||||
: humanBasePower;
|
||||
@@ -470,7 +470,7 @@ public class AiAttackController {
|
||||
if (totalAttack > 0 && ai.getLife() <= totalAttack && !ai.cantLoseForZeroOrLessLife()) {
|
||||
return true;
|
||||
}
|
||||
return ai.canReceiveCounters(CounterEnumType.POISON) && ai.getPoisonCounters() + totalPoison > 9;
|
||||
return ai.getPoisonCounters() + totalPoison > 9 && ai.canReceiveCounters(CounterEnumType.POISON);
|
||||
}
|
||||
|
||||
private boolean doAssault(final Player ai) {
|
||||
@@ -672,9 +672,9 @@ public class AiAttackController {
|
||||
*
|
||||
* @return a {@link forge.game.combat.Combat} object.
|
||||
*/
|
||||
public final void declareAttackers(final Combat combat) {
|
||||
public final int declareAttackers(final Combat combat) {
|
||||
if (this.attackers.isEmpty()) {
|
||||
return;
|
||||
return aiAggression;
|
||||
}
|
||||
|
||||
// Aggro options
|
||||
@@ -686,6 +686,7 @@ public class AiAttackController {
|
||||
boolean predictEvasion = false;
|
||||
if (ai.getController().isAI()) {
|
||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||
if (!aic.usesSimulation()) {
|
||||
playAggro = aic.getBooleanProperty(AiProps.PLAY_AGGRO);
|
||||
chanceToAttackToTrade = aic.getIntProperty(AiProps.CHANCE_TO_ATTACK_INTO_TRADE);
|
||||
tradeIfTappedOut = aic.getBooleanProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT);
|
||||
@@ -693,6 +694,7 @@ public class AiAttackController {
|
||||
tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
|
||||
predictEvasion = aic.getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION);
|
||||
}
|
||||
}
|
||||
|
||||
final boolean bAssault = doAssault(ai);
|
||||
// TODO: detect Lightmine Field by presence of a card with a specific trigger
|
||||
@@ -714,7 +716,7 @@ public class AiAttackController {
|
||||
|
||||
if (attackMax == 0) {
|
||||
// can't attack anymore
|
||||
return;
|
||||
return aiAggression;
|
||||
}
|
||||
|
||||
// Attackers that don't really have a choice
|
||||
@@ -751,7 +753,7 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
if (attackersLeft.isEmpty()) {
|
||||
return;
|
||||
return aiAggression;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -761,7 +763,7 @@ public class AiAttackController {
|
||||
}
|
||||
// Revenge of Ravens: make sure the AI doesn't kill itself and doesn't damage itself unnecessarily
|
||||
if (!doRevengeOfRavensAttackLogic(ai, defender, attackersLeft, numForcedAttackers, attackMax)) {
|
||||
return;
|
||||
return aiAggression;
|
||||
}
|
||||
|
||||
if (bAssault && defender == this.defendingOpponent) { // in case we are forced to attack someone else
|
||||
@@ -771,14 +773,14 @@ public class AiAttackController {
|
||||
for (Card attacker : attackersLeft) {
|
||||
// reached max, breakup
|
||||
if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
|
||||
return;
|
||||
return aiAggression;
|
||||
|
||||
if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat, defender)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
}
|
||||
}
|
||||
// no more creatures to attack
|
||||
return;
|
||||
return aiAggression;
|
||||
}
|
||||
|
||||
// Cards that are remembered to attack anyway (e.g. temporarily stolen creatures)
|
||||
@@ -816,7 +818,7 @@ public class AiAttackController {
|
||||
for (Card attacker : this.attackers) {
|
||||
if (canAttackWrapper(attacker, defender) && shouldAttack(ai, attacker, this.blockers, combat, defender)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
return;
|
||||
return aiAggression;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -835,7 +837,7 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
// no more creatures to attack
|
||||
return;
|
||||
return aiAggression;
|
||||
}
|
||||
|
||||
// *******************
|
||||
@@ -877,7 +879,7 @@ public class AiAttackController {
|
||||
|
||||
for (final Card pCard : categorizedOppList) {
|
||||
// if the creature can attack next turn add it to counter attackers list
|
||||
if (ComputerUtilCombat.canAttackNextTurn(pCard) && pCard.getNetCombatDamage() > 0) {
|
||||
if (pCard.getNetCombatDamage() > 0 && ComputerUtilCombat.canAttackNextTurn(pCard)) {
|
||||
nextTurnAttackers.add(pCard);
|
||||
candidateCounterAttackDamage += pCard.getNetCombatDamage();
|
||||
humanForces += 1; // player forces they might use to attack
|
||||
@@ -1108,6 +1110,8 @@ public class AiAttackController {
|
||||
defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
|
||||
}
|
||||
}
|
||||
|
||||
return aiAggression;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1133,8 +1137,7 @@ public class AiAttackController {
|
||||
int numberOfPossibleBlockers = 0;
|
||||
|
||||
// Is it a creature that has a more valuable ability with a tap cost than what it can do by attacking?
|
||||
if ((attacker.hasSVar("NonCombatPriority"))
|
||||
&& (!attacker.hasKeyword(Keyword.VIGILANCE))) {
|
||||
if (attacker.hasSVar("NonCombatPriority") && !attacker.hasKeyword(Keyword.VIGILANCE)) {
|
||||
// For each level of priority, enemy has to have life as much as the creature's power
|
||||
// so a priority of 4 means the creature will not attack unless it can defeat that player in 4 successful attacks.
|
||||
// the lower the priroity, the less willing the AI is to use the creature for attacking.
|
||||
@@ -1159,8 +1162,7 @@ public class AiAttackController {
|
||||
}
|
||||
boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator");
|
||||
// is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...)
|
||||
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
|
||||
|| "Blocked".equals(attacker.getSVar("HasAttackEffect"));
|
||||
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE") || "Blocked".equals(attacker.getSVar("HasAttackEffect"));
|
||||
|
||||
// contains only the defender's blockers that can actually block the attacker
|
||||
CardCollection validBlockers = CardLists.filter(defenders, new Predicate<Card>() {
|
||||
@@ -1318,7 +1320,7 @@ public class AiAttackController {
|
||||
return false; // don't attack
|
||||
}
|
||||
|
||||
public static List<Card> exertAttackers(List<Card> attackers) {
|
||||
public static List<Card> exertAttackers(List<Card> attackers, int aggression) {
|
||||
List<Card> exerters = Lists.newArrayList();
|
||||
for (Card c : attackers) {
|
||||
boolean shouldExert = false;
|
||||
@@ -1362,9 +1364,14 @@ public class AiAttackController {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!shouldExert) {
|
||||
// TODO Improve when the AI wants to use Exert powers
|
||||
shouldExert = aggression > 3;
|
||||
}
|
||||
|
||||
// A specific AI condition for Exert: if specified on the card, the AI will always
|
||||
// exert creatures that meet this condition
|
||||
if (c.hasSVar("AIExertCondition")) {
|
||||
if (!shouldExert && c.hasSVar("AIExertCondition")) {
|
||||
if (!c.getSVar("AIExertCondition").isEmpty()) {
|
||||
final String needsToExert = c.getSVar("AIExertCondition");
|
||||
String sVar = needsToExert.split(" ")[0];
|
||||
@@ -1379,11 +1386,6 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldExert && MyRandom.getRandom().nextBoolean()) {
|
||||
// TODO Improve when the AI wants to use Exert powers
|
||||
shouldExert = true;
|
||||
}
|
||||
|
||||
if (shouldExert) {
|
||||
exerters.add(c);
|
||||
}
|
||||
|
||||
@@ -457,7 +457,7 @@ public class AiBlockController {
|
||||
return false;
|
||||
}
|
||||
final boolean randomTrade = wouldLikeToRandomlyTrade(attacker, c, combat);
|
||||
return lifeInDanger || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker) || randomTrade;
|
||||
return lifeInDanger || randomTrade || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker);
|
||||
}
|
||||
});
|
||||
if (usableBlockers.size() < 2) {
|
||||
@@ -854,7 +854,7 @@ public class AiBlockController {
|
||||
}
|
||||
|
||||
private void makeChumpBlocksToSavePW(Combat combat) {
|
||||
if (ComputerUtilCombat.lifeInDanger(ai, combat) || ai.getLife() <= ai.getStartingLife() / 5) {
|
||||
if (ai.getLife() <= ai.getStartingLife() / 5 || ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
// most likely not worth trying to protect planeswalkers when at threateningly low life or in
|
||||
// dangerous combat which threatens lethal or severe damage to face
|
||||
return;
|
||||
@@ -1145,7 +1145,7 @@ public class AiBlockController {
|
||||
|
||||
// check to see if it's possible to defend a Planeswalker under attack with a chump block,
|
||||
// unless life is low enough to be more worried about saving preserving the life total
|
||||
if (ai.getController().isAI() && !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
if (ai.getController().isAI()) {
|
||||
makeChumpBlocksToSavePW(combat);
|
||||
}
|
||||
|
||||
@@ -1206,7 +1206,7 @@ public class AiBlockController {
|
||||
final CardCollection result = new CardCollection();
|
||||
boolean newBlockerIsAdded = false;
|
||||
// The new blocker comes right after this one
|
||||
final Card newBlockerRightAfter = (newBlockerIndex == 0 ? null : allBlockers.get(newBlockerIndex - 1));
|
||||
final Card newBlockerRightAfter = newBlockerIndex == 0 ? null : allBlockers.get(newBlockerIndex - 1);
|
||||
if (newBlockerRightAfter == null
|
||||
&& damage >= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
|
||||
result.add(blocker);
|
||||
@@ -1274,6 +1274,8 @@ public class AiBlockController {
|
||||
int oppCreatureCount = 0;
|
||||
if (ai.getController().isAI()) {
|
||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||
// simulation must get same results or it may crash
|
||||
if (!aic.usesSimulation()) {
|
||||
enableRandomTrades = aic.getBooleanProperty(AiProps.ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK);
|
||||
randomTradeIfBehindOnBoard = aic.getBooleanProperty(AiProps.RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS);
|
||||
randomTradeIfCreatInHand = aic.getBooleanProperty(AiProps.ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT);
|
||||
@@ -1285,6 +1287,7 @@ public class AiBlockController {
|
||||
chanceToTradeToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_TO_SAVE_PLANESWALKER);
|
||||
chanceToTradeDownToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_DOWN_TO_SAVE_PLANESWALKER);
|
||||
}
|
||||
}
|
||||
|
||||
if (!enableRandomTrades) {
|
||||
return false;
|
||||
|
||||
@@ -88,6 +88,7 @@ public class AiController {
|
||||
private boolean cheatShuffle;
|
||||
private boolean useSimulation;
|
||||
private SpellAbilityPicker simPicker;
|
||||
private int lastAttackAggression;
|
||||
|
||||
public boolean canCheatShuffle() {
|
||||
return cheatShuffle;
|
||||
@@ -104,6 +105,10 @@ public class AiController {
|
||||
this.useSimulation = value;
|
||||
}
|
||||
|
||||
public int getAttackAggression() {
|
||||
return lastAttackAggression;
|
||||
}
|
||||
|
||||
public SpellAbilityPicker getSimulationPicker() {
|
||||
return simPicker;
|
||||
}
|
||||
@@ -1445,7 +1450,7 @@ public class AiController {
|
||||
public void declareAttackers(Player attacker, Combat combat) {
|
||||
// 12/2/10(sol) the decision making here has moved to getAttackers()
|
||||
AiAttackController aiAtk = new AiAttackController(attacker);
|
||||
aiAtk.declareAttackers(combat);
|
||||
lastAttackAggression = aiAtk.declareAttackers(combat);
|
||||
|
||||
// if invalid: just try an attack declaration that we know to be legal
|
||||
if (!CombatUtil.validateAttackers(combat)) {
|
||||
|
||||
@@ -1483,6 +1483,7 @@ public class ComputerUtil {
|
||||
if (sa.getApi() != ApiType.DealDamage) {
|
||||
continue;
|
||||
}
|
||||
sa.setActivatingPlayer(ai);
|
||||
final String numDam = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), numDam, sa);
|
||||
if (dmg <= damage) {
|
||||
|
||||
@@ -264,6 +264,7 @@ public class ComputerUtilCard {
|
||||
return ut;
|
||||
}
|
||||
|
||||
// TODO potentially risky if simulation mode currently able to reach this from triggers
|
||||
return Aggregates.random(bLand); // random tapped land of least represented type
|
||||
}
|
||||
|
||||
@@ -743,7 +744,7 @@ public class ComputerUtilCard {
|
||||
final String name = c.getName();
|
||||
Integer currentCnt = map.get(name);
|
||||
map.put(name, currentCnt == null ? Integer.valueOf(1) : Integer.valueOf(1 + currentCnt));
|
||||
} // for
|
||||
}
|
||||
|
||||
int max = 0;
|
||||
String maxName = "";
|
||||
@@ -1296,12 +1297,16 @@ public class ComputerUtilCard {
|
||||
boolean combatTrick = false;
|
||||
boolean holdCombatTricks = false;
|
||||
int chanceToHoldCombatTricks = -1;
|
||||
boolean simAI = false;
|
||||
|
||||
if (ai.getController().isAI()) {
|
||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||
simAI = aic.usesSimulation();
|
||||
if (!simAI) {
|
||||
holdCombatTricks = aic.getBooleanProperty(AiProps.TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK);
|
||||
chanceToHoldCombatTricks = aic.getIntProperty(AiProps.CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK);
|
||||
}
|
||||
}
|
||||
|
||||
if (!c.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
@@ -1629,7 +1634,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
}
|
||||
|
||||
return MyRandom.getRandom().nextFloat() < chance;
|
||||
return simAI || MyRandom.getRandom().nextFloat() < chance;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -160,7 +160,7 @@ public class ComputerUtilCombat {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return CombatUtil.canBlock(att, c) && (c.hasFirstStrike() || c.hasDoubleStrike());
|
||||
return (c.hasFirstStrike() || c.hasDoubleStrike()) && CombatUtil.canBlock(att, c);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -324,12 +324,12 @@ public class ComputerUtilCombat {
|
||||
for (final Card attacker : attackers) {
|
||||
final List<Card> blockers = combat.getBlockers(attacker);
|
||||
|
||||
if ((blockers.size() == 0)
|
||||
if (blockers.size() == 0
|
||||
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage "
|
||||
+ "as though it weren't blocked.")) {
|
||||
unblocked.add(attacker);
|
||||
} else if (attacker.hasKeyword(Keyword.TRAMPLE)
|
||||
&& (getAttack(attacker) > totalShieldDamage(attacker, blockers))) {
|
||||
&& getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
|
||||
if (!attacker.hasKeyword(Keyword.INFECT)) {
|
||||
damage += getAttack(attacker) - totalShieldDamage(attacker, blockers);
|
||||
}
|
||||
@@ -366,12 +366,12 @@ public class ComputerUtilCombat {
|
||||
for (final Card attacker : attackers) {
|
||||
final List<Card> blockers = combat.getBlockers(attacker);
|
||||
|
||||
if ((blockers.size() == 0)
|
||||
if (blockers.size() == 0
|
||||
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage"
|
||||
+ " as though it weren't blocked.")) {
|
||||
unblocked.add(attacker);
|
||||
} else if (attacker.hasKeyword(Keyword.TRAMPLE)
|
||||
&& (getAttack(attacker) > totalShieldDamage(attacker, blockers))) {
|
||||
&& getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
|
||||
int trampleDamage = getAttack(attacker) - totalShieldDamage(attacker, blockers);
|
||||
if (attacker.hasKeyword(Keyword.INFECT)) {
|
||||
poison += trampleDamage;
|
||||
@@ -617,7 +617,7 @@ public class ComputerUtilCombat {
|
||||
if (flankingMagnitude >= defender.getNetToughness()) {
|
||||
return 0;
|
||||
}
|
||||
if (flankingMagnitude >= (defender.getNetToughness() - defender.getDamage())
|
||||
if (flankingMagnitude >= defender.getNetToughness() - defender.getDamage()
|
||||
&& !defender.hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -1660,7 +1660,7 @@ public class ComputerUtilCombat {
|
||||
*/
|
||||
public static boolean combatantCantBeDestroyed(Player ai, final Card combatant) {
|
||||
// either indestructible or may regenerate
|
||||
if (combatant.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, combatant))) {
|
||||
if (combatant.hasKeyword(Keyword.INDESTRUCTIBLE) || ComputerUtil.canRegenerate(ai, combatant)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1715,7 +1715,7 @@ public class ComputerUtilCombat {
|
||||
if (flankingMagnitude >= blocker.getNetToughness()) {
|
||||
return false;
|
||||
}
|
||||
if ((flankingMagnitude >= (blocker.getNetToughness() - blocker.getDamage()))
|
||||
if (flankingMagnitude >= blocker.getNetToughness() - blocker.getDamage()
|
||||
&& !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
@Override
|
||||
public List<Card> exertAttackers(List<Card> attackers) {
|
||||
return AiAttackController.exertAttackers(attackers);
|
||||
return AiAttackController.exertAttackers(attackers, brains.getAttackAggression());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -916,7 +916,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
prefList = CardLists.filter(prefList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return ComputerUtilCombat.canAttackNextTurn(c) && c.getNetPower() > 0;
|
||||
return c.getNetPower() > 0 && ComputerUtilCombat.canAttackNextTurn(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1278,7 +1278,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if (!c.isCreature()) {
|
||||
return true;
|
||||
}
|
||||
return ComputerUtilCombat.canAttackNextTurn(c) && powerBonus + c.getNetPower() > 0;
|
||||
return powerBonus + c.getNetPower() > 0 && ComputerUtilCombat.canAttackNextTurn(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1588,8 +1588,8 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
if (evasive) {
|
||||
return card.getNetCombatDamage() + powerBonus > 0
|
||||
&& ComputerUtilCombat.canAttackNextTurn(card)
|
||||
&& canBeBlocked;
|
||||
&& canBeBlocked
|
||||
&& ComputerUtilCombat.canAttackNextTurn(card);
|
||||
} else if (keyword.equals("Haste")) {
|
||||
return card.hasSickness() && ph.isPlayerTurn(sa.getActivatingPlayer()) && !card.isTapped()
|
||||
&& card.getNetCombatDamage() + powerBonus > 0
|
||||
@@ -1609,8 +1609,8 @@ public class AttachAi extends SpellAbilityAi {
|
||||
&& (ComputerUtilCombat.canAttackNextTurn(card) || CombatUtil.canBlock(card, true));
|
||||
} else if (keyword.startsWith("Flanking")) {
|
||||
return card.getNetCombatDamage() + powerBonus > 0
|
||||
&& ComputerUtilCombat.canAttackNextTurn(card)
|
||||
&& canBeBlocked;
|
||||
&& canBeBlocked
|
||||
&& ComputerUtilCombat.canAttackNextTurn(card);
|
||||
} else if (keyword.startsWith("Bushido")) {
|
||||
return (canBeBlocked && ComputerUtilCombat.canAttackNextTurn(card))
|
||||
|| CombatUtil.canBlock(card, true);
|
||||
@@ -1652,9 +1652,9 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
if (keyword.endsWith("CARDNAME can't attack.") || keyword.equals("Defender")
|
||||
|| keyword.endsWith("CARDNAME can't attack or block.")) {
|
||||
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
|
||||
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 ComputerUtilCombat.canAttackNextTurn(card) && CombatUtil.canBlock(card, true) && !ai.getCreaturesInPlay().isEmpty();
|
||||
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.")) {
|
||||
@@ -1665,10 +1665,10 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
return false;
|
||||
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")) {
|
||||
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
|
||||
return card.getNetCombatDamage() >= 1 && ComputerUtilCombat.canAttackNextTurn(card);
|
||||
} else if (keyword.endsWith("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
||||
|| keyword.endsWith("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
||||
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 2;
|
||||
return card.getNetCombatDamage() >= 2 && ComputerUtilCombat.canAttackNextTurn(card);
|
||||
} else if (keyword.endsWith("CARDNAME doesn't untap during your untap step.")) {
|
||||
return !card.isUntapped();
|
||||
}
|
||||
|
||||
@@ -8,8 +8,11 @@ import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import forge.LobbyPlayer;
|
||||
import forge.ai.AIOption;
|
||||
import forge.ai.LobbyPlayerAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
@@ -64,9 +67,9 @@ public class GameCopier {
|
||||
}
|
||||
|
||||
public Game makeCopy() {
|
||||
return makeCopy(null);
|
||||
return makeCopy(null, null);
|
||||
}
|
||||
public Game makeCopy(PhaseType advanceToPhase) {
|
||||
public Game makeCopy(PhaseType advanceToPhase, Player aiPlayer) {
|
||||
List<RegisteredPlayer> origPlayers = origGame.getMatch().getPlayers();
|
||||
List<RegisteredPlayer> newPlayers = new ArrayList<>();
|
||||
for (RegisteredPlayer p : origPlayers) {
|
||||
@@ -82,8 +85,7 @@ public class GameCopier {
|
||||
newPlayer.setActivateLoyaltyAbilityThisTurn(origPlayer.getActivateLoyaltyAbilityThisTurn());
|
||||
for (int j = 0; j < origPlayer.getSpellsCastThisTurn(); j++)
|
||||
newPlayer.addSpellCastThisTurn();
|
||||
for (int j = 0; j < origPlayer.getLandsPlayedThisTurn(); j++)
|
||||
newPlayer.addLandPlayedThisTurn();
|
||||
newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn());
|
||||
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
|
||||
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
|
||||
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
|
||||
@@ -138,18 +140,25 @@ public class GameCopier {
|
||||
effect.removeMapped(gameObjectMap);
|
||||
}
|
||||
|
||||
if (origPhaseHandler.getCombat() != null) {
|
||||
newGame.getPhaseHandler().setCombat(new Combat(origPhaseHandler.getCombat(), gameObjectMap));
|
||||
}
|
||||
|
||||
newGame.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
|
||||
newGame.getTriggerHandler().resetActiveTriggers();
|
||||
|
||||
if (GameSimulator.COPY_STACK)
|
||||
copyStack(origGame, newGame, gameObjectMap);
|
||||
|
||||
if (origPhaseHandler.getCombat() != null) {
|
||||
newGame.getPhaseHandler().setCombat(new Combat(origPhaseHandler.getCombat(), gameObjectMap));
|
||||
}
|
||||
// TODO update thisTurnCast
|
||||
|
||||
if (advanceToPhase != null) {
|
||||
newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase);
|
||||
newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GameSimulator.resolveStack(newGame, aiPlayer.getWeakestOpponent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return newGame;
|
||||
@@ -185,7 +194,8 @@ public class GameCopier {
|
||||
RegisteredPlayer clone = new RegisteredPlayer(p.getDeck());
|
||||
LobbyPlayer lp = p.getPlayer();
|
||||
if (!(lp instanceof LobbyPlayerAi)) {
|
||||
lp = new LobbyPlayerAi(p.getPlayer().getName(), null);
|
||||
// TODO should probably also override them if they're normal AI
|
||||
lp = new LobbyPlayerAi(p.getPlayer().getName(), Sets.newHashSet(AIOption.USE_SIMULATION));
|
||||
}
|
||||
clone.setPlayer(lp);
|
||||
return clone;
|
||||
@@ -197,6 +207,7 @@ public class GameCopier {
|
||||
for (Card card : origGame.getCardsIn(zone)) {
|
||||
addCard(newGame, zone, card);
|
||||
}
|
||||
// TODO CardsAddedThisTurn is now messed up
|
||||
}
|
||||
gameObjectMap = new CopiedGameObjectMap(newGame);
|
||||
|
||||
@@ -271,11 +282,14 @@ public class GameCopier {
|
||||
cardMap.put(c, newCard);
|
||||
|
||||
Player zoneOwner = owner;
|
||||
// everything the CreatureEvaluator checks must be set here
|
||||
if (zone == ZoneType.Battlefield) {
|
||||
// TODO: Controllers' list with timestamps should be copied.
|
||||
zoneOwner = playerMap.get(c.getController());
|
||||
newCard.setController(zoneOwner, 0);
|
||||
|
||||
newCard.setCameUnderControlSinceLastUpkeep(c.cameUnderControlSinceLastUpkeep());
|
||||
|
||||
newCard.setPTTable(c.getSetPTTable());
|
||||
newCard.setPTCharacterDefiningTable(c.getSetPTCharacterDefiningTable());
|
||||
|
||||
@@ -290,9 +304,11 @@ public class GameCopier {
|
||||
newCard.setChangedCardKeywords(c.getChangedCardKeywords());
|
||||
newCard.setChangedCardNames(c.getChangedCardNames());
|
||||
|
||||
// TODO: Is this correct? Does it not duplicate keywords from enchantments and such?
|
||||
//for (KeywordInterface kw : c.getHiddenExtrinsicKeywords())
|
||||
// newCard.addHiddenExtrinsicKeyword(kw);
|
||||
for (Table.Cell<Long, Long, List<String>> kw : c.getHiddenExtrinsicKeywordsTable().cellSet()) {
|
||||
newCard.addHiddenExtrinsicKeywords(kw.getRowKey(), kw.getColumnKey(), kw.getValue());
|
||||
}
|
||||
newCard.updateKeywordsCache(newCard.getCurrentState());
|
||||
|
||||
if (c.isTapped()) {
|
||||
newCard.setTapped(true);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class GameSimulator {
|
||||
public GameSimulator(SimulationController controller, Game origGame, Player origAiPlayer, PhaseType advanceToPhase) {
|
||||
this.controller = controller;
|
||||
copier = new GameCopier(origGame);
|
||||
simGame = copier.makeCopy(advanceToPhase);
|
||||
simGame = copier.makeCopy(advanceToPhase, origAiPlayer);
|
||||
|
||||
aiPlayer = (Player) copier.find(origAiPlayer);
|
||||
eval = new GameStateEvaluator();
|
||||
@@ -74,6 +74,8 @@ public class GameSimulator {
|
||||
eval.getScoreForGameState(origGame, origAiPlayer);
|
||||
// Print debug info.
|
||||
printDiff(origLines, simLines);
|
||||
// make sure it gets printed
|
||||
System.out.flush();
|
||||
throw new RuntimeException("Game copy error. See diff output above for details.");
|
||||
}
|
||||
eval.setDebugging(false);
|
||||
@@ -230,8 +232,10 @@ public class GameSimulator {
|
||||
return score;
|
||||
}
|
||||
|
||||
private static void resolveStack(final Game game, final Player opponent) {
|
||||
public static void resolveStack(final Game game, final Player opponent) {
|
||||
// TODO: This needs to set an AI controller for all opponents, in case of multiplayer.
|
||||
PlayerControllerAi sim = new PlayerControllerAi(game, opponent, opponent.getLobbyPlayer());
|
||||
sim.setUseSimulation(true);
|
||||
opponent.runWithController(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -259,7 +263,7 @@ public class GameSimulator {
|
||||
// Continue until stack is empty.
|
||||
}
|
||||
}
|
||||
}, new PlayerControllerAi(game, opponent, opponent.getLobbyPlayer()));
|
||||
}, sim);
|
||||
}
|
||||
|
||||
public Game getSimulatedGameState() {
|
||||
|
||||
@@ -24,7 +24,7 @@ public class GameStateEvaluator {
|
||||
public GameCopier copier;
|
||||
public Game gameCopy;
|
||||
}
|
||||
private CombatSimResult simulateUpcomingCombatThisTurn(final Game evalGame) {
|
||||
private CombatSimResult simulateUpcomingCombatThisTurn(final Game evalGame, final Player aiPlayer) {
|
||||
PhaseType phase = evalGame.getPhaseHandler().getPhase();
|
||||
if (phase.isAfter(PhaseType.COMBAT_DAMAGE) || evalGame.isGameOver()) {
|
||||
return null;
|
||||
@@ -38,7 +38,12 @@ public class GameStateEvaluator {
|
||||
}
|
||||
GameCopier copier = new GameCopier(evalGame);
|
||||
Game gameCopy = copier.makeCopy();
|
||||
gameCopy.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DAMAGE);
|
||||
gameCopy.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DAMAGE, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GameSimulator.resolveStack(gameCopy, aiPlayer.getWeakestOpponent());
|
||||
}
|
||||
});
|
||||
CombatSimResult result = new CombatSimResult();
|
||||
result.copier = copier;
|
||||
result.gameCopy = gameCopy;
|
||||
@@ -67,7 +72,7 @@ public class GameStateEvaluator {
|
||||
return getScoreForGameOver(game, aiPlayer);
|
||||
}
|
||||
|
||||
CombatSimResult result = simulateUpcomingCombatThisTurn(game);
|
||||
CombatSimResult result = simulateUpcomingCombatThisTurn(game, aiPlayer);
|
||||
if (result != null) {
|
||||
Player aiPlayerCopy = (Player) result.copier.find(aiPlayer);
|
||||
if (result.gameCopy.isGameOver()) {
|
||||
|
||||
@@ -101,7 +101,7 @@ public class SpellAbilityPicker {
|
||||
}
|
||||
|
||||
public SpellAbility chooseSpellAbilityToPlay(SimulationController controller) {
|
||||
printOutput = (controller == null);
|
||||
printOutput = controller == null;
|
||||
|
||||
// Pass if top of stack is owned by me.
|
||||
if (!game.getStack().isEmpty() && game.getStack().peekAbility().getActivatingPlayer().equals(player)) {
|
||||
|
||||
@@ -56,11 +56,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("RememberObjects")) {
|
||||
for (final String rem : sa.getParam("RememberObjects").split(",")) {
|
||||
for (final Object o : AbilityUtils.getDefinedObjects(sa.getHostCard(), rem, sa)) {
|
||||
if (o instanceof SpellAbility) {
|
||||
// "RememberObjects$ Remembered" don't remember spellability
|
||||
continue;
|
||||
}
|
||||
for (final Object o : AbilityUtils.getDefinedEntities(sa.getHostCard(), rem, sa)) {
|
||||
delTrig.addRemembered(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("RememberObjects")) {
|
||||
rememberList = new FCollection<>();
|
||||
for (final String rem : sa.getParam("RememberObjects").split(",")) {
|
||||
rememberList.addAll(AbilityUtils.getDefinedObjects(hostCard, rem, sa));
|
||||
rememberList.addAll(AbilityUtils.getDefinedEntities(hostCard, rem, sa));
|
||||
}
|
||||
|
||||
if (sa.hasParam("ForgetCounter")) {
|
||||
|
||||
@@ -59,11 +59,7 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("RememberObjects")) {
|
||||
for (final String rem : sa.getParam("RememberObjects").split(",")) {
|
||||
for (final Object o : AbilityUtils.getDefinedObjects(sa.getHostCard(), rem, sa)) {
|
||||
if (o instanceof SpellAbility) {
|
||||
// "RememberObjects$ Remembered" don't remember spellability
|
||||
continue;
|
||||
}
|
||||
for (final Object o : AbilityUtils.getDefinedEntities(sa.getHostCard(), rem, sa)) {
|
||||
immediateTrig.addRemembered(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3617,7 +3617,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public Iterable<KeywordsChange> getChangedCardKeywordsList() {
|
||||
|
||||
return Iterables.concat(
|
||||
changedCardKeywordsByText.values(), // Layer 3
|
||||
ImmutableList.of(new KeywordsChange(ImmutableList.<String>of(), null, this.hasRemoveIntrinsic())), // Layer 4
|
||||
@@ -3629,7 +3628,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
return changedCardKeywords;
|
||||
}
|
||||
|
||||
|
||||
public Table<Long, Long, CardColor> getChangedCardColorsTable() {
|
||||
return changedCardColors;
|
||||
}
|
||||
@@ -4561,6 +4559,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
public final Iterable<String> getHiddenExtrinsicKeywords() {
|
||||
return Iterables.concat(this.hiddenExtrinsicKeywords.values());
|
||||
}
|
||||
public final Table<Long, Long, List<String>> getHiddenExtrinsicKeywordsTable() {
|
||||
return hiddenExtrinsicKeywords;
|
||||
}
|
||||
|
||||
public final void addHiddenExtrinsicKeywords(long timestamp, long staticId, Iterable<String> keywords) {
|
||||
// TODO if some keywords aren't removed anymore, then no need for extra Array List
|
||||
|
||||
@@ -1036,7 +1036,7 @@ public class CombatUtil {
|
||||
* @return a boolean.
|
||||
*/
|
||||
public static boolean canBlock(final Card attacker, final Card blocker, final boolean nextTurn) {
|
||||
if ((attacker == null) || (blocker == null)) {
|
||||
if (attacker == null || blocker == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1162,11 +1162,17 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
}
|
||||
|
||||
public final boolean devAdvanceToPhase(PhaseType targetPhase) {
|
||||
return devAdvanceToPhase(targetPhase, null);
|
||||
}
|
||||
public final boolean devAdvanceToPhase(PhaseType targetPhase, Runnable resolver) {
|
||||
boolean isTopsy = playerTurn.getAmountOfKeyword("The phases of your turn are reversed.") % 2 == 1;
|
||||
while (phase.isBefore(targetPhase, isTopsy)) {
|
||||
if (checkStateBasedEffects()) {
|
||||
return false;
|
||||
}
|
||||
if (resolver != null) {
|
||||
resolver.run();
|
||||
}
|
||||
onPhaseEnd();
|
||||
advanceToNextPhase();
|
||||
onPhaseBegin();
|
||||
|
||||
@@ -855,11 +855,7 @@ public final class StaticAbilityContinuous {
|
||||
if (params.containsKey("TriggerRememberDefined")) {
|
||||
String triggerRemembered = (params.get("TriggerRememberDefined"));
|
||||
for (final String rem : triggerRemembered.split(",")) {
|
||||
for (final Object o : AbilityUtils.getDefinedObjects(hostCard, rem, stAb)) {
|
||||
if (o instanceof SpellAbility) {
|
||||
// "RememberObjects$ Remembered" don't remember spellability
|
||||
continue;
|
||||
}
|
||||
for (final Object o : AbilityUtils.getDefinedEntities(hostCard, rem, stAb)) {
|
||||
actualTrigger.addRemembered(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,11 +81,9 @@ public class Zone implements java.io.Serializable, Iterable<Card> {
|
||||
public final void add(final Card c) {
|
||||
add(c, null);
|
||||
}
|
||||
|
||||
public final void add(final Card c, final Integer index) {
|
||||
add(c, index, null);
|
||||
}
|
||||
|
||||
public void add(final Card c, Integer index, final Card latestState) {
|
||||
if (index != null && cardList.isEmpty() && index.intValue() > 0) {
|
||||
// something went wrong, most likely the method fired when the game was in an unexpected state
|
||||
|
||||
Reference in New Issue
Block a user