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