mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 18:58:00 +00:00
Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -32,6 +32,8 @@ bin
|
|||||||
gen
|
gen
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# Ignore macOS Spotlight rubbish
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
# TODO: specify what these ignores are for (releasing?)
|
# TODO: specify what these ignores are for (releasing?)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[Official GitLab repo](https://git.cardforge.org/core-developers/forge).
|
[Official GitLab repo](https://git.cardforge.org/core-developers/forge).
|
||||||
|
|
||||||
Dev instructions here: [Getting Started](https://www.slightlymagic.net/wiki/Forge:How_to_Get_Started_Developing_Forge) (Somewhat outdated)
|
Dev instructions here: [Getting Started](https://git.cardforge.org/core-developers/forge/-/wikis/(SM-autoconverted)-how-to-get-started-developing-forge) (Somewhat outdated)
|
||||||
|
|
||||||
Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968)
|
Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968)
|
||||||
|
|
||||||
@@ -168,11 +168,11 @@ The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desk
|
|||||||
|
|
||||||
## IntelliJ
|
## IntelliJ
|
||||||
|
|
||||||
Quick start guide for [setting up the Forge project within IntelliJ](https://git.cardforge.org/core-developers/forge/wikis/intellij-setup).
|
Quick start guide for [setting up the Forge project within IntelliJ](https://git.cardforge.org/core-developers/forge/-/wikis/Development/intellij-setup).
|
||||||
|
|
||||||
## Card Scripting
|
## Card Scripting
|
||||||
|
|
||||||
Visit [this page](https://www.slightlymagic.net/wiki/Forge_API) for information on scripting.
|
Visit [this page](https://git.cardforge.org/core-developers/forge/-/wikis/Card-scripting-API/Card-scripting-API) for information on scripting.
|
||||||
|
|
||||||
Card scripting resources are found in the forge-gui/res/ path.
|
Card scripting resources are found in the forge-gui/res/ path.
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ import forge.util.TextUtil;
|
|||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
|
|
||||||
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* ComputerUtil_Attack2 class.
|
* ComputerUtil_Attack2 class.
|
||||||
@@ -412,12 +411,10 @@ public class AiAttackController {
|
|||||||
|
|
||||||
final Player opp = this.defendingOpponent;
|
final Player opp = this.defendingOpponent;
|
||||||
|
|
||||||
// Increase the total number of blockers needed by 1 if Finest Hour in
|
// Increase the total number of blockers needed by 1 if Finest Hour in play
|
||||||
// play
|
|
||||||
// (human will get an extra first attack with a creature that untaps)
|
// (human will get an extra first attack with a creature that untaps)
|
||||||
// In addition, if the computer guesses it needs no blockers, make sure
|
// In addition, if the computer guesses it needs no blockers, make sure
|
||||||
// that
|
// that it won't be surprised by Exalted
|
||||||
// it won't be surprised by Exalted
|
|
||||||
final int humanExaltedBonus = opp.countExaltedBonus();
|
final int humanExaltedBonus = opp.countExaltedBonus();
|
||||||
|
|
||||||
if (humanExaltedBonus > 0) {
|
if (humanExaltedBonus > 0) {
|
||||||
@@ -427,8 +424,7 @@ public class AiAttackController {
|
|||||||
// total attack = biggest creature + exalted, *2 if Rafiq is in play
|
// total attack = biggest creature + exalted, *2 if Rafiq is in play
|
||||||
int humanBasePower = getAttack(this.oppList.get(0)) + humanExaltedBonus;
|
int humanBasePower = getAttack(this.oppList.get(0)) + humanExaltedBonus;
|
||||||
if (finestHour) {
|
if (finestHour) {
|
||||||
// For Finest Hour, one creature could attack and get the
|
// For Finest Hour, one creature could attack and get the bonus TWICE
|
||||||
// bonus TWICE
|
|
||||||
humanBasePower = humanBasePower + humanExaltedBonus;
|
humanBasePower = humanBasePower + humanExaltedBonus;
|
||||||
}
|
}
|
||||||
final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBasePower
|
final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBasePower
|
||||||
@@ -449,7 +445,6 @@ public class AiAttackController {
|
|||||||
return notNeededAsBlockers;
|
return notNeededAsBlockers;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this uses a global variable, which isn't perfect
|
|
||||||
public final boolean doesHumanAttackAndWin(final Player ai, final int nBlockingCreatures) {
|
public final boolean doesHumanAttackAndWin(final Player ai, final int nBlockingCreatures) {
|
||||||
int totalAttack = 0;
|
int totalAttack = 0;
|
||||||
int totalPoison = 0;
|
int totalPoison = 0;
|
||||||
@@ -681,7 +676,6 @@ 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 void declareAttackers(final Combat combat) {
|
||||||
|
|
||||||
if (this.attackers.isEmpty()) {
|
if (this.attackers.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -716,7 +710,7 @@ public class AiAttackController {
|
|||||||
int attackMax = restrict.getMax();
|
int attackMax = restrict.getMax();
|
||||||
if (attackMax == -1) {
|
if (attackMax == -1) {
|
||||||
// check with the local limitations vs. the chosen defender
|
// check with the local limitations vs. the chosen defender
|
||||||
attackMax = ComputerUtilCombat.getMaxAttackersFor(defender);
|
attackMax = restrict.getDefenderMax().get(defender) == null ? -1 : restrict.getDefenderMax().get(defender);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attackMax == 0) {
|
if (attackMax == 0) {
|
||||||
@@ -776,7 +770,6 @@ public class AiAttackController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (bAssault) {
|
if (bAssault) {
|
||||||
if (LOG_AI_ATTACKS)
|
if (LOG_AI_ATTACKS)
|
||||||
System.out.println("Assault");
|
System.out.println("Assault");
|
||||||
@@ -851,7 +844,6 @@ public class AiAttackController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// *******************
|
// *******************
|
||||||
// Evaluate the creature forces
|
// Evaluate the creature forces
|
||||||
// *******************
|
// *******************
|
||||||
@@ -935,12 +927,9 @@ public class AiAttackController {
|
|||||||
|
|
||||||
// *********************
|
// *********************
|
||||||
// if outnumber and superior ratio work out whether attritional all out
|
// if outnumber and superior ratio work out whether attritional all out
|
||||||
// attacking will work
|
// attacking will work attritional attack will expect some creatures to die but to achieve
|
||||||
// attritional attack will expect some creatures to die but to achieve
|
// victory by sheer weight of numbers attacking turn after turn. It's not calculate very
|
||||||
// victory by sheer weight
|
// carefully, the accuracy can probably be improved
|
||||||
// of numbers attacking turn after turn. It's not calculate very
|
|
||||||
// carefully, the accuracy
|
|
||||||
// can probably be improved
|
|
||||||
// *********************
|
// *********************
|
||||||
boolean doAttritionalAttack = false;
|
boolean doAttritionalAttack = false;
|
||||||
// get list of attackers ordered from low power to high
|
// get list of attackers ordered from low power to high
|
||||||
@@ -974,7 +963,6 @@ public class AiAttackController {
|
|||||||
doAttritionalAttack = true;
|
doAttritionalAttack = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// System.out.println(doAttritionalAttack + " = do attritional attack");
|
|
||||||
// *********************
|
// *********************
|
||||||
// end attritional attack calculation
|
// end attritional attack calculation
|
||||||
// *********************
|
// *********************
|
||||||
@@ -1024,11 +1012,9 @@ public class AiAttackController {
|
|||||||
// end see how long until unblockable attackers will be fatal
|
// end see how long until unblockable attackers will be fatal
|
||||||
// *****************
|
// *****************
|
||||||
|
|
||||||
|
|
||||||
// decide on attack aggression based on a comparison of forces, life
|
// decide on attack aggression based on a comparison of forces, life
|
||||||
// totals and other considerations
|
// totals and other considerations some bad "magic numbers" here
|
||||||
// some bad "magic numbers" here, TODO replace with nice descriptive
|
// TODO replace with nice descriptive variable names
|
||||||
// variable names
|
|
||||||
if (ratioDiff > 0 && doAttritionalAttack) {
|
if (ratioDiff > 0 && doAttritionalAttack) {
|
||||||
this.aiAggression = 5; // attack at all costs
|
this.aiAggression = 5; // attack at all costs
|
||||||
} else if ((ratioDiff >= 1 && this.attackers.size() > 1 && (humanLifeToDamageRatio < 2 || outNumber > 0))
|
} else if ((ratioDiff >= 1 && this.attackers.size() > 1 && (humanLifeToDamageRatio < 2 || outNumber > 0))
|
||||||
@@ -1223,9 +1209,8 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// look at the attacker in relation to the blockers to establish a
|
// look at the attacker in relation to the blockers to establish a
|
||||||
// number of factors about the attacking
|
// number of factors about the attacking context that will be relevant
|
||||||
// context that will be relevant to the attackers decision according to
|
// to the attackers decision according to the selected strategy
|
||||||
// the selected strategy
|
|
||||||
for (final Card defender : validBlockers) {
|
for (final Card defender : validBlockers) {
|
||||||
// if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check
|
// if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check
|
||||||
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
|
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
|
||||||
@@ -1299,14 +1284,11 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (numberOfPossibleBlockers > 2
|
if (numberOfPossibleBlockers > 2
|
||||||
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, combat))
|
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, this.defendingOpponent))
|
||||||
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, combat))) {
|
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, this.defendingOpponent))) {
|
||||||
canBeBlocked = true;
|
canBeBlocked = true;
|
||||||
}
|
}
|
||||||
/*System.out.println(attacker + " canBeKilledByOne: " + canBeKilledByOne + " canKillAll: "
|
// decide if the creature should attack based on the prevailing strategy choice in aiAggression
|
||||||
+ canKillAll + " isWorthLessThanAllKillers: " + isWorthLessThanAllKillers + " canBeBlocked: " + canBeBlocked);*/
|
|
||||||
// decide if the creature should attack based on the prevailing strategy
|
|
||||||
// choice in aiAggression
|
|
||||||
switch (this.aiAggression) {
|
switch (this.aiAggression) {
|
||||||
case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked
|
case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked
|
||||||
if ((canKillAll && isWorthLessThanAllKillers) || !canBeBlocked) {
|
if ((canKillAll && isWorthLessThanAllKillers) || !canBeBlocked) {
|
||||||
@@ -1562,4 +1544,4 @@ public class AiAttackController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // end class ComputerUtil_Attack2
|
}
|
||||||
|
|||||||
@@ -180,11 +180,9 @@ public class AiBlockController {
|
|||||||
|
|
||||||
// Good Blocks means a good trade or no trade
|
// Good Blocks means a good trade or no trade
|
||||||
private void makeGoodBlocks(final Combat combat) {
|
private void makeGoodBlocks(final Combat combat) {
|
||||||
|
|
||||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||||
|
|
||||||
for (final Card attacker : attackersLeft) {
|
for (final Card attacker : attackersLeft) {
|
||||||
|
|
||||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|
||||||
|| attacker.hasKeyword(Keyword.MENACE)) {
|
|| attacker.hasKeyword(Keyword.MENACE)) {
|
||||||
@@ -192,7 +190,6 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Card blocker = null;
|
Card blocker = null;
|
||||||
|
|
||||||
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||||
|
|
||||||
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
|
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
|
||||||
@@ -305,7 +302,6 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Card blocker = null;
|
Card blocker = null;
|
||||||
|
|
||||||
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||||
|
|
||||||
for (Card b : blockers) {
|
for (Card b : blockers) {
|
||||||
@@ -366,10 +362,9 @@ public class AiBlockController {
|
|||||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
||||||
// if the total damage of the blockgang was not enough
|
// if the total damage of the blockgang was not enough
|
||||||
// without but is enough with this blocker finish the
|
// without but is enough with this blocker finish the blockgang
|
||||||
// blockgang
|
|
||||||
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) < damageNeeded
|
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) < damageNeeded
|
||||||
|| CombatUtil.needsBlockers(attacker) > blockGang.size()) {
|
|| CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > blockGang.size()) {
|
||||||
blockGang.add(blocker);
|
blockGang.add(blocker);
|
||||||
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) >= damageNeeded) {
|
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) >= damageNeeded) {
|
||||||
currentAttackers.remove(attacker);
|
currentAttackers.remove(attacker);
|
||||||
@@ -407,7 +402,7 @@ public class AiBlockController {
|
|||||||
boolean foundDoubleBlock = false; // if true, a good double block is found
|
boolean foundDoubleBlock = false; // if true, a good double block is found
|
||||||
|
|
||||||
// AI can't handle good blocks with more than three creatures yet
|
// AI can't handle good blocks with more than three creatures yet
|
||||||
if (CombatUtil.needsBlockers(attacker) > (considerTripleBlock ? 3 : 2)) {
|
if (CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > (considerTripleBlock ? 3 : 2)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,7 +439,7 @@ public class AiBlockController {
|
|||||||
final int addedValue = ComputerUtilCard.evaluateCreature(blocker);
|
final int addedValue = ComputerUtilCard.evaluateCreature(blocker);
|
||||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
||||||
if ((damageNeeded > currentDamage || CombatUtil.needsBlockers(attacker) > blockGang.size())
|
if ((damageNeeded > currentDamage || CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > blockGang.size())
|
||||||
&& !(damageNeeded > currentDamage + additionalDamage)
|
&& !(damageNeeded > currentDamage + additionalDamage)
|
||||||
// The attacker will be killed
|
// The attacker will be killed
|
||||||
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
|
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
|
||||||
@@ -454,8 +449,7 @@ public class AiBlockController {
|
|||||||
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
|
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
|
||||||
// or life is in danger
|
// or life is in danger
|
||||||
&& CombatUtil.canBlock(attacker, blocker, combat)) {
|
&& CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||||
// this is needed for attackers that can't be blocked by
|
// this is needed for attackers that can't be blocked by more than 1
|
||||||
// more than 1
|
|
||||||
currentAttackers.remove(attacker);
|
currentAttackers.remove(attacker);
|
||||||
combat.addBlocker(attacker, blocker);
|
combat.addBlocker(attacker, blocker);
|
||||||
if (CombatUtil.canBlock(attacker, leader, combat)) {
|
if (CombatUtil.canBlock(attacker, leader, combat)) {
|
||||||
@@ -496,7 +490,7 @@ public class AiBlockController {
|
|||||||
final int addedValue3 = ComputerUtilCard.evaluateCreature(secondBlocker);
|
final int addedValue3 = ComputerUtilCard.evaluateCreature(secondBlocker);
|
||||||
final int netCombatDamage = attacker.getNetCombatDamage();
|
final int netCombatDamage = attacker.getNetCombatDamage();
|
||||||
|
|
||||||
if ((damageNeeded > currentDamage || CombatUtil.needsBlockers(attacker) > blockGang.size())
|
if ((damageNeeded > currentDamage || CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > blockGang.size())
|
||||||
&& !(damageNeeded > currentDamage + additionalDamage2 + additionalDamage3)
|
&& !(damageNeeded > currentDamage + additionalDamage2 + additionalDamage3)
|
||||||
// The attacker will be killed
|
// The attacker will be killed
|
||||||
&& ((absorbedDamage2 + absorbedDamage > netCombatDamage && absorbedDamage3 + absorbedDamage > netCombatDamage
|
&& ((absorbedDamage2 + absorbedDamage > netCombatDamage && absorbedDamage3 + absorbedDamage > netCombatDamage
|
||||||
@@ -510,8 +504,7 @@ public class AiBlockController {
|
|||||||
// or life is in danger
|
// or life is in danger
|
||||||
&& CombatUtil.canBlock(attacker, secondBlocker, combat)
|
&& CombatUtil.canBlock(attacker, secondBlocker, combat)
|
||||||
&& CombatUtil.canBlock(attacker, thirdBlocker, combat)) {
|
&& CombatUtil.canBlock(attacker, thirdBlocker, combat)) {
|
||||||
// this is needed for attackers that can't be blocked by
|
// this is needed for attackers that can't be blocked by more than 1
|
||||||
// more than 1
|
|
||||||
currentAttackers.remove(attacker);
|
currentAttackers.remove(attacker);
|
||||||
combat.addBlocker(attacker, thirdBlocker);
|
combat.addBlocker(attacker, thirdBlocker);
|
||||||
if (CombatUtil.canBlock(attacker, secondBlocker, combat)) {
|
if (CombatUtil.canBlock(attacker, secondBlocker, combat)) {
|
||||||
@@ -587,12 +580,10 @@ public class AiBlockController {
|
|||||||
* @param combat a {@link forge.game.combat.Combat} object.
|
* @param combat a {@link forge.game.combat.Combat} object.
|
||||||
*/
|
*/
|
||||||
private void makeTradeBlocks(final Combat combat) {
|
private void makeTradeBlocks(final Combat combat) {
|
||||||
|
|
||||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||||
List<Card> killingBlockers;
|
List<Card> killingBlockers;
|
||||||
|
|
||||||
for (final Card attacker : attackersLeft) {
|
for (final Card attacker : attackersLeft) {
|
||||||
|
|
||||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||||
|| attacker.hasKeyword(Keyword.MENACE)
|
|| attacker.hasKeyword(Keyword.MENACE)
|
||||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||||
@@ -628,7 +619,6 @@ public class AiBlockController {
|
|||||||
|
|
||||||
// Chump Blocks (should only be made if life is in danger)
|
// Chump Blocks (should only be made if life is in danger)
|
||||||
private void makeChumpBlocks(final Combat combat) {
|
private void makeChumpBlocks(final Combat combat) {
|
||||||
|
|
||||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||||
|
|
||||||
makeChumpBlocks(combat, currentAttackers);
|
makeChumpBlocks(combat, currentAttackers);
|
||||||
@@ -639,7 +629,6 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void makeChumpBlocks(final Combat combat, List<Card> attackers) {
|
private void makeChumpBlocks(final Combat combat, List<Card> attackers) {
|
||||||
|
|
||||||
if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -694,11 +683,9 @@ public class AiBlockController {
|
|||||||
|
|
||||||
// Block creatures with "can't be blocked except by two or more creatures"
|
// Block creatures with "can't be blocked except by two or more creatures"
|
||||||
private void makeMultiChumpBlocks(final Combat combat) {
|
private void makeMultiChumpBlocks(final Combat combat) {
|
||||||
|
|
||||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||||
|
|
||||||
for (final Card attacker : currentAttackers) {
|
for (final Card attacker : currentAttackers) {
|
||||||
|
|
||||||
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||||
&& !attacker.hasKeyword(Keyword.MENACE)
|
&& !attacker.hasKeyword(Keyword.MENACE)
|
||||||
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||||
@@ -730,14 +717,12 @@ public class AiBlockController {
|
|||||||
|
|
||||||
/** Reinforce blockers blocking attackers with trample (should only be made if life is in danger) */
|
/** Reinforce blockers blocking attackers with trample (should only be made if life is in danger) */
|
||||||
private void reinforceBlockersAgainstTrample(final Combat combat) {
|
private void reinforceBlockersAgainstTrample(final Combat combat) {
|
||||||
|
|
||||||
List<Card> chumpBlockers;
|
List<Card> chumpBlockers;
|
||||||
|
|
||||||
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
|
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
|
||||||
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
|
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||||
|
|
||||||
// TODO - should check here for a "rampage-like" trigger that replaced
|
// TODO - should check here for a "rampage-like" trigger that replaced the keyword:
|
||||||
// the keyword:
|
|
||||||
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
|
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
|
||||||
|
|
||||||
for (final Card attacker : tramplingAttackers) {
|
for (final Card attacker : tramplingAttackers) {
|
||||||
@@ -764,7 +749,6 @@ public class AiBlockController {
|
|||||||
|
|
||||||
/** Support blockers not destroying the attacker with more blockers to try to kill the attacker */
|
/** Support blockers not destroying the attacker with more blockers to try to kill the attacker */
|
||||||
private void reinforceBlockersToKill(final Combat combat) {
|
private void reinforceBlockersToKill(final Combat combat) {
|
||||||
|
|
||||||
List<Card> safeBlockers;
|
List<Card> safeBlockers;
|
||||||
List<Card> blockers;
|
List<Card> blockers;
|
||||||
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
|
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||||
@@ -1036,27 +1020,21 @@ public class AiBlockController {
|
|||||||
} else {
|
} else {
|
||||||
lifeInDanger = false;
|
lifeInDanger = false;
|
||||||
}
|
}
|
||||||
// if life is still in danger
|
// Reinforce blockers blocking attackers with trample if life is still in danger
|
||||||
// Reinforce blockers blocking attackers with trample if life is
|
|
||||||
// still
|
|
||||||
// in danger
|
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
reinforceBlockersAgainstTrample(combat);
|
reinforceBlockersAgainstTrample(combat);
|
||||||
} else {
|
} else {
|
||||||
lifeInDanger = false;
|
lifeInDanger = false;
|
||||||
}
|
}
|
||||||
// Support blockers not destroying the attacker with more blockers
|
// Support blockers not destroying the attacker with more blockers
|
||||||
// to
|
// to try to kill the attacker
|
||||||
// try to kill the attacker
|
|
||||||
if (!lifeInDanger) {
|
if (!lifeInDanger) {
|
||||||
reinforceBlockersToKill(combat);
|
reinforceBlockersToKill(combat);
|
||||||
}
|
}
|
||||||
|
|
||||||
// == 2. If the AI life would still be in danger make a safer
|
// == 2. If the AI life would still be in danger make a safer approach ==
|
||||||
// approach ==
|
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
clearBlockers(combat, possibleBlockers); // reset every block
|
clearBlockers(combat, possibleBlockers); // reset every block assignment
|
||||||
// assignment
|
|
||||||
makeTradeBlocks(combat); // choose necessary trade blocks
|
makeTradeBlocks(combat); // choose necessary trade blocks
|
||||||
// if life is in danger
|
// if life is in danger
|
||||||
makeGoodBlocks(combat);
|
makeGoodBlocks(combat);
|
||||||
@@ -1066,8 +1044,7 @@ public class AiBlockController {
|
|||||||
} else {
|
} else {
|
||||||
lifeInDanger = false;
|
lifeInDanger = false;
|
||||||
}
|
}
|
||||||
// Reinforce blockers blocking attackers with trample if life is
|
// Reinforce blockers blocking attackers with trample if life is still in danger
|
||||||
// still in danger
|
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
reinforceBlockersAgainstTrample(combat);
|
reinforceBlockersAgainstTrample(combat);
|
||||||
} else {
|
} else {
|
||||||
@@ -1077,11 +1054,9 @@ public class AiBlockController {
|
|||||||
reinforceBlockersToKill(combat);
|
reinforceBlockersToKill(combat);
|
||||||
}
|
}
|
||||||
|
|
||||||
// == 3. If the AI life would be in serious danger make an even
|
// == 3. If the AI life would be in serious danger make an even safer approach ==
|
||||||
// safer approach ==
|
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
||||||
clearBlockers(combat, possibleBlockers); // reset every block
|
clearBlockers(combat, possibleBlockers); // reset every block assignment
|
||||||
// assignment
|
|
||||||
makeChumpBlocks(combat); // choose chump blocks
|
makeChumpBlocks(combat); // choose chump blocks
|
||||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
makeTradeBlocks(combat); // choose necessary trade
|
makeTradeBlocks(combat); // choose necessary trade
|
||||||
@@ -1090,15 +1065,13 @@ public class AiBlockController {
|
|||||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
makeGoodBlocks(combat);
|
makeGoodBlocks(combat);
|
||||||
}
|
}
|
||||||
// Reinforce blockers blocking attackers with trample if life is
|
// Reinforce blockers blocking attackers with trample if life is still in danger
|
||||||
// still in danger
|
|
||||||
else {
|
else {
|
||||||
reinforceBlockersAgainstTrample(combat);
|
reinforceBlockersAgainstTrample(combat);
|
||||||
}
|
}
|
||||||
makeGangBlocks(combat);
|
makeGangBlocks(combat);
|
||||||
// Support blockers not destroying the attacker with more
|
// Support blockers not destroying the attacker with more
|
||||||
// blockers
|
// blockers to try to kill the attacker
|
||||||
// to try to kill the attacker
|
|
||||||
reinforceBlockersToKill(combat);
|
reinforceBlockersToKill(combat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1108,7 +1081,7 @@ public class AiBlockController {
|
|||||||
chumpBlockers.addAll(CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."));
|
chumpBlockers.addAll(CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."));
|
||||||
// if an attacker with lure attacks - all that can block
|
// if an attacker with lure attacks - all that can block
|
||||||
for (final Card blocker : blockersLeft) {
|
for (final Card blocker : blockersLeft) {
|
||||||
if (CombatUtil.mustBlockAnAttacker(blocker, combat)) {
|
if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) {
|
||||||
chumpBlockers.add(blocker);
|
chumpBlockers.add(blocker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1118,7 +1091,7 @@ public class AiBlockController {
|
|||||||
blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
|
blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
|
||||||
for (final Card blocker : blockers) {
|
for (final Card blocker : blockers) {
|
||||||
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
|
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
|
||||||
&& (CombatUtil.mustBlockAnAttacker(blocker, combat)
|
&& (CombatUtil.mustBlockAnAttacker(blocker, combat, null)
|
||||||
|| blocker.hasKeyword("CARDNAME blocks each turn if able.")
|
|| blocker.hasKeyword("CARDNAME blocks each turn if able.")
|
||||||
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
|
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
|
||||||
combat.addBlocker(attacker, blocker);
|
combat.addBlocker(attacker, blocker);
|
||||||
@@ -1137,7 +1110,6 @@ 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() && !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import forge.ai.ability.ChangeZoneAi;
|
|||||||
import forge.ai.ability.ExploreAi;
|
import forge.ai.ability.ExploreAi;
|
||||||
import forge.ai.ability.LearnAi;
|
import forge.ai.ability.LearnAi;
|
||||||
import forge.ai.simulation.SpellAbilityPicker;
|
import forge.ai.simulation.SpellAbilityPicker;
|
||||||
|
import forge.card.CardStateName;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.deck.CardPool;
|
import forge.deck.CardPool;
|
||||||
@@ -46,6 +47,7 @@ import forge.game.Game;
|
|||||||
import forge.game.GameActionUtil;
|
import forge.game.GameActionUtil;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.GlobalRuleChange;
|
import forge.game.GlobalRuleChange;
|
||||||
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.ability.SpellApiBased;
|
import forge.game.ability.SpellApiBased;
|
||||||
@@ -78,6 +80,7 @@ import forge.game.player.Player;
|
|||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.replacement.ReplaceMoved;
|
import forge.game.replacement.ReplaceMoved;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
|
import forge.game.replacement.ReplacementLayer;
|
||||||
import forge.game.replacement.ReplacementType;
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.LandAbility;
|
import forge.game.spellability.LandAbility;
|
||||||
@@ -307,6 +310,8 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exSA.setTrigger(tr);
|
exSA.setTrigger(tr);
|
||||||
|
// need to set TriggeredObject
|
||||||
|
exSA.setTriggeringObject(AbilityKey.Card, card);
|
||||||
|
|
||||||
// for trigger test, need to ignore the conditions
|
// for trigger test, need to ignore the conditions
|
||||||
SpellAbilityCondition cons = exSA.getConditions();
|
SpellAbilityCondition cons = exSA.getConditions();
|
||||||
@@ -432,17 +437,22 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't play the land if it has cycling and enough lands are available
|
|
||||||
final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities();
|
|
||||||
|
|
||||||
final CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
|
final CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
|
||||||
CardCollection lands = new CardCollection(battlefield);
|
CardCollection lands = new CardCollection(battlefield);
|
||||||
lands.addAll(hand);
|
lands.addAll(hand);
|
||||||
lands = CardLists.filter(lands, CardPredicates.Presets.LANDS);
|
lands = CardLists.filter(lands, CardPredicates.Presets.LANDS);
|
||||||
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
|
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
|
||||||
|
|
||||||
|
if (lands.size() >= Math.max(maxCmcInHand, 6)) {
|
||||||
|
// don't play MDFC land if other side is spell and enough lands are available
|
||||||
|
if (!c.isLand() || (c.isModal() && !c.getState(CardStateName.Modal).getType().isLand())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't play the land if it has cycling and enough lands are available
|
||||||
|
final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities();
|
||||||
for (final SpellAbility sa : spellAbilities) {
|
for (final SpellAbility sa : spellAbilities) {
|
||||||
if (sa.isCycling()) {
|
if (sa.isCycling()) {
|
||||||
if (lands.size() >= Math.max(maxCmcInHand, 6)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -503,20 +513,35 @@ public class AiController {
|
|||||||
|
|
||||||
//try to skip lands that enter the battlefield tapped
|
//try to skip lands that enter the battlefield tapped
|
||||||
if (!nonLandsInHand.isEmpty()) {
|
if (!nonLandsInHand.isEmpty()) {
|
||||||
CardCollection nonTappeddLands = new CardCollection();
|
CardCollection nonTappedLands = new CardCollection();
|
||||||
for (Card land : landList) {
|
for (Card land : landList) {
|
||||||
// Is this the best way to check if a land ETB Tapped?
|
// check replacement effects if land would enter tapped or not
|
||||||
if (land.hasSVar("ETBTappedSVar")) {
|
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(land);
|
||||||
|
repParams.put(AbilityKey.Origin, land.getZone().getZoneType());
|
||||||
|
repParams.put(AbilityKey.Destination, ZoneType.Battlefield);
|
||||||
|
repParams.put(AbilityKey.Source, land);
|
||||||
|
|
||||||
|
boolean foundTapped = false;
|
||||||
|
for (ReplacementEffect re : player.getGame().getReplacementHandler().getReplacementList(ReplacementType.Moved, repParams, ReplacementLayer.Other)) {
|
||||||
|
SpellAbility reSA = re.ensureAbility();
|
||||||
|
if (reSA == null || !ApiType.Tap.equals(reSA.getApi())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Glacial Fortress and friends
|
reSA.setActivatingPlayer(reSA.getHostCard().getController());
|
||||||
if (land.hasSVar("ETBCheckSVar") && CardFactoryUtil.xCount(land, land.getSVar("ETBCheckSVar")) == 0) {
|
if (reSA.metConditions()) {
|
||||||
|
foundTapped = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundTapped) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
nonTappeddLands.add(land);
|
|
||||||
|
nonTappedLands.add(land);
|
||||||
}
|
}
|
||||||
if (!nonTappeddLands.isEmpty()) {
|
if (!nonTappedLands.isEmpty()) {
|
||||||
landList = nonTappeddLands;
|
landList = nonTappedLands;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,7 +613,6 @@ public class AiController {
|
|||||||
sa.setActivatingPlayer(player);
|
sa.setActivatingPlayer(player);
|
||||||
// check everything necessary
|
// check everything necessary
|
||||||
|
|
||||||
|
|
||||||
AiPlayDecision opinion = canPlayAndPayFor(currentSA);
|
AiPlayDecision opinion = canPlayAndPayFor(currentSA);
|
||||||
//PhaseHandler ph = game.getPhaseHandler();
|
//PhaseHandler ph = game.getPhaseHandler();
|
||||||
// System.out.printf("Ai thinks '%s' of %s @ %s %s >>> \n", opinion, sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
|
// System.out.printf("Ai thinks '%s' of %s @ %s %s >>> \n", opinion, sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
|
||||||
@@ -1301,15 +1325,6 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
|
public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
|
||||||
if (logic.equalsIgnoreCase("ProtectFriendly")) {
|
|
||||||
final Player controller = hostCard.getController();
|
|
||||||
if (affected instanceof Player) {
|
|
||||||
return !((Player) affected).isOpponentOf(controller);
|
|
||||||
}
|
|
||||||
if (affected instanceof Card) {
|
|
||||||
return !((Card) affected).getController().isOpponentOf(controller);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1408,8 +1423,6 @@ public class AiController {
|
|||||||
private List<SpellAbility> singleSpellAbilityList(SpellAbility sa) {
|
private List<SpellAbility> singleSpellAbilityList(SpellAbility sa) {
|
||||||
if (sa == null) { return null; }
|
if (sa == null) { return null; }
|
||||||
|
|
||||||
// System.out.println("Chosen to play: " + sa);
|
|
||||||
|
|
||||||
final List<SpellAbility> abilities = Lists.newArrayList();
|
final List<SpellAbility> abilities = Lists.newArrayList();
|
||||||
abilities.add(sa);
|
abilities.add(sa);
|
||||||
return abilities;
|
return abilities;
|
||||||
@@ -1454,6 +1467,13 @@ public class AiController {
|
|||||||
if (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land)) {
|
if (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land)) {
|
||||||
final List<SpellAbility> abilities = Lists.newArrayList();
|
final List<SpellAbility> abilities = Lists.newArrayList();
|
||||||
|
|
||||||
|
// TODO extend this logic to evaluate MDFC with both sides land
|
||||||
|
// this can only happen if its a MDFC land
|
||||||
|
if (!land.isLand()) {
|
||||||
|
land.setState(CardStateName.Modal, true);
|
||||||
|
land.setBackSide(true);
|
||||||
|
}
|
||||||
|
|
||||||
LandAbility la = new LandAbility(land, player, null);
|
LandAbility la = new LandAbility(land, player, null);
|
||||||
la.setCardState(land.getCurrentState());
|
la.setCardState(land.getCurrentState());
|
||||||
if (la.canPlay()) {
|
if (la.canPlay()) {
|
||||||
@@ -1704,10 +1724,8 @@ public class AiController {
|
|||||||
for (int i = 0; i < numToExile; i++) {
|
for (int i = 0; i < numToExile; i++) {
|
||||||
Card chosen = null;
|
Card chosen = null;
|
||||||
for (final Card c : grave) { // Exile noncreatures first in
|
for (final Card c : grave) { // Exile noncreatures first in
|
||||||
// case we can revive. Might
|
// case we can revive. Might wanna do some additional
|
||||||
// wanna do some additional
|
// checking here for Flashback and the like.
|
||||||
// checking here for Flashback
|
|
||||||
// and the like.
|
|
||||||
if (!c.isCreature()) {
|
if (!c.isCreature()) {
|
||||||
chosen = c;
|
chosen = c;
|
||||||
break;
|
break;
|
||||||
@@ -1746,12 +1764,21 @@ public class AiController {
|
|||||||
* @param sa the sa
|
* @param sa the sa
|
||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
public final boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa) {
|
public final boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa, GameEntity affected) {
|
||||||
Card hostCard = effect.getHostCard();
|
Card hostCard = effect.getHostCard();
|
||||||
if (hostCard.hasAlternateState()) {
|
if (hostCard.hasAlternateState()) {
|
||||||
hostCard = game.getCardState(hostCard);
|
hostCard = game.getCardState(hostCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (effect.hasParam("AILogic") && effect.getParam("AILogic").equalsIgnoreCase("ProtectFriendly")) {
|
||||||
|
final Player controller = hostCard.getController();
|
||||||
|
if (affected instanceof Player) {
|
||||||
|
return !((Player) affected).isOpponentOf(controller);
|
||||||
|
}
|
||||||
|
if (affected instanceof Card) {
|
||||||
|
return !((Card) affected).getController().isOpponentOf(controller);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (effect.hasParam("AICheckSVar")) {
|
if (effect.hasParam("AICheckSVar")) {
|
||||||
System.out.println("aiShouldRun?" + sa);
|
System.out.println("aiShouldRun?" + sa);
|
||||||
final String svarToCheck = effect.getParam("AICheckSVar");
|
final String svarToCheck = effect.getParam("AICheckSVar");
|
||||||
@@ -1766,7 +1793,7 @@ public class AiController {
|
|||||||
compareTo = Integer.parseInt(strCmpTo);
|
compareTo = Integer.parseInt(strCmpTo);
|
||||||
} catch (final Exception ignored) {
|
} catch (final Exception ignored) {
|
||||||
if (sa == null) {
|
if (sa == null) {
|
||||||
compareTo = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(strCmpTo));
|
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), effect);
|
||||||
} else {
|
} else {
|
||||||
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
|
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
|
||||||
}
|
}
|
||||||
@@ -1776,7 +1803,7 @@ public class AiController {
|
|||||||
int left = 0;
|
int left = 0;
|
||||||
|
|
||||||
if (sa == null) {
|
if (sa == null) {
|
||||||
left = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(svarToCheck));
|
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, effect);
|
||||||
} else {
|
} else {
|
||||||
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
||||||
}
|
}
|
||||||
@@ -1866,11 +1893,13 @@ public class AiController {
|
|||||||
} else if ("LowestLoseLife".equals(logic)) {
|
} else if ("LowestLoseLife".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
||||||
} else if ("HighestLoseLife".equals(logic)) {
|
} else if ("HighestLoseLife".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
return Math.min(player.getLife() -1,MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1);
|
||||||
} else if ("HighestGetCounter".equals(logic)) {
|
} else if ("HighestGetCounter".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(3);
|
return MyRandom.getRandom().nextInt(3);
|
||||||
} else if (source.hasSVar("EnergyToPay")) {
|
} else if (source.hasSVar("EnergyToPay")) {
|
||||||
return AbilityUtils.calculateAmount(source, source.getSVar("EnergyToPay"), sa);
|
return AbilityUtils.calculateAmount(source, source.getSVar("EnergyToPay"), sa);
|
||||||
|
} else if ("Vermin".equals(logic)) {
|
||||||
|
return MyRandom.getRandom().nextInt(Math.max(player.getLife() - 5, 0));
|
||||||
}
|
}
|
||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
@@ -1965,7 +1994,6 @@ public class AiController {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// this is where the computer cheats
|
// this is where the computer cheats
|
||||||
// changes AllZone.getComputerPlayer().getZone(Zone.Library)
|
// changes AllZone.getComputerPlayer().getZone(Zone.Library)
|
||||||
|
|
||||||
@@ -2245,10 +2273,8 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI logic for choosing which replacement effect to apply
|
// AI logic for choosing which replacement effect to apply happens here.
|
||||||
// happens here.
|
|
||||||
return Iterables.getFirst(list, null);
|
return Iterables.getFirst(list, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ public class ComputerUtil {
|
|||||||
SpellAbility newSA = sa.copyWithNoManaCost();
|
SpellAbility newSA = sa.copyWithNoManaCost();
|
||||||
newSA.setActivatingPlayer(ai);
|
newSA.setActivatingPlayer(ai);
|
||||||
|
|
||||||
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) {
|
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA) || !ComputerUtilMana.canPayManaCost(newSA, ai, 0)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1125,7 +1125,7 @@ public class ComputerUtil {
|
|||||||
creatures2.add(creatures.get(i));
|
creatures2.add(creatures.get(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (((creatures2.size() + CardUtil.getThisTurnCast("Creature.YouCtrl", vengevines.get(0)).size()) > 1)
|
if (((creatures2.size() + CardUtil.getThisTurnCast("Creature.YouCtrl", vengevines.get(0), null).size()) > 1)
|
||||||
&& card.isCreature() && card.getManaCost().getCMC() <= 3) {
|
&& card.isCreature() && card.getManaCost().getCMC() <= 3) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2253,10 +2253,8 @@ public class ComputerUtil {
|
|||||||
String chosen = "";
|
String chosen = "";
|
||||||
if (kindOfType.equals("Card")) {
|
if (kindOfType.equals("Card")) {
|
||||||
// TODO
|
// TODO
|
||||||
// computer will need to choose a type
|
// computer will need to choose a type based on whether it needs a creature or land,
|
||||||
// based on whether it needs a creature or land,
|
// otherwise, lib search for most common type left then, reveal chosenType to Human
|
||||||
// otherwise, lib search for most common type left
|
|
||||||
// then, reveal chosenType to Human
|
|
||||||
if (game.getPhaseHandler().is(PhaseType.UNTAP) && logic == null) { // Storage Matrix
|
if (game.getPhaseHandler().is(PhaseType.UNTAP) && logic == null) { // Storage Matrix
|
||||||
double amount = 0;
|
double amount = 0;
|
||||||
for (String type : CardType.getAllCardTypes()) {
|
for (String type : CardType.getAllCardTypes()) {
|
||||||
@@ -2434,8 +2432,7 @@ public class ComputerUtil {
|
|||||||
if (!source.canReceiveCounters(p1p1Type)) {
|
if (!source.canReceiveCounters(p1p1Type)) {
|
||||||
return opponent ? "Feather" : "Quill";
|
return opponent ? "Feather" : "Quill";
|
||||||
}
|
}
|
||||||
// if source is not on the battlefield anymore, choose +1/+1
|
// if source is not on the battlefield anymore, choose +1/+1 ones
|
||||||
// ones
|
|
||||||
if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
|
if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
|
||||||
return opponent ? "Feather" : "Quill";
|
return opponent ? "Feather" : "Quill";
|
||||||
}
|
}
|
||||||
@@ -2477,8 +2474,7 @@ public class ComputerUtil {
|
|||||||
return opponent ? "Numbers" : "Strength";
|
return opponent ? "Numbers" : "Strength";
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO check for ETB to +1/+1 counters
|
// TODO check for ETB to +1/+1 counters or over another trigger like lifegain
|
||||||
// or over another trigger like lifegain
|
|
||||||
|
|
||||||
int tokenScore = ComputerUtilCard.evaluateCreature(token);
|
int tokenScore = ComputerUtilCard.evaluateCreature(token);
|
||||||
|
|
||||||
@@ -2556,8 +2552,7 @@ public class ComputerUtil {
|
|||||||
return "Taxes";
|
return "Taxes";
|
||||||
} else {
|
} else {
|
||||||
// ai is first voter or ally of controller
|
// ai is first voter or ally of controller
|
||||||
// both are not affected, but if opponents controll creatures,
|
// both are not affected, but if opponents control creatures, sacrifice is worse
|
||||||
// sacrifice is worse
|
|
||||||
return controller.getOpponents().getCreaturesInPlay().isEmpty() ? "Taxes" : "Death";
|
return controller.getOpponents().getCreaturesInPlay().isEmpty() ? "Taxes" : "Death";
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -2854,7 +2849,6 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean lifegainNegative(final Player player, final Card source, final int n) {
|
public static boolean lifegainNegative(final Player player, final Card source, final int n) {
|
||||||
|
|
||||||
if (!player.canGainLife()) {
|
if (!player.canGainLife()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return ComputerUtilCard.getMostExpensivePermanentAI(all);
|
return getMostExpensivePermanentAI(all);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -265,16 +265,14 @@ public class ComputerUtilCard {
|
|||||||
* @return a {@link forge.game.card.Card} object.
|
* @return a {@link forge.game.card.Card} object.
|
||||||
*/
|
*/
|
||||||
public static Card getBestAI(final Iterable<Card> list) {
|
public static Card getBestAI(final Iterable<Card> list) {
|
||||||
// Get Best will filter by appropriate getBest list if ALL of the list
|
// Get Best will filter by appropriate getBest list if ALL of the list is of that type
|
||||||
// is of that type
|
|
||||||
if (Iterables.all(list, CardPredicates.Presets.CREATURES)) {
|
if (Iterables.all(list, CardPredicates.Presets.CREATURES)) {
|
||||||
return ComputerUtilCard.getBestCreatureAI(list);
|
return ComputerUtilCard.getBestCreatureAI(list);
|
||||||
}
|
}
|
||||||
if (Iterables.all(list, CardPredicates.Presets.LANDS)) {
|
if (Iterables.all(list, CardPredicates.Presets.LANDS)) {
|
||||||
return getBestLandAI(list);
|
return getBestLandAI(list);
|
||||||
}
|
}
|
||||||
// TODO - Once we get an EvaluatePermanent this should call
|
// TODO - Once we get an EvaluatePermanent this should call getBestPermanent()
|
||||||
// getBestPermanent()
|
|
||||||
return ComputerUtilCard.getMostExpensivePermanentAI(list);
|
return ComputerUtilCard.getMostExpensivePermanentAI(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +381,7 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (biasLand && Iterables.any(list, CardPredicates.Presets.LANDS)) {
|
if (biasLand && Iterables.any(list, CardPredicates.Presets.LANDS)) {
|
||||||
return ComputerUtilCard.getWorstLand(CardLists.filter(list, CardPredicates.Presets.LANDS));
|
return getWorstLand(CardLists.filter(list, CardPredicates.Presets.LANDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean hasCreatures = Iterables.any(list, CardPredicates.Presets.CREATURES);
|
final boolean hasCreatures = Iterables.any(list, CardPredicates.Presets.CREATURES);
|
||||||
@@ -393,7 +391,7 @@ public class ComputerUtilCard {
|
|||||||
|
|
||||||
List<Card> lands = CardLists.filter(list, CardPredicates.Presets.LANDS);
|
List<Card> lands = CardLists.filter(list, CardPredicates.Presets.LANDS);
|
||||||
if (lands.size() > 6) {
|
if (lands.size() > 6) {
|
||||||
return ComputerUtilCard.getWorstLand(lands);
|
return getWorstLand(lands);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasEnchantmants || hasArtifacts) {
|
if (hasEnchantmants || hasArtifacts) {
|
||||||
@@ -410,8 +408,7 @@ public class ComputerUtilCard {
|
|||||||
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
|
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Planeswalkers fall through to here, lands will fall through if there
|
// Planeswalkers fall through to here, lands will fall through if there aren't very many
|
||||||
// aren't very many
|
|
||||||
return getCheapestPermanentAI(list, null, false);
|
return getCheapestPermanentAI(list, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,7 +441,7 @@ public class ComputerUtilCard {
|
|||||||
public static final Comparator<Card> EvaluateCreatureComparator = new Comparator<Card>() {
|
public static final Comparator<Card> EvaluateCreatureComparator = new Comparator<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(final Card a, final Card b) {
|
public int compare(final Card a, final Card b) {
|
||||||
return ComputerUtilCard.evaluateCreature(b) - ComputerUtilCard.evaluateCreature(a);
|
return evaluateCreature(b) - evaluateCreature(a);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -882,9 +879,9 @@ public class ComputerUtilCard {
|
|||||||
public static final Predicate<Deck> AI_KNOWS_HOW_TO_PLAY_ALL_CARDS = new Predicate<Deck>() {
|
public static final Predicate<Deck> AI_KNOWS_HOW_TO_PLAY_ALL_CARDS = new Predicate<Deck>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Deck d) {
|
public boolean apply(Deck d) {
|
||||||
for(Entry<DeckSection, CardPool> cp: d) {
|
for (Entry<DeckSection, CardPool> cp: d) {
|
||||||
for(Entry<PaperCard, Integer> e : cp.getValue()) {
|
for (Entry<PaperCard, Integer> e : cp.getValue()) {
|
||||||
if ( e.getKey().getRules().getAiHints().getRemAIDecks() )
|
if (e.getKey().getRules().getAiHints().getRemAIDecks())
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -982,7 +979,7 @@ public class ComputerUtilCard {
|
|||||||
for(byte c : MagicColor.WUBRG) {
|
for(byte c : MagicColor.WUBRG) {
|
||||||
String devotionCode = "Count$Devotion." + MagicColor.toLongString(c);
|
String devotionCode = "Count$Devotion." + MagicColor.toLongString(c);
|
||||||
|
|
||||||
int devotion = CardFactoryUtil.xCount(sa.getHostCard(), devotionCode);
|
int devotion = AbilityUtils.calculateAmount(sa.getHostCard(), devotionCode, sa);
|
||||||
if (devotion > curDevotion && !CardLists.filter(hand, CardPredicates.isColor(c)).isEmpty()) {
|
if (devotion > curDevotion && !CardLists.filter(hand, CardPredicates.isColor(c)).isEmpty()) {
|
||||||
curDevotion = devotion;
|
curDevotion = devotion;
|
||||||
chosenColor = MagicColor.toLongString(c);
|
chosenColor = MagicColor.toLongString(c);
|
||||||
@@ -1644,11 +1641,12 @@ public class ComputerUtilCard {
|
|||||||
copiedKeywords.insertAll(pumped.getKeywords());
|
copiedKeywords.insertAll(pumped.getKeywords());
|
||||||
List<KeywordInterface> toCopy = Lists.newArrayList();
|
List<KeywordInterface> toCopy = Lists.newArrayList();
|
||||||
for (KeywordInterface k : c.getKeywords()) {
|
for (KeywordInterface k : c.getKeywords()) {
|
||||||
if (!copiedKeywords.contains(k.getOriginal())) {
|
KeywordInterface copiedKI = k.copy(c, true);
|
||||||
if (k.getHidden()) {
|
if (!copiedKeywords.contains(copiedKI.getOriginal())) {
|
||||||
pumped.addHiddenExtrinsicKeyword(k);
|
if (copiedKI.getHidden()) {
|
||||||
|
pumped.addHiddenExtrinsicKeyword(copiedKI);
|
||||||
} else {
|
} else {
|
||||||
toCopy.add(k);
|
toCopy.add(copiedKI);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1678,37 +1676,27 @@ public class ComputerUtilCard {
|
|||||||
// remove old boost that might be copied
|
// remove old boost that might be copied
|
||||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||||
vCard.removePTBoost(c.getTimestamp(), stAb.getId());
|
vCard.removePTBoost(c.getTimestamp(), stAb.getId());
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
if (!stAb.getParam("Mode").equals("Continuous")) {
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!params.containsKey("Affected")) {
|
if (!stAb.hasParam("Affected")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!params.containsKey("AddPower") && !params.containsKey("AddToughness")) {
|
if (!stAb.hasParam("AddPower") && !stAb.hasParam("AddToughness")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String valid = params.get("Affected");
|
if (!vCard.isValid(stAb.getParam("Affected").split(","), c.getController(), c, stAb)) {
|
||||||
if (!vCard.isValid(valid, c.getController(), c, null)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int att = 0;
|
int att = 0;
|
||||||
if (params.containsKey("AddPower")) {
|
if (stAb.hasParam("AddPower")) {
|
||||||
String addP = params.get("AddPower");
|
String addP = stAb.getParam("AddPower");
|
||||||
if (addP.equals("AffectedX")) {
|
att = AbilityUtils.calculateAmount(addP.startsWith("Affected") ? vCard : c, addP, stAb, true);
|
||||||
att = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addP));
|
|
||||||
} else {
|
|
||||||
att = AbilityUtils.calculateAmount(c, addP, stAb);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
int def = 0;
|
int def = 0;
|
||||||
if (params.containsKey("AddToughness")) {
|
if (stAb.hasParam("AddToughness")) {
|
||||||
String addT = params.get("AddToughness");
|
String addT = stAb.getParam("AddToughness");
|
||||||
if (addT.equals("AffectedY")) {
|
def = AbilityUtils.calculateAmount(addT.startsWith("Affected") ? vCard : c, addT, stAb, true);
|
||||||
def = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addT));
|
|
||||||
} else {
|
|
||||||
def = AbilityUtils.calculateAmount(c, addT, stAb);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
vCard.addPTBoost(att, def, c.getTimestamp(), stAb.getId());
|
vCard.addPTBoost(att, def, c.getTimestamp(), stAb.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import forge.game.ability.ApiType;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
@@ -335,7 +334,7 @@ public class ComputerUtilCombat {
|
|||||||
*/
|
*/
|
||||||
public static int resultingPoison(final Player ai, final Combat combat) {
|
public static int resultingPoison(final Player ai, final Combat combat) {
|
||||||
|
|
||||||
// ai can't get poision counters, so the value can't change
|
// ai can't get poison counters, so the value can't change
|
||||||
if (!ai.canReceiveCounters(CounterEnumType.POISON)) {
|
if (!ai.canReceiveCounters(CounterEnumType.POISON)) {
|
||||||
return ai.getPoisonCounters();
|
return ai.getPoisonCounters();
|
||||||
}
|
}
|
||||||
@@ -413,7 +412,6 @@ public class ComputerUtilCombat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
@@ -493,8 +491,7 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean lifeInSeriousDanger(final Player ai, final Combat combat, final int payment) {
|
public static boolean lifeInSeriousDanger(final Player ai, final Combat combat, final int payment) {
|
||||||
// life in danger only cares about the player's life. Not about a
|
// life in danger only cares about the player's life. Not about a Planeswalkers life
|
||||||
// Planeswalkers life
|
|
||||||
if (ai.cantLose() || combat == null) {
|
if (ai.cantLose() || combat == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -569,8 +566,7 @@ public class ComputerUtilCombat {
|
|||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This calculates the amount of damage a blocker in a blockgang can deal to
|
// This calculates the amount of damage a blocker in a blockgang can deal to the attacker
|
||||||
// the attacker
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* dealsDamageAsBlocker.
|
* dealsDamageAsBlocker.
|
||||||
@@ -583,7 +579,6 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int dealsDamageAsBlocker(final Card attacker, final Card defender) {
|
public static int dealsDamageAsBlocker(final Card attacker, final Card defender) {
|
||||||
|
|
||||||
int defenderDamage = predictDamageByBlockerWithoutDoubleStrike(attacker, defender);
|
int defenderDamage = predictDamageByBlockerWithoutDoubleStrike(attacker, defender);
|
||||||
|
|
||||||
if (defender.hasKeyword(Keyword.DOUBLE_STRIKE)) {
|
if (defender.hasKeyword(Keyword.DOUBLE_STRIKE)) {
|
||||||
@@ -648,7 +643,6 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int totalShieldDamage(final Card attacker, final List<Card> defenders) {
|
public static int totalShieldDamage(final Card attacker, final List<Card> defenders) {
|
||||||
|
|
||||||
int defenderDefense = 0;
|
int defenderDefense = 0;
|
||||||
|
|
||||||
for (final Card defender : defenders) {
|
for (final Card defender : defenders) {
|
||||||
@@ -672,7 +666,6 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int shieldDamage(final Card attacker, final Card blocker) {
|
public static int shieldDamage(final Card attacker, final Card blocker) {
|
||||||
|
|
||||||
if (ComputerUtilCombat.canDestroyBlockerBeforeFirstStrike(blocker, attacker, false)) {
|
if (ComputerUtilCombat.canDestroyBlockerBeforeFirstStrike(blocker, attacker, false)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -777,7 +770,6 @@ public class ComputerUtilCombat {
|
|||||||
public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger,
|
public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger,
|
||||||
Combat combat, final List<Card> plannedAttackers) {
|
Combat combat, final List<Card> plannedAttackers) {
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
final Map<String, String> trigParams = trigger.getMapParams();
|
|
||||||
boolean willTrigger = false;
|
boolean willTrigger = false;
|
||||||
final Card source = trigger.getHostCard();
|
final Card source = trigger.getHostCard();
|
||||||
if (combat == null) {
|
if (combat == null) {
|
||||||
@@ -800,29 +792,27 @@ public class ComputerUtilCombat {
|
|||||||
if (combat.isAttacking(attacker)) {
|
if (combat.isAttacking(attacker)) {
|
||||||
return false; // The trigger should have triggered already
|
return false; // The trigger should have triggered already
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
if (trigger.hasParam("ValidCard")) {
|
||||||
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))
|
if (!trigger.matchesValidParam("ValidCard", attacker)
|
||||||
&& !(combat.isAttacking(source) && trigger.matchesValid(source,
|
&& !(combat.isAttacking(source) && trigger.matchesValidParam("ValidCard", source)
|
||||||
trigParams.get("ValidCard").split(","))
|
&& !trigger.hasParam("Alone"))) {
|
||||||
&& !trigParams.containsKey("Alone"))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("Attacked")) {
|
if (trigger.hasParam("Attacked")) {
|
||||||
if (combat.isAttacking(attacker)) {
|
if (combat.isAttacking(attacker)) {
|
||||||
GameEntity attacked = combat.getDefenderByAttacker(attacker);
|
if (!trigger.matchesValidParam("Attacked", combat.getDefenderByAttacker(attacker))) {
|
||||||
if (!trigger.matchesValid(attacked, trigParams.get("Attacked").split(","))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ("You,Planeswalker.YouCtrl".equals(trigParams.get("Attacked"))) {
|
if ("You,Planeswalker.YouCtrl".equals(trigger.getParam("Attacked"))) {
|
||||||
if (source.getController() == attacker.getController()) {
|
if (source.getController() == attacker.getController()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("Alone") && plannedAttackers != null && plannedAttackers.size() != 1) {
|
if (trigger.hasParam("Alone") && plannedAttackers != null && plannedAttackers.size() != 1) {
|
||||||
return false; // won't trigger since the AI is planning to attack with more than one creature
|
return false; // won't trigger since the AI is planning to attack with more than one creature
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -830,12 +820,10 @@ public class ComputerUtilCombat {
|
|||||||
// defender == null means unblocked
|
// defender == null means unblocked
|
||||||
if ((defender == null) && mode == TriggerType.AttackerUnblocked) {
|
if ((defender == null) && mode == TriggerType.AttackerUnblocked) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
if (!trigger.matchesValidParam("ValidCard", attacker)) {
|
||||||
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (defender == null) {
|
if (defender == null) {
|
||||||
return willTrigger;
|
return willTrigger;
|
||||||
@@ -843,8 +831,8 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
if (mode == TriggerType.Blocks) {
|
if (mode == TriggerType.Blocks) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidBlocked")) {
|
if (trigger.hasParam("ValidBlocked")) {
|
||||||
String validBlocked = trigParams.get("ValidBlocked");
|
String validBlocked = trigger.getParam("ValidBlocked");
|
||||||
if (validBlocked.contains(".withLesserPower")) {
|
if (validBlocked.contains(".withLesserPower")) {
|
||||||
// Have to check this restriction here as triggering objects aren't set yet, so
|
// Have to check this restriction here as triggering objects aren't set yet, so
|
||||||
// ValidBlocked$Creature.powerLTX where X:TriggeredBlocker$CardPower crashes with NPE
|
// ValidBlocked$Creature.powerLTX where X:TriggeredBlocker$CardPower crashes with NPE
|
||||||
@@ -857,8 +845,8 @@ public class ComputerUtilCombat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
if (trigger.hasParam("ValidCard")) {
|
||||||
String validBlocker = trigParams.get("ValidCard");
|
String validBlocker = trigger.getParam("ValidCard");
|
||||||
if (validBlocker.contains(".withLesserPower")) {
|
if (validBlocker.contains(".withLesserPower")) {
|
||||||
// Have to check this restriction here as triggering objects aren't set yet, so
|
// Have to check this restriction here as triggering objects aren't set yet, so
|
||||||
// ValidCard$Creature.powerLTX where X:TriggeredAttacker$CardPower crashes with NPE
|
// ValidCard$Creature.powerLTX where X:TriggeredAttacker$CardPower crashes with NPE
|
||||||
@@ -873,29 +861,23 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
} else if (mode == TriggerType.AttackerBlocked || mode == TriggerType.AttackerBlockedByCreature) {
|
} else if (mode == TriggerType.AttackerBlocked || mode == TriggerType.AttackerBlockedByCreature) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidBlocker")) {
|
if (!trigger.matchesValidParam("ValidBlocker", defender)) {
|
||||||
if (!trigger.matchesValid(defender, trigParams.get("ValidBlocker").split(","))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
if (!trigger.matchesValidParam("ValidCard", attacker)) {
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
|
||||||
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if (mode == TriggerType.DamageDone) {
|
} else if (mode == TriggerType.DamageDone) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidSource")) {
|
if (trigger.hasParam("ValidSource")) {
|
||||||
if (!(trigger.matchesValid(defender, trigParams.get("ValidSource").split(","))
|
if (!(trigger.matchesValidParam("ValidSource", defender)
|
||||||
&& defender.getNetCombatDamage() > 0
|
&& defender.getNetCombatDamage() > 0
|
||||||
&& (!trigParams.containsKey("ValidTarget")
|
&& trigger.matchesValidParam("ValidTarget", attacker))) {
|
||||||
|| trigger.matchesValid(attacker, trigParams.get("ValidTarget").split(","))))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!(trigger.matchesValid(attacker, trigParams.get("ValidSource").split(","))
|
if (!(trigger.matchesValidParam("ValidSource", attacker)
|
||||||
&& attacker.getNetCombatDamage() > 0
|
&& attacker.getNetCombatDamage() > 0
|
||||||
&& (!trigParams.containsKey("ValidTarget")
|
&& trigger.matchesValidParam("ValidTarget", defender))) {
|
||||||
|| trigger.matchesValid(defender, trigParams.get("ValidTarget").split(","))))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -944,30 +926,22 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
// look out for continuous static abilities that only care for blocking
|
// look out for continuous static abilities that only care for blocking creatures
|
||||||
// creatures
|
|
||||||
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
if (!stAb.getParam("Mode").equals("Continuous")) {
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!params.containsKey("Affected") || !params.get("Affected").contains("blocking")) {
|
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("blocking")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String valid = TextUtil.fastReplace(params.get("Affected"), "blocking", "Creature");
|
final String valid = TextUtil.fastReplace(stAb.getParam("Affected"), "blocking", "Creature");
|
||||||
if (!blocker.isValid(valid, card.getController(), card, null)) {
|
if (!blocker.isValid(valid, card.getController(), card, stAb)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("AddPower")) {
|
if (stAb.hasParam("AddPower")) {
|
||||||
if (params.get("AddPower").equals("X")) {
|
power += AbilityUtils.calculateAmount(card, stAb.getParam("AddPower"), stAb);
|
||||||
power += CardFactoryUtil.xCount(card, card.getSVar("X"));
|
|
||||||
} else if (params.get("AddPower").equals("Y")) {
|
|
||||||
power += CardFactoryUtil.xCount(card, card.getSVar("Y"));
|
|
||||||
} else {
|
|
||||||
power += Integer.valueOf(params.get("AddPower"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1252,31 +1226,23 @@ public class ComputerUtilCombat {
|
|||||||
theTriggers.addAll(blocker.getTriggers());
|
theTriggers.addAll(blocker.getTriggers());
|
||||||
}
|
}
|
||||||
|
|
||||||
// look out for continuous static abilities that only care for attacking
|
// look out for continuous static abilities that only care for attacking creatures
|
||||||
// creatures
|
|
||||||
if (!withoutCombatStaticAbilities) {
|
if (!withoutCombatStaticAbilities) {
|
||||||
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
if (!stAb.getParam("Mode").equals("Continuous")) {
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!params.containsKey("Affected") || !params.get("Affected").contains("attacking")) {
|
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("attacking")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String valid = TextUtil.fastReplace(params.get("Affected"), "attacking", "Creature");
|
final String valid = TextUtil.fastReplace(stAb.getParam("Affected"), "attacking", "Creature");
|
||||||
if (!attacker.isValid(valid, card.getController(), card, null)) {
|
if (!attacker.isValid(valid, card.getController(), card, stAb)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("AddPower")) {
|
if (stAb.hasParam("AddPower")) {
|
||||||
if (params.get("AddPower").equals("X")) {
|
power += AbilityUtils.calculateAmount(card, stAb.getParam("AddPower"), stAb);
|
||||||
power += CardFactoryUtil.xCount(card, card.getSVar("X"));
|
|
||||||
} else if (params.get("AddPower").equals("Y")) {
|
|
||||||
power += CardFactoryUtil.xCount(card, card.getSVar("Y"));
|
|
||||||
} else {
|
|
||||||
power += Integer.valueOf(params.get("AddPower"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1350,7 +1316,7 @@ public class ComputerUtilCombat {
|
|||||||
} else if (bonus.contains("TriggeredAttacker$CardToughness")) {
|
} else if (bonus.contains("TriggeredAttacker$CardToughness")) {
|
||||||
bonus = TextUtil.fastReplace(bonus, "TriggeredAttacker$CardToughness", TextUtil.concatNoSpace("Number$", String.valueOf(attacker.getNetToughness())));
|
bonus = TextUtil.fastReplace(bonus, "TriggeredAttacker$CardToughness", TextUtil.concatNoSpace("Number$", String.valueOf(attacker.getNetToughness())));
|
||||||
}
|
}
|
||||||
power += CardFactoryUtil.xCount(source, bonus);
|
power += AbilityUtils.calculateAmount(source, bonus, sa);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1447,8 +1413,7 @@ public class ComputerUtilCombat {
|
|||||||
theTriggers.addAll(blocker.getTriggers());
|
theTriggers.addAll(blocker.getTriggers());
|
||||||
}
|
}
|
||||||
|
|
||||||
// look out for continuous static abilities that only care for attacking
|
// look out for continuous static abilities that only care for attacking creatures
|
||||||
// creatures
|
|
||||||
if (!withoutCombatStaticAbilities) {
|
if (!withoutCombatStaticAbilities) {
|
||||||
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
@@ -1540,7 +1505,7 @@ public class ComputerUtilCombat {
|
|||||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||||
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
||||||
}
|
}
|
||||||
toughness += CardFactoryUtil.xCount(source, bonus);
|
toughness += AbilityUtils.calculateAmount(source, bonus, sa);
|
||||||
}
|
}
|
||||||
} else if (ApiType.PumpAll.equals(sa.getApi())) {
|
} else if (ApiType.PumpAll.equals(sa.getApi())) {
|
||||||
|
|
||||||
@@ -1573,7 +1538,7 @@ public class ComputerUtilCombat {
|
|||||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||||
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
||||||
}
|
}
|
||||||
toughness += CardFactoryUtil.xCount(source, bonus);
|
toughness += AbilityUtils.calculateAmount(source, bonus, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2256,11 +2221,12 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public final static int getDamageToKill(final Card c) {
|
public final static int getDamageToKill(final Card c) {
|
||||||
int killDamage = c.getLethalDamage() + c.getPreventNextDamageTotalShields();
|
int damageShield = c.getPreventNextDamageTotalShields();
|
||||||
|
int killDamage = c.getLethalDamage() + damageShield;
|
||||||
|
|
||||||
if ((killDamage > c.getPreventNextDamageTotalShields())
|
if ((killDamage > damageShield)
|
||||||
&& c.hasSVar("DestroyWhenDamaged")) {
|
&& c.hasSVar("DestroyWhenDamaged")) {
|
||||||
killDamage = 1 + c.getPreventNextDamageTotalShields();
|
killDamage = 1 + damageShield;
|
||||||
}
|
}
|
||||||
|
|
||||||
return killDamage;
|
return killDamage;
|
||||||
@@ -2282,7 +2248,6 @@ public class ComputerUtilCombat {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public final static int predictDamageTo(final Player target, final int damage, final Card source, final boolean isCombat) {
|
public final static int predictDamageTo(final Player target, final int damage, final Card source, final boolean isCombat) {
|
||||||
|
|
||||||
final Game game = target.getGame();
|
final Game game = target.getGame();
|
||||||
int restDamage = damage;
|
int restDamage = damage;
|
||||||
|
|
||||||
@@ -2291,39 +2256,38 @@ public class ComputerUtilCombat {
|
|||||||
// Predict replacement effects
|
// Predict replacement effects
|
||||||
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||||
for (final ReplacementEffect re : ca.getReplacementEffects()) {
|
for (final ReplacementEffect re : ca.getReplacementEffects()) {
|
||||||
Map<String, String> params = re.getMapParams();
|
if (!re.getMode().equals(ReplacementType.DamageDone) ||
|
||||||
if (!re.getMode().equals(ReplacementType.DamageDone) || !params.containsKey("PreventionEffect")) {
|
(!re.hasParam("PreventionEffect") && !re.hasParam("Prevent"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Immortal Coil prevents the damage but has a similar negative effect
|
// Immortal Coil prevents the damage but has a similar negative effect
|
||||||
if ("Immortal Coil".equals(ca.getName())) {
|
if ("Immortal Coil".equals(ca.getName())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("ValidSource")
|
if (!re.matchesValidParam("ValidSource", source)) {
|
||||||
&& !source.isValid(params.get("ValidSource"), ca.getController(), ca, null)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("ValidTarget")
|
if (!re.matchesValidParam("ValidTarget", source)) {
|
||||||
&& !target.isValid(params.get("ValidTarget"), ca.getController(), ca, null)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("IsCombat")) {
|
if (re.hasParam("IsCombat")) {
|
||||||
if (params.get("IsCombat").equals("True")) {
|
if (re.getParam("IsCombat").equals("True") != isCombat) {
|
||||||
if (!isCombat) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isCombat) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (re.hasParam("Prevent")) {
|
||||||
|
return 0;
|
||||||
|
} else if (re.getOverridingAbility() != null) {
|
||||||
|
SpellAbility repSA = re.getOverridingAbility();
|
||||||
|
if (repSA.getApi() == ApiType.ReplaceDamage) {
|
||||||
|
return Math.max(0, restDamage - AbilityUtils.calculateAmount(ca, repSA.getParam("Amount"), repSA));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
restDamage = target.staticDamagePrevention(restDamage, source, isCombat, true);
|
restDamage = target.staticDamagePrevention(restDamage, 0, source, isCombat);
|
||||||
|
|
||||||
return restDamage;
|
return restDamage;
|
||||||
}
|
}
|
||||||
@@ -2344,13 +2308,7 @@ public class ComputerUtilCombat {
|
|||||||
// This function helps the AI calculate the actual amount of damage an
|
// This function helps the AI calculate the actual amount of damage an
|
||||||
// effect would deal
|
// effect would deal
|
||||||
public final static int predictDamageTo(final Card target, final int damage, final Card source, final boolean isCombat) {
|
public final static int predictDamageTo(final Card target, final int damage, final Card source, final boolean isCombat) {
|
||||||
|
return predictDamageTo(target, damage, 0, source, isCombat);
|
||||||
int restDamage = damage;
|
|
||||||
|
|
||||||
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
|
|
||||||
restDamage = target.staticDamagePrevention(restDamage, source, isCombat, true);
|
|
||||||
|
|
||||||
return restDamage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2372,7 +2330,6 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public final static int predictDamageTo(final Card target, final int damage, final int possiblePrevention, final Card source, final boolean isCombat) {
|
public final static int predictDamageTo(final Card target, final int damage, final int possiblePrevention, final Card source, final boolean isCombat) {
|
||||||
|
|
||||||
int restDamage = damage;
|
int restDamage = damage;
|
||||||
|
|
||||||
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
|
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
|
||||||
@@ -2382,7 +2339,6 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final static boolean dealsFirstStrikeDamage(final Card combatant, final boolean withoutAbilities, final Combat combat) {
|
public final static boolean dealsFirstStrikeDamage(final Card combatant, final boolean withoutAbilities, final Combat combat) {
|
||||||
|
|
||||||
if (combatant.hasKeyword(Keyword.DOUBLE_STRIKE) || combatant.hasKeyword(Keyword.FIRST_STRIKE)) {
|
if (combatant.hasKeyword(Keyword.DOUBLE_STRIKE) || combatant.hasKeyword(Keyword.FIRST_STRIKE)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2490,7 +2446,14 @@ public class ComputerUtilCombat {
|
|||||||
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(
|
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(
|
||||||
ReplacementType.DamageDone, repParams, ReplacementLayer.Other);
|
ReplacementType.DamageDone, repParams, ReplacementLayer.Other);
|
||||||
|
|
||||||
return !list.isEmpty();
|
for (final ReplacementEffect re : list) {
|
||||||
|
Map<String, String> params = re.getMapParams();
|
||||||
|
if (params.containsKey("Prevent") ||
|
||||||
|
(re.getOverridingAbility() != null && re.getOverridingAbility().getApi() != ApiType.ReplaceDamage && re.getOverridingAbility().getApi() != ApiType.ReplaceEffect)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean attackerHasThreateningAfflict(Card attacker, Player aiDefender) {
|
public static boolean attackerHasThreateningAfflict(Card attacker, Player aiDefender) {
|
||||||
@@ -2499,20 +2462,6 @@ public class ComputerUtilCombat {
|
|||||||
return afflictDmg > attacker.getNetPower() || afflictDmg >= aiDefender.getLife();
|
return afflictDmg > attacker.getNetPower() || afflictDmg >= aiDefender.getLife();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getMaxAttackersFor(final GameEntity defender) {
|
|
||||||
if (defender instanceof Player) {
|
|
||||||
for (final Card card : ((Player) defender).getCardsIn(ZoneType.Battlefield)) {
|
|
||||||
if (card.hasKeyword("No more than one creature can attack you each combat.")) {
|
|
||||||
return 1;
|
|
||||||
} else if (card.hasKeyword("No more than two creatures can attack you each combat.")) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Card> categorizeAttackersByEvasion(List<Card> attackers) {
|
public static List<Card> categorizeAttackersByEvasion(List<Card> attackers) {
|
||||||
List<Card> categorizedAttackers = Lists.newArrayList();
|
List<Card> categorizedAttackers = Lists.newArrayList();
|
||||||
|
|
||||||
@@ -2602,5 +2551,3 @@ public class ComputerUtilCombat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package forge.ai;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ public class ComputerUtilCost {
|
|||||||
if (typeList.size() > ai.getMaxHandSize()) {
|
if (typeList.size() > ai.getMaxHandSize()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int num = AbilityUtils.calculateAmount(source, disc.getAmount(), null);
|
int num = AbilityUtils.calculateAmount(source, disc.getAmount(), sa);
|
||||||
|
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; i < num; i++) {
|
||||||
Card pref = ComputerUtil.getCardPreference(ai, source, "DiscardCost", typeList);
|
Card pref = ComputerUtil.getCardPreference(ai, source, "DiscardCost", typeList);
|
||||||
@@ -557,7 +558,6 @@ public class ComputerUtilCost {
|
|||||||
boolean payForOwnOnly = "OnlyOwn".equals(aiLogic);
|
boolean payForOwnOnly = "OnlyOwn".equals(aiLogic);
|
||||||
boolean payOwner = sa.hasParam("UnlessAI") && aiLogic.startsWith("Defined");
|
boolean payOwner = sa.hasParam("UnlessAI") && aiLogic.startsWith("Defined");
|
||||||
boolean payNever = "Never".equals(aiLogic);
|
boolean payNever = "Never".equals(aiLogic);
|
||||||
boolean shockland = "Shockland".equals(aiLogic);
|
|
||||||
boolean isMine = sa.getActivatingPlayer().equals(payer);
|
boolean isMine = sa.getActivatingPlayer().equals(payer);
|
||||||
|
|
||||||
if (payNever) { return false; }
|
if (payNever) { return false; }
|
||||||
@@ -572,26 +572,6 @@ public class ComputerUtilCost {
|
|||||||
if (sa.getHostCard() == null || payer.equals(sa.getHostCard().getController())) {
|
if (sa.getHostCard() == null || payer.equals(sa.getHostCard().getController())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (shockland) {
|
|
||||||
if (payer.getLife() > 3 && payer.canPayLife(2)) {
|
|
||||||
final int landsize = payer.getLandsInPlay().size() + 1;
|
|
||||||
for (Card c : payer.getCardsIn(ZoneType.Hand)) {
|
|
||||||
// if the new land size would equal the CMC of a card in AIs hand, consider playing it untapped,
|
|
||||||
// otherwise don't bother running other checks
|
|
||||||
if (landsize != c.getCMC()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// try to determine in the AI is actually planning to play a spell ability from the card
|
|
||||||
boolean willPlay = ComputerUtil.hasReasonToPlayCardThisTurn(payer, c);
|
|
||||||
// try to determine if the mana shards provided by the lands would be applicable to pay the mana cost
|
|
||||||
boolean canPay = c.getManaCost().canBePaidWithAvaliable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor());
|
|
||||||
|
|
||||||
if (canPay && willPlay) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else if ("Paralyze".equals(aiLogic)) {
|
} else if ("Paralyze".equals(aiLogic)) {
|
||||||
final Card c = source.getEnchantingCard();
|
final Card c = source.getEnchantingCard();
|
||||||
if (c == null || c.isUntapped()) {
|
if (c == null || c.isUntapped()) {
|
||||||
@@ -629,6 +609,29 @@ public class ComputerUtilCost {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for shocklands and similar ETB replacement effects
|
||||||
|
if (sa.hasParam("ETB") && sa.getApi().equals(ApiType.Tap)) {
|
||||||
|
for (final CostPart part : cost.getCostParts()) {
|
||||||
|
if (part instanceof CostPayLife) {
|
||||||
|
final CostPayLife lifeCost = (CostPayLife) part;
|
||||||
|
Integer amount = lifeCost.convertAmount();
|
||||||
|
if (payer.getLife() > (amount + 1) && payer.canPayLife(amount)) {
|
||||||
|
final int landsize = payer.getLandsInPlay().size() + 1;
|
||||||
|
for (Card c : payer.getCardsIn(ZoneType.Hand)) {
|
||||||
|
// Check if the AI has enough lands to play the card
|
||||||
|
if (landsize != c.getCMC()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Check if the AI intends to play the card and if it can pay for it with the mana it has
|
||||||
|
boolean willPlay = ComputerUtil.hasReasonToPlayCardThisTurn(payer, c);
|
||||||
|
boolean canPay = c.getManaCost().canBePaidWithAvaliable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor());
|
||||||
|
return canPay && willPlay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AI will only pay when it's not already payed and only opponents abilities
|
// AI will only pay when it's not already payed and only opponents abilities
|
||||||
if (alreadyPaid || (payers.size() > 1 && (isMine && !payForOwnOnly))) {
|
if (alreadyPaid || (payers.size() > 1 && (isMine && !payForOwnOnly))) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -609,7 +609,6 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
||||||
|
|
||||||
//System.out.println(manaProduced);
|
|
||||||
payMultipleMana(cost, manaProduced, ai);
|
payMultipleMana(cost, manaProduced, ai);
|
||||||
|
|
||||||
// remove from available lists
|
// remove from available lists
|
||||||
|
|||||||
@@ -162,9 +162,9 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
|||||||
value -= subValue(10, "must-attack");
|
value -= subValue(10, "must-attack");
|
||||||
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
||||||
value -= subValue(10, "must-attack-player");
|
value -= subValue(10, "must-attack-player");
|
||||||
} else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
|
}/* else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
|
||||||
value -= subValue(toughness * 5, "reverse-reach");
|
value -= subValue(toughness * 5, "reverse-reach");
|
||||||
}
|
}//*/
|
||||||
|
|
||||||
if (c.hasSVar("DestroyWhenDamaged")) {
|
if (c.hasSVar("DestroyWhenDamaged")) {
|
||||||
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
|
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
|
||||||
|
|||||||
@@ -1215,8 +1215,7 @@ public abstract class GameState {
|
|||||||
boolean tapped = c.isTapped();
|
boolean tapped = c.isTapped();
|
||||||
boolean sickness = c.hasSickness();
|
boolean sickness = c.hasSickness();
|
||||||
Map<CounterType, Integer> counters = c.getCounters();
|
Map<CounterType, Integer> counters = c.getCounters();
|
||||||
// Note: Not clearCounters() since we want to keep the counters
|
// Note: Not clearCounters() since we want to keep the counters var as-is.
|
||||||
// var as-is.
|
|
||||||
c.setCounters(Maps.newHashMap());
|
c.setCounters(Maps.newHashMap());
|
||||||
if (c.isAura()) {
|
if (c.isAura()) {
|
||||||
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
|
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
|
||||||
|
|||||||
@@ -131,6 +131,12 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return ComputerUtilCombat.distributeAIDamage(attacker, blockers, damageDealt, defender, overrideOrder);
|
return ComputerUtilCombat.distributeAIDamage(attacker, blockers, damageDealt, defender, overrideOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> affected, int shieldAmount) {
|
||||||
|
// TODO: AI current can't use this so this is not implemented.
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer announceRequirements(SpellAbility ability, String announce) {
|
public Integer announceRequirements(SpellAbility ability, String announce) {
|
||||||
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
|
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
|
||||||
@@ -568,8 +574,8 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
|
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question) {
|
||||||
return brains.aiShouldRun(replacementEffect, effectSA);
|
return brains.aiShouldRun(replacementEffect, effectSA, affected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -649,7 +655,6 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean playChosenSpellAbility(SpellAbility sa) {
|
public boolean playChosenSpellAbility(SpellAbility sa) {
|
||||||
// System.out.println("Playing sa: " + sa);
|
|
||||||
if (sa instanceof LandAbility) {
|
if (sa instanceof LandAbility) {
|
||||||
if (sa.canPlay()) {
|
if (sa.canPlay()) {
|
||||||
sa.resolve();
|
sa.resolve();
|
||||||
@@ -788,7 +793,8 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
case "BetterTgtThanRemembered":
|
case "BetterTgtThanRemembered":
|
||||||
if (source.getRememberedCount() > 0) {
|
if (source.getRememberedCount() > 0) {
|
||||||
Card rem = (Card) source.getFirstRemembered();
|
Card rem = (Card) source.getFirstRemembered();
|
||||||
if (!rem.isInZone(ZoneType.Battlefield)) {
|
// avoid pumping opponent creature
|
||||||
|
if (!rem.isInZone(ZoneType.Battlefield) || rem.getController().isOpponentOf(source.getController())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (Card c : source.getController().getCreaturesInPlay()) {
|
for (Card c : source.getController().getCreaturesInPlay()) {
|
||||||
@@ -998,6 +1004,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
emptyAbility.setActivatingPlayer(player);
|
emptyAbility.setActivatingPlayer(player);
|
||||||
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
||||||
emptyAbility.setSVars(sa.getSVars());
|
emptyAbility.setSVars(sa.getSVars());
|
||||||
|
emptyAbility.setXManaCostPaid(sa.getRootAbility().getXManaCostPaid());
|
||||||
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
||||||
ComputerUtil.playNoStack(player, emptyAbility, getGame()); // AI needs something to resolve to pay that cost
|
ComputerUtil.playNoStack(player, emptyAbility, getGame()); // AI needs something to resolve to pay that cost
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import forge.game.ability.ApiType;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
@@ -379,7 +378,7 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do not activate if damage will be prevented
|
// Do not activate if damage will be prevented
|
||||||
if (source.staticDamagePrevention(predictedPT.getLeft(), source, true, true) == 0) {
|
if (source.staticDamagePrevention(predictedPT.getLeft(), 0, source, true) == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1085,7 +1084,7 @@ public class SpecialCardAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String prominentColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield));
|
String prominentColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield));
|
||||||
int devotion = CardFactoryUtil.xCount(sa.getHostCard(), "Count$Devotion." + prominentColor);
|
int devotion = AbilityUtils.calculateAmount(sa.getHostCard(), "Count$Devotion." + prominentColor, sa);
|
||||||
int activationCost = sa.getPayCosts().getTotalMana().getCMC() + (sa.getPayCosts().hasTapCost() ? 1 : 0);
|
int activationCost = sa.getPayCosts().getTotalMana().getCMC() + (sa.getPayCosts().hasTapCost() ? 1 : 0);
|
||||||
|
|
||||||
// do not use this SA if devotion to most prominent color is less than its own activation cost + 1 (to actually get advantage)
|
// do not use this SA if devotion to most prominent color is less than its own activation cost + 1 (to actually get advantage)
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ public abstract class SpellAbilityAi {
|
|||||||
* Handles the AI decision to play a triggered SpellAbility
|
* Handles the AI decision to play a triggered SpellAbility
|
||||||
*/
|
*/
|
||||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
if (canPlayWithoutRestrict(aiPlayer, sa)) {
|
if (canPlayWithoutRestrict(aiPlayer, sa) && (!mandatory || sa.isTargetNumberValid())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.Learn, LearnAi.class)
|
.put(ApiType.Learn, LearnAi.class)
|
||||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||||
.put(ApiType.LosesGame, GameLossAi.class)
|
.put(ApiType.LosesGame, GameLossAi.class)
|
||||||
|
.put(ApiType.MakeCard, AlwaysPlayAi.class)
|
||||||
.put(ApiType.Mana, ManaEffectAi.class)
|
.put(ApiType.Mana, ManaEffectAi.class)
|
||||||
.put(ApiType.ManaReflected, CannotPlayAi.class)
|
.put(ApiType.ManaReflected, CannotPlayAi.class)
|
||||||
.put(ApiType.Manifest, ManifestAi.class)
|
.put(ApiType.Manifest, ManifestAi.class)
|
||||||
|
|||||||
@@ -100,30 +100,30 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
// Don't use instant speed animate abilities before AI's COMBAT_BEGIN
|
// Don't use instant speed animate abilities before AI's COMBAT_BEGIN
|
||||||
if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
&& !sa.hasParam("ActivationPhases") && !"Permanent".equals(sa.getParam("Duration"))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Don't use instant speed animate abilities outside human's
|
// Don't use instant speed animate abilities outside human's
|
||||||
// COMBAT_DECLARE_ATTACKERS or if no attackers
|
// COMBAT_DECLARE_ATTACKERS or if no attackers
|
||||||
if (ph.getPlayerTurn().isOpponentOf(ai) && !sa.hasParam("Permanent")
|
if (ph.getPlayerTurn().isOpponentOf(ai) && !"Permanent".equals(sa.getParam("Duration"))
|
||||||
&& (!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
&& (!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| game.getCombat() != null && game.getCombat().getAttackersOf(ai).isEmpty())) {
|
|| game.getCombat() != null && game.getCombat().getAttackersOf(ai).isEmpty())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Don't activate during MAIN2 unless this effect is permanent
|
// Don't activate during MAIN2 unless this effect is permanent
|
||||||
if (ph.is(PhaseType.MAIN2) && !sa.hasParam("Permanent") && !sa.hasParam("UntilYourNextTurn")) {
|
if (ph.is(PhaseType.MAIN2) && !"Permanent".equals(sa.getParam("Duration")) && !"UntilYourNextTurn".equals(sa.getParam("Duration"))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Don't animate if the AI won't attack anyway or use as a potential blocker
|
// Don't animate if the AI won't attack anyway or use as a potential blocker
|
||||||
Player opponent = ai.getWeakestOpponent();
|
Player opponent = ai.getWeakestOpponent();
|
||||||
// Activating as a potential blocker is only viable if it's an ability activated from a permanent, otherwise
|
// Activating as a potential blocker is only viable if it's an ability activated from a permanent, otherwise
|
||||||
// the AI will waste resources
|
// the AI will waste resources
|
||||||
boolean activateAsPotentialBlocker = sa.hasParam("UntilYourNextTurn")
|
boolean activateAsPotentialBlocker = "UntilYourNextTurn".equals(sa.getParam("Duration"))
|
||||||
&& ai.getGame().getPhaseHandler().getNextTurn() != ai
|
&& ai.getGame().getPhaseHandler().getNextTurn() != ai
|
||||||
&& source.isPermanent();
|
&& source.isPermanent();
|
||||||
if (ph.isPlayerTurn(ai) && ai.getLife() < 6 && opponent.getLife() > 6
|
if (ph.isPlayerTurn(ai) && ai.getLife() < 6 && opponent.getLife() > 6
|
||||||
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)
|
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)
|
||||||
&& !sa.hasParam("AILogic") && !sa.hasParam("Permanent") && !activateAsPotentialBlocker) {
|
&& !sa.hasParam("AILogic") && !"Permanent".equals(sa.getParam("Duration")) && !activateAsPotentialBlocker) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -156,7 +156,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
&& !c.isEquipping();
|
&& !c.isEquipping();
|
||||||
|
|
||||||
// for creatures that could be improved (like Figure of Destiny)
|
// for creatures that could be improved (like Figure of Destiny)
|
||||||
if (!bFlag && c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
|
if (!bFlag && c.isCreature() && ("Permanent".equals(sa.getParam("Duration")) || (!c.isTapped() && !c.isSick()))) {
|
||||||
int power = -5;
|
int power = -5;
|
||||||
if (sa.hasParam("Power")) {
|
if (sa.hasParam("Power")) {
|
||||||
power = AbilityUtils.calculateAmount(c, sa.getParam("Power"), sa);
|
power = AbilityUtils.calculateAmount(c, sa.getParam("Power"), sa);
|
||||||
@@ -179,7 +179,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SpellAbilityAi.isSorcerySpeed(sa) && !sa.hasParam("Permanent")) {
|
if (!SpellAbilityAi.isSorcerySpeed(sa) && !"Permanent".equals(sa.getParam("Duration"))) {
|
||||||
if (sa.hasParam("Crew") && c.isCreature()) {
|
if (sa.hasParam("Crew") && c.isCreature()) {
|
||||||
// Do not try to crew a vehicle which is already a creature
|
// Do not try to crew a vehicle which is already a creature
|
||||||
return false;
|
return false;
|
||||||
@@ -278,7 +278,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
Map<Card, Integer> data = Maps.newHashMap();
|
Map<Card, Integer> data = Maps.newHashMap();
|
||||||
for (final Card c : list) {
|
for (final Card c : list) {
|
||||||
// don't use Permanent animate on something that would leave the field
|
// don't use Permanent animate on something that would leave the field
|
||||||
if (c.hasSVar("EndOfTurnLeavePlay") && sa.hasParam("Permanent")) {
|
if (c.hasSVar("EndOfTurnLeavePlay") && "Permanent".equals(sa.getParam("Duration"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,9 +312,9 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
// if its player turn,
|
// if its player turn,
|
||||||
// check if its Permanent or that creature would attack
|
// check if its Permanent or that creature would attack
|
||||||
if (ph.isPlayerTurn(ai)) {
|
if (ph.isPlayerTurn(ai)) {
|
||||||
if (!sa.hasParam("Permanent")
|
if (!"Permanent".equals(sa.getParam("Duration"))
|
||||||
&& !ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animatedCopy)
|
&& !ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animatedCopy)
|
||||||
&& !sa.hasParam("UntilHostLeavesPlay")) {
|
&& !"UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,8 +360,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
|
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
|
||||||
// two are the only things
|
// two are the only things
|
||||||
// that animate a target. Those can just use AI:RemoveDeck:All until
|
// that animate a target. Those can just use AI:RemoveDeck:All until
|
||||||
// this can do a reasonably
|
// this can do a reasonably good job of picking a good target
|
||||||
// good job of picking a good target
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -555,12 +555,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (!evenBetterList.isEmpty()) {
|
if (!evenBetterList.isEmpty()) {
|
||||||
betterList = evenBetterList;
|
betterList = evenBetterList;
|
||||||
}
|
}
|
||||||
evenBetterList = CardLists.filter(betterList, new Predicate<Card>() {
|
evenBetterList = CardLists.filter(betterList, CardPredicates.Presets.UNTAPPED);
|
||||||
@Override
|
|
||||||
public boolean apply(final Card c) {
|
|
||||||
return c.isUntapped();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!evenBetterList.isEmpty()) {
|
if (!evenBetterList.isEmpty()) {
|
||||||
betterList = evenBetterList;
|
betterList = evenBetterList;
|
||||||
}
|
}
|
||||||
@@ -734,17 +729,15 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
private static Card attachAISpecificCardPreference(final SpellAbility sa, final List<Card> list, final boolean mandatory,
|
private static Card attachAISpecificCardPreference(final SpellAbility sa, final List<Card> list, final boolean mandatory,
|
||||||
final Card attachSource) {
|
final Card attachSource) {
|
||||||
// I know this isn't much better than Hardcoding, but some cards need it for now
|
|
||||||
final Player ai = sa.getActivatingPlayer();
|
final Player ai = sa.getActivatingPlayer();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
Card chosen = null;
|
Card chosen = null;
|
||||||
|
|
||||||
if ("Guilty Conscience".equals(sourceName)) {
|
if ("Guilty Conscience".equals(sourceName)) {
|
||||||
chosen = SpecialCardAi.GuiltyConscience.getBestAttachTarget(ai, sa, list);
|
chosen = SpecialCardAi.GuiltyConscience.getBestAttachTarget(ai, sa, list);
|
||||||
} else if ("Bonds of Faith".equals(sourceName)) {
|
} else if (sa.hasParam("AIValid")) {
|
||||||
chosen = doPumpOrCurseAILogic(ai, sa, list, "Human");
|
// TODO: Make the AI recognize which cards to pump based on the card's abilities alone
|
||||||
} else if ("Clutch of Undeath".equals(sourceName)) {
|
chosen = doPumpOrCurseAILogic(ai, sa, list, sa.getParam("AIValid"));
|
||||||
chosen = doPumpOrCurseAILogic(ai, sa, list, "Zombie");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Mandatory (brought directly into play without casting) gotta
|
// If Mandatory (brought directly into play without casting) gotta
|
||||||
@@ -768,7 +761,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
int powerBuff = 0;
|
int powerBuff = 0;
|
||||||
for (StaticAbility stAb : sa.getHostCard().getStaticAbilities()) {
|
for (StaticAbility stAb : sa.getHostCard().getStaticAbilities()) {
|
||||||
if ("Card.EquippedBy".equals(stAb.getParam("Affected")) && stAb.hasParam("AddPower")) {
|
if ("Card.EquippedBy".equals(stAb.getParam("Affected")) && stAb.hasParam("AddPower")) {
|
||||||
powerBuff = AbilityUtils.calculateAmount(sa.getHostCard(), stAb.getParam("AddPower"), null);
|
powerBuff = AbilityUtils.calculateAmount(sa.getHostCard(), stAb.getParam("AddPower"), stAb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (combat != null && combat.isAttacking(equipped) && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS, sa.getActivatingPlayer())) {
|
if (combat != null && combat.isAttacking(equipped) && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS, sa.getActivatingPlayer())) {
|
||||||
@@ -1646,7 +1639,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
|
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
|
||||||
} 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 ComputerUtilCombat.canAttackNextTurn(card) && CombatUtil.canBlock(card, true) && !ai.getCreaturesInPlay().isEmpty();
|
||||||
} else if (keyword.endsWith("CARDNAME can't block.") || keyword.contains("CantBlock")) {
|
} 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.")) {
|
||||||
for (SpellAbility ability : card.getSpellAbilities()) {
|
for (SpellAbility ability : card.getSpellAbilities()) {
|
||||||
@@ -1699,7 +1692,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (!c.getController().equals(ai)) {
|
if (!c.getController().equals(ai)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return c.getType().hasCreatureType(type);
|
return c.isValid(type, ai, sa.getHostCard(), sa);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
List<Card> oppNonType = CardLists.filter(list, new Predicate<Card>() {
|
List<Card> oppNonType = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@@ -1709,7 +1702,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (c.getController().equals(ai)) {
|
if (c.getController().equals(ai)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !c.getType().hasCreatureType(type) && !ComputerUtilCard.isUselessCreature(ai, c);
|
return !c.isValid(type, ai, sa.getHostCard(), sa) && !ComputerUtilCard.isUselessCreature(ai, c);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return sa.isTargetNumberValid(); // Pre-targeted in checkAiLogic
|
return sa.isTargetNumberValid(); // Pre-targeted in checkAiLogic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isHidden(sa)) {
|
if (sa.isHidden()) {
|
||||||
return hiddenOriginCanPlayAI(aiPlayer, sa);
|
return hiddenOriginCanPlayAI(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
return knownOriginCanPlayAI(aiPlayer, sa);
|
return knownOriginCanPlayAI(aiPlayer, sa);
|
||||||
@@ -182,21 +182,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
if (isHidden(sa)) {
|
if (sa.isHidden()) {
|
||||||
return hiddenOriginPlayDrawbackAI(aiPlayer, sa);
|
return hiddenOriginPlayDrawbackAI(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
return knownOriginPlayDrawbackAI(aiPlayer, sa);
|
return knownOriginPlayDrawbackAI(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static boolean isHidden(SpellAbility sa) {
|
|
||||||
boolean hidden = sa.hasParam("Hidden");
|
|
||||||
if (!hidden && sa.hasParam("Origin")) {
|
|
||||||
hidden = ZoneType.isHidden(sa.getParam("Origin"));
|
|
||||||
}
|
|
||||||
return hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* changeZoneTriggerAINoCost.
|
* changeZoneTriggerAINoCost.
|
||||||
@@ -232,7 +223,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return delta <= 0;
|
return delta <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHidden(sa)) {
|
if (sa.isHidden()) {
|
||||||
return hiddenTriggerAI(aiPlayer, sa, mandatory);
|
return hiddenTriggerAI(aiPlayer, sa, mandatory);
|
||||||
}
|
}
|
||||||
return knownOriginTriggerAI(aiPlayer, sa, mandatory);
|
return knownOriginTriggerAI(aiPlayer, sa, mandatory);
|
||||||
@@ -788,7 +779,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHidden(sa)) {
|
if (sa.isHidden()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1316,8 +1307,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
private static Card canBouncePermanent(final Player ai, SpellAbility sa, CardCollectionView list) {
|
private static Card canBouncePermanent(final Player ai, SpellAbility sa, CardCollectionView list) {
|
||||||
Game game = ai.getGame();
|
Game game = ai.getGame();
|
||||||
// filter out untargetables
|
// filter out untargetables
|
||||||
CardCollectionView aiPermanents = CardLists
|
CardCollectionView aiPermanents = CardLists.filterControlledBy(list, ai);
|
||||||
.filterControlledBy(list, ai);
|
|
||||||
CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS);
|
CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS);
|
||||||
|
|
||||||
// Felidar Guardian + Saheeli Rai combo support
|
// Felidar Guardian + Saheeli Rai combo support
|
||||||
@@ -1870,6 +1860,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!data.isEmpty()) {
|
||||||
// JAVA 1.8 use Map.Entry.comparingByValue() somehow
|
// JAVA 1.8 use Map.Entry.comparingByValue() somehow
|
||||||
Map.Entry<Player, Map.Entry<String, Integer>> max = Collections.max(data.entrySet(), new Comparator<Map.Entry<Player, Map.Entry<String, Integer>>>() {
|
Map.Entry<Player, Map.Entry<String, Integer>> max = Collections.max(data.entrySet(), new Comparator<Map.Entry<Player, Map.Entry<String, Integer>>>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -1888,6 +1879,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1987,8 +1979,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
boolean setPayX = false;
|
boolean setPayX = false;
|
||||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||||
setPayX = true;
|
setPayX = true;
|
||||||
// TODO use ComputerUtilCost.getMaxXValue if able
|
toPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
|
||||||
} else {
|
} else {
|
||||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,8 +52,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else if ("GideonBlackblade".equals(aiLogic)) {
|
} else if ("GideonBlackblade".equals(aiLogic)) {
|
||||||
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
|
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
|
||||||
} else if ("SoulEcho".equals(aiLogic)) {
|
|
||||||
return doTriggerAINoCost(ai, sa, true);
|
|
||||||
} else if ("Always".equals(aiLogic)) {
|
} else if ("Always".equals(aiLogic)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -70,7 +68,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
return checkApiLogic(aiPlayer, sa);
|
return sa.isTrigger() ? doTriggerAINoCost(aiPlayer, sa, sa.isMandatory()) : checkApiLogic(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
|
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
|
||||||
|
|
||||||
// for creatures that could be improved (like Figure of Destiny)
|
// for creatures that could be improved (like Figure of Destiny)
|
||||||
if (c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
|
if (c.isCreature() && (!sa.hasParam("Duration") || (!c.isTapped() && !c.isSick()))) {
|
||||||
int power = -5;
|
int power = -5;
|
||||||
if (sa.hasParam("Power")) {
|
if (sa.hasParam("Power")) {
|
||||||
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
||||||
@@ -72,8 +72,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bFlag) { // All of the defined stuff is cloned, not very
|
if (!bFlag) { // All of the defined stuff is cloned, not very useful
|
||||||
// useful
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -246,7 +245,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
// Combat_Begin step
|
// Combat_Begin step
|
||||||
if (!ph.is(PhaseType.COMBAT_BEGIN)
|
if (!ph.is(PhaseType.COMBAT_BEGIN)
|
||||||
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
&& !sa.hasParam("ActivationPhases") && sa.hasParam("Duration")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +256,6 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// don't activate during main2 unless this effect is permanent
|
// don't activate during main2 unless this effect is permanent
|
||||||
return !ph.is(PhaseType.MAIN2) || sa.hasParam("Permanent");
|
return !ph.is(PhaseType.MAIN2) || !sa.hasParam("Duration");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
CardCollection list =
|
CardCollection list =
|
||||||
CardLists.getValidCards(AiAttackController.choosePreferredDefenderPlayer(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
CardLists.getValidCards(AiAttackController.choosePreferredDefenderPlayer(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||||
// AI won't try to grab cards that are filtered out of AI decks on
|
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||||
// purpose
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -110,8 +110,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't steal something if I can't Attack without, or prevent it from
|
// Don't steal something if I can't Attack without, or prevent it from blocking at least
|
||||||
// blocking at least
|
|
||||||
if (lose.contains("EOT")
|
if (lose.contains("EOT")
|
||||||
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& !sa.isTrigger()) {
|
&& !sa.isTrigger()) {
|
||||||
@@ -211,6 +210,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO check life of controller and consider stealing from another opponent so the risk of your army disappearing is spread out
|
||||||
while (t == null) {
|
while (t == null) {
|
||||||
// filter by MustTarget requirement
|
// filter by MustTarget requirement
|
||||||
CardCollection originalList = new CardCollection(list);
|
CardCollection originalList = new CardCollection(list);
|
||||||
@@ -264,7 +264,6 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -113,8 +113,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
boolean setPayX = false;
|
boolean setPayX = false;
|
||||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||||
setPayX = true;
|
setPayX = true;
|
||||||
// TODO use ComputerUtilCost.getMaxXValue
|
toPay = Math.min(ComputerUtilCost.getMaxXValue(sa, ai), usableManaSources + 1);
|
||||||
toPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), usableManaSources + 1);
|
|
||||||
} else {
|
} else {
|
||||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||||
}
|
}
|
||||||
@@ -124,8 +123,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (toPay <= usableManaSources) {
|
if (toPay <= usableManaSources) {
|
||||||
// If this is a reusable Resource, feel free to play it most of
|
// If this is a reusable Resource, feel free to play it most of the time
|
||||||
// the time
|
|
||||||
if (!SpellAbilityAi.playReusable(ai,sa)) {
|
if (!SpellAbilityAi.playReusable(ai,sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,8 +120,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
|
||||||
// pre filter targetable cards with counters and can receive one of
|
// pre filter targetable cards with counters and can receive one of them
|
||||||
// them
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -866,6 +866,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
choice = Aggregates.random(list);
|
choice = Aggregates.random(list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (choice != null && divided) {
|
if (choice != null && divided) {
|
||||||
int alloc = Math.max(amount / totalTargets, 1);
|
int alloc = Math.max(amount / totalTargets, 1);
|
||||||
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
||||||
@@ -875,8 +876,6 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
left -= alloc;
|
left -= alloc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (choice != null) {
|
if (choice != null) {
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
|
|||||||
@@ -111,8 +111,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
|||||||
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
|
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
|
||||||
|
|
||||||
if ((enemy.getLife() - restDamage) < 5) {
|
if ((enemy.getLife() - restDamage) < 5) {
|
||||||
// drop the human to less than 5
|
// drop the human to less than 5 life
|
||||||
// life
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import forge.game.ability.AbilityUtils;
|
|||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
@@ -41,7 +40,6 @@ import forge.game.keyword.Keyword;
|
|||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerPredicates;
|
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetChoices;
|
import forge.game.spellability.TargetChoices;
|
||||||
@@ -132,7 +130,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
// Set PayX here to maximum value. It will be adjusted later depending on the target.
|
// Set PayX here to maximum value. It will be adjusted later depending on the target.
|
||||||
sa.setXManaCostPaid(dmg);
|
sa.setXManaCostPaid(dmg);
|
||||||
} else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) {
|
} else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) {
|
||||||
dmg = CardFactoryUtil.xCount(source, sa.getSVar(damage)) - 1; // the card will be spent casting the spell, so actual damage is 1 less
|
dmg = AbilityUtils.calculateAmount(source, damage, sa) - 1; // the card will be spent casting the spell, so actual damage is 1 less
|
||||||
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
|
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
|
||||||
// cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact
|
// cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact
|
||||||
if (sa.getTargetRestrictions().canTgtPlayer()) {
|
if (sa.getTargetRestrictions().canTgtPlayer()) {
|
||||||
@@ -734,12 +732,10 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// When giving priority to targeting Creatures for mandatory
|
// When giving priority to targeting Creatures for mandatory
|
||||||
// triggers
|
// triggers feel free to add the Human after we run out of good targets
|
||||||
// feel free to add the Human after we run out of good targets
|
|
||||||
|
|
||||||
// TODO: add check here if card is about to die from something
|
// TODO: add check here if card is about to die from something
|
||||||
// on the stack
|
// on the stack or from taking combat damage
|
||||||
// or from taking combat damage
|
|
||||||
|
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
boolean freePing = immediately || abCost == null
|
boolean freePing = immediately || abCost == null
|
||||||
@@ -796,8 +792,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: Improve Damage, we shouldn't just target the player just
|
// TODO: Improve Damage, we shouldn't just target the player just because we can
|
||||||
// because we can
|
|
||||||
if (sa.canTarget(enemy) && tcs.size() < tgt.getMaxTargets(source, sa)) {
|
if (sa.canTarget(enemy) && tcs.size() < tgt.getMaxTargets(source, sa)) {
|
||||||
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||||
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
||||||
@@ -985,7 +980,6 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
||||||
@@ -1042,7 +1036,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
saTgt = saTgt.getParent();
|
saTgt = saTgt.getParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
Player opponent = ai.getOpponents().min(PlayerPredicates.compareByLife());
|
Player opponent = ai.getWeakestOpponent();
|
||||||
|
|
||||||
// TODO: somehow account for the possible cost reduction?
|
// TODO: somehow account for the possible cost reduction?
|
||||||
int dmg = ComputerUtilMana.determineLeftoverMana(sa, ai, saTgt.getParam("XColor"));
|
int dmg = ComputerUtilMana.determineLeftoverMana(sa, ai, saTgt.getParam("XColor"));
|
||||||
@@ -1085,7 +1079,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
saTgt.resetTargets();
|
saTgt.resetTargets();
|
||||||
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
|
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
|
||||||
|
|
||||||
sa.setXManaCostPaid(dmg);
|
saTgt.setXManaCostPaid(dmg);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,9 +45,8 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
sa.getTargets().add(opp);
|
|
||||||
}
|
}
|
||||||
|
sa.getTargets().add(opp);
|
||||||
libraryOwner = opp;
|
libraryOwner = opp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,9 +28,8 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
sa.getTargets().add(opp);
|
|
||||||
}
|
}
|
||||||
|
sa.getTargets().add(opp);
|
||||||
libraryOwner = opp;
|
libraryOwner = opp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,9 +60,8 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!sa.canTarget(opp)) {
|
if (!sa.canTarget(opp)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
sa.getTargets().add(opp);
|
|
||||||
}
|
}
|
||||||
|
sa.getTargets().add(opp);
|
||||||
libraryOwner = opp;
|
libraryOwner = opp;
|
||||||
} else {
|
} else {
|
||||||
if (sa.hasParam("Valid")) {
|
if (sa.hasParam("Valid")) {
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
} else {
|
} else {
|
||||||
numCards = ComputerUtilCost.getMaxXValue(sa, ai);
|
numCards = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
// try not to overdraw
|
// try not to overdraw
|
||||||
int safeDraw = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
|
int safeDraw = Math.abs(Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3));
|
||||||
if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent
|
if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent
|
||||||
numCards = Math.min(numCards, safeDraw);
|
numCards = Math.min(numCards, safeDraw);
|
||||||
|
|
||||||
@@ -377,7 +377,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
// checks what the ai prevent from casting it on itself
|
// checks what the ai prevent from casting it on itself
|
||||||
// if spell is not mandatory
|
// if spell is not mandatory
|
||||||
if (aiTarget && !ai.cantLose()) {
|
if (aiTarget && !ai.cantLose()) {
|
||||||
if (numCards >= computerLibrarySize) {
|
if (numCards >= computerLibrarySize - 3) {
|
||||||
if (xPaid) {
|
if (xPaid) {
|
||||||
numCards = computerLibrarySize - 1;
|
numCards = computerLibrarySize - 1;
|
||||||
if (numCards <= 0 && !mandatory) {
|
if (numCards <= 0 && !mandatory) {
|
||||||
@@ -422,8 +422,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
root.setXManaCostPaid(numCards);
|
root.setXManaCostPaid(numCards);
|
||||||
} else {
|
} else {
|
||||||
// Don't draw too many cards and then risk discarding
|
// Don't draw too many cards and then risk discarding cards at EOT
|
||||||
// cards at EOT
|
|
||||||
if (!drawback && !mandatory) {
|
if (!drawback && !mandatory) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -441,7 +440,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// use xPaid abilties only for itself
|
// use xPaid abilities only for itself
|
||||||
if (xPaid) {
|
if (xPaid) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -493,7 +492,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
// TODO: consider if human is the defined player
|
// TODO: consider if human is the defined player
|
||||||
|
|
||||||
// ability is not targeted
|
// ability is not targeted
|
||||||
if (numCards >= computerLibrarySize) {
|
if (numCards >= computerLibrarySize - 3) {
|
||||||
if (ai.isCardInPlay("Laboratory Maniac")) {
|
if (ai.isCardInPlay("Laboratory Maniac")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -509,8 +508,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
&& game.getPhaseHandler().isPlayerTurn(ai)
|
&& game.getPhaseHandler().isPlayerTurn(ai)
|
||||||
&& !sa.isTrigger()
|
&& !sa.isTrigger()
|
||||||
&& !assumeSafeX) {
|
&& !assumeSafeX) {
|
||||||
// Don't draw too many cards and then risk discarding cards at
|
// Don't draw too many cards and then risk discarding cards at EOT
|
||||||
// EOT
|
|
||||||
if (!drawback) {
|
if (!drawback) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class FogAi extends SpellAbilityAi {
|
|||||||
final Card hostCard = sa.getHostCard();
|
final Card hostCard = sa.getHostCard();
|
||||||
|
|
||||||
// Don't cast it, if the effect is already in place
|
// Don't cast it, if the effect is already in place
|
||||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ public class GameLossAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only one SA Lose the Game card right now, which is Door to
|
// Only one SA Lose the Game card right now, which is Door to Nothingness
|
||||||
// Nothingness
|
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
@@ -29,19 +28,23 @@ public class GameLossAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
Player loser = ai;
|
||||||
|
|
||||||
// Phage the Untouchable
|
// Phage the Untouchable
|
||||||
// (Final Fortune would need to attach it's delayed trigger to a
|
// (Final Fortune would need to attach it's delayed trigger to a
|
||||||
// specific turn, which can't be done yet)
|
// specific turn, which can't be done yet)
|
||||||
Player opp = ai.getGame().getCombat().getDefenderPlayerByAttacker(sa.getHostCard());
|
if (ai.getGame().getCombat() != null) {
|
||||||
|
loser = ai.getGame().getCombat().getDefenderPlayerByAttacker(sa.getHostCard());
|
||||||
|
}
|
||||||
|
|
||||||
if (!mandatory && opp.cantLose()) {
|
if (!mandatory && loser.cantLose()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(loser);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -266,8 +266,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if (!hasTgt && mandatory) {
|
if (!hasTgt && mandatory) {
|
||||||
// need to target something but its neither negative against
|
// need to target something but its neither negative against
|
||||||
// opponents,
|
// opponents, nor positive against allies
|
||||||
// nor posive against allies
|
|
||||||
|
|
||||||
// hurting ally is probably better than healing opponent
|
// hurting ally is probably better than healing opponent
|
||||||
// look for Lifegain not Negative (case of lifegain negated)
|
// look for Lifegain not Negative (case of lifegain negated)
|
||||||
@@ -295,8 +294,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(ally);
|
sa.getTargets().add(ally);
|
||||||
hasTgt = true;
|
hasTgt = true;
|
||||||
}
|
}
|
||||||
// better heal opponent which most life then the one with the
|
// better heal opponent which most life then the one with the lowest
|
||||||
// lowest
|
|
||||||
if (!hasTgt) {
|
if (!hasTgt) {
|
||||||
Player opp = opps.max(PlayerPredicates.compareByLife());
|
Player opp = opps.max(PlayerPredicates.compareByLife());
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
|
||||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
@@ -60,7 +59,6 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
@@ -123,12 +121,12 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
|
||||||
|
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||||
|
// TODO: check against the amount we could obtain when multiple activations are possible
|
||||||
PlayerCollection filteredPlayer = tgtPlayers
|
PlayerCollection filteredPlayer = tgtPlayers
|
||||||
.filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount)));
|
.filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount)));
|
||||||
// killing opponents asap
|
// killing opponents asap
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -214,13 +215,7 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get targeted or defined Player with largest library
|
// get targeted or defined Player with largest library
|
||||||
// TODO in Java 8 find better way
|
final Player m = Collections.max(list, PlayerPredicates.compareByZoneSize(ZoneType.Library));
|
||||||
final Player m = Collections.max(list, new Comparator<Player>() {
|
|
||||||
@Override
|
|
||||||
public int compare(Player arg0, Player arg1) {
|
|
||||||
return arg0.getCardsIn(ZoneType.Library).size() - arg1.getCardsIn(ZoneType.Library).size();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
int cardsToDiscard = m.getCardsIn(ZoneType.Library).size();
|
int cardsToDiscard = m.getCardsIn(ZoneType.Library).size();
|
||||||
|
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return checkApiLogic(ai, sa);
|
return mandatory || checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import forge.game.card.CardCollection;
|
|||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,10 +39,8 @@ public class PermanentNoncreatureAi extends PermanentAi {
|
|||||||
if (host.hasSVar("OblivionRing")) {
|
if (host.hasSVar("OblivionRing")) {
|
||||||
SpellAbility effectExile = AbilityFactory.getAbility(host.getSVar("TrigExile"), host);
|
SpellAbility effectExile = AbilityFactory.getAbility(host.getSVar("TrigExile"), host);
|
||||||
final ZoneType origin = ZoneType.listValueOf(effectExile.getParam("Origin")).get(0);
|
final ZoneType origin = ZoneType.listValueOf(effectExile.getParam("Origin")).get(0);
|
||||||
final TargetRestrictions tgt = effectExile.getTargetRestrictions();
|
effectExile.setActivatingPlayer(ai);
|
||||||
final CardCollection list = CardLists.getValidCards(game.getCardsIn(origin), tgt.getValidTgts(), ai, host,
|
CardCollection targets = CardLists.getTargetableCards(game.getCardsIn(origin), effectExile);
|
||||||
effectExile);
|
|
||||||
CardCollection targets = CardLists.getTargetableCards(list, sa);
|
|
||||||
if (sourceName.equals("Suspension Field")
|
if (sourceName.equals("Suspension Field")
|
||||||
|| sourceName.equals("Detention Sphere")) {
|
|| sourceName.equals("Detention Sphere")) {
|
||||||
// existing "exile until leaves" enchantments only target
|
// existing "exile until leaves" enchantments only target
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import forge.game.card.CardPredicates;
|
|||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
|
||||||
import forge.game.spellability.Spell;
|
import forge.game.spellability.Spell;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.SpellAbilityPredicates;
|
import forge.game.spellability.SpellAbilityPredicates;
|
||||||
@@ -159,15 +158,6 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
|
||||||
// as called from PlayEffect:173
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -400,7 +400,6 @@ public class PumpAi extends PumpAiBase {
|
|||||||
if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) {
|
if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
||||||
|
|
||||||
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
||||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai)
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai)
|
||||||
|| game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) {
|
|| game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) {
|
||||||
@@ -438,7 +437,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
&& !(sa.isCurse() && defense < 0)
|
&& !(sa.isCurse() && defense < 0)
|
||||||
&& !containsNonCombatKeyword(keywords)
|
&& !containsNonCombatKeyword(keywords)
|
||||||
&& !sa.hasParam("UntilYourNextTurn")
|
&& !"UntilYourNextTurn".equals(sa.getParam("Duration"))
|
||||||
&& !"Snapcaster".equals(sa.getParam("AILogic"))
|
&& !"Snapcaster".equals(sa.getParam("AILogic"))
|
||||||
&& !isFight) {
|
&& !isFight) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean grantsUsefulExtraBlockOpts(final Player ai, final SpellAbility sa, final Card card, List<String> keywords) {
|
public boolean grantsUsefulExtraBlockOpts(final Player ai, final SpellAbility sa, final Card card, List<String> keywords) {
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
||||||
@@ -110,7 +109,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
&& (card.getNetCombatDamage() > 0)
|
&& (card.getNetCombatDamage() > 0)
|
||||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
|
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
|
||||||
} else if (keyword.endsWith("CARDNAME can't attack or block.")) {
|
} else if (keyword.endsWith("CARDNAME can't attack or block.")) {
|
||||||
if (sa.hasParam("UntilYourNextTurn")) {
|
if ("UntilYourNextTurn".equals(sa.getParam("Duration"))) {
|
||||||
return CombatUtil.canAttack(card, ai) || CombatUtil.canBlock(card, true);
|
return CombatUtil.canAttack(card, ai) || CombatUtil.canBlock(card, true);
|
||||||
}
|
}
|
||||||
if (!ph.isPlayerTurn(ai)) {
|
if (!ph.isPlayerTurn(ai)) {
|
||||||
@@ -154,14 +153,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return CombatUtil.canBlockAtLeastOne(card, attackers);
|
return CombatUtil.canBlockAtLeastOne(card, attackers);
|
||||||
} else if (keyword.endsWith("CantBlockCardUIDSource")) { // can't block CARDNAME this turn
|
|
||||||
if (!ph.isPlayerTurn(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
|
||||||
|| ph.getPhase().isBefore(PhaseType.MAIN1) || !CombatUtil.canBlock(sa.getHostCard(), card)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// target needs to be a creature, controlled by the player which is attacked
|
|
||||||
return !sa.getHostCard().isTapped() || (combat != null && combat.isAttacking(sa.getHostCard())
|
|
||||||
&& card.getController().equals(combat.getDefenderPlayerByAttacker(sa.getHostCard())));
|
|
||||||
} else if (keyword.endsWith("This card doesn't untap during your next untap step.")) {
|
} else if (keyword.endsWith("This card doesn't untap during your next untap step.")) {
|
||||||
return !ph.getPhase().isBefore(PhaseType.MAIN2) && !card.isUntapped() && ph.isPlayerTurn(ai)
|
return !ph.getPhase().isBefore(PhaseType.MAIN2) && !card.isUntapped() && ph.isPlayerTurn(ai)
|
||||||
&& Untap.canUntap(card);
|
&& Untap.canUntap(card);
|
||||||
@@ -480,7 +471,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}); // leaves all creatures that will be destroyed
|
}); // leaves all creatures that will be destroyed
|
||||||
} // -X/-X end
|
} // -X/-X end
|
||||||
else if (attack < 0 && !game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
else if (attack < 0 && !game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
// spells that give -X/0
|
// spells that give -X/0
|
||||||
boolean isMyTurn = game.getPhaseHandler().isPlayerTurn(ai);
|
boolean isMyTurn = game.getPhaseHandler().isPlayerTurn(ai);
|
||||||
if (isMyTurn) {
|
if (isMyTurn) {
|
||||||
@@ -514,7 +505,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
else {
|
else {
|
||||||
final boolean addsKeywords = !keywords.isEmpty();
|
final boolean addsKeywords = !keywords.isEmpty();
|
||||||
if (addsKeywords) {
|
if (addsKeywords) {
|
||||||
|
|
||||||
// If the keyword can prevent a creature from attacking, see if there's some kind of viable prioritization
|
// If the keyword can prevent a creature from attacking, see if there's some kind of viable prioritization
|
||||||
if (keywords.contains("CARDNAME can't attack.") || keywords.contains("CARDNAME can't attack or block.")
|
if (keywords.contains("CARDNAME can't attack.") || keywords.contains("CARDNAME can't attack or block.")
|
||||||
|| keywords.contains("HIDDEN CARDNAME can't attack.") || keywords.contains("HIDDEN CARDNAME can't attack or block.")) {
|
|| keywords.contains("HIDDEN CARDNAME can't attack.") || keywords.contains("HIDDEN CARDNAME can't attack or block.")) {
|
||||||
@@ -540,8 +530,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
|
|
||||||
protected boolean containsNonCombatKeyword(final List<String> keywords) {
|
protected boolean containsNonCombatKeyword(final List<String> keywords) {
|
||||||
for (final String keyword : keywords) {
|
for (final String keyword : keywords) {
|
||||||
// since most keywords are combat relevant check for those that are
|
// since most keywords are combat relevant check for those that are not
|
||||||
// not
|
|
||||||
if (keyword.endsWith("This card doesn't untap during your next untap step.")
|
if (keyword.endsWith("This card doesn't untap during your next untap step.")
|
||||||
|| keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) {
|
|| keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|| phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
|| phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())
|
|| game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())
|
||||||
|| game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
|| game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int totalPower = 0;
|
int totalPower = 0;
|
||||||
@@ -151,6 +151,12 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
// important to call canPlay first so targets are added if needed
|
||||||
|
return canPlayAI(ai, sa) || mandatory;
|
||||||
|
}
|
||||||
|
|
||||||
boolean pumpAgainstRemoval(Player ai, SpellAbility sa, List<Card> comp) {
|
boolean pumpAgainstRemoval(Player ai, SpellAbility sa, List<Card> comp) {
|
||||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||||
for (final Card c : comp) {
|
for (final Card c : comp) {
|
||||||
|
|||||||
@@ -49,14 +49,6 @@ import forge.game.zone.ZoneType;
|
|||||||
*/
|
*/
|
||||||
public class RegenerateAi extends SpellAbilityAi {
|
public class RegenerateAi extends SpellAbilityAi {
|
||||||
|
|
||||||
// Ex: A:SP$Regenerate | Cost$W | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$Regenerate
|
|
||||||
// target creature.
|
|
||||||
// http://www.slightlymagic.net/wiki/Forge_AbilityFactory#Regenerate
|
|
||||||
|
|
||||||
// **************************************************************
|
|
||||||
// ********************* Regenerate ****************************
|
|
||||||
// **************************************************************
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
@@ -65,8 +57,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
// As far as I can tell these Defined Cards will only have one of
|
// As far as I can tell these Defined Cards will only have one of them
|
||||||
// them
|
|
||||||
final List<Card> list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa);
|
final List<Card> list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
@@ -105,8 +96,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
// check stack for something on the stack will kill anything i
|
// check stack for something on the stack will kill anything i control
|
||||||
// control
|
|
||||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||||
|
|
||||||
final List<Card> threatenedTargets = new ArrayList<>();
|
final List<Card> threatenedTargets = new ArrayList<>();
|
||||||
@@ -191,8 +181,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO see if something on the stack is about to kill something i
|
// TODO see if something on the stack is about to kill something i can target
|
||||||
// can target
|
|
||||||
|
|
||||||
// choose my best X without regen
|
// choose my best X without regen
|
||||||
if (CardLists.getNotType(compTargetables, "Creature").isEmpty()) {
|
if (CardLists.getNotType(compTargetables, "Creature").isEmpty()) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class RepeatAi extends SpellAbilityAi {
|
|||||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!sa.canTarget(opp)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|||||||
@@ -119,11 +119,9 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
double chance = .4; // 40 percent chance of milling with instant speed
|
double chance = .4; // 40 percent chance of milling with instant speed stuff
|
||||||
// stuff
|
|
||||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
chance = .667; // 66.7% chance for sorcery speed (since it will
|
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
|
||||||
// never activate EOT)
|
|
||||||
}
|
}
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,7 @@ public class SetStateAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
// Gross generalization, but this always considers alternate
|
// Gross generalization, but this always considers alternate states more powerful
|
||||||
// states more powerful
|
|
||||||
return !sa.getHostCard().isInAlternateState();
|
return !sa.getHostCard().isInAlternateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ public class ShuffleAi extends SpellAbilityAi {
|
|||||||
return aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN2, aiPlayer);
|
return aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN2, aiPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// not really sure when the compy would use this; maybe only after a
|
// not really sure when the compy would use this; maybe only after a human
|
||||||
// human
|
|
||||||
// deliberately put a card on top of their library
|
// deliberately put a card on top of their library
|
||||||
return false;
|
return false;
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import forge.ai.ComputerUtilAbility;
|
|||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
@@ -309,9 +310,8 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use Defined to determine, if this is an unfavorable result
|
final List<Card> pDefined = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
|
return pDefined.isEmpty() || (pDefined.get(0).isUntapped() && pDefined.get(0).getController() != ai);
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (tapPrefTargeting(ai, source, sa, mandatory)) {
|
if (tapPrefTargeting(ai, source, sa, mandatory)) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import forge.game.ability.AbilityUtils;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -49,7 +49,7 @@ public class TapAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validTappables = CardLists.getValidCards(validTappables, valid, source.getController(), source, sa);
|
validTappables = CardLists.getValidCards(validTappables, valid, source.getController(), source, sa);
|
||||||
validTappables = CardLists.filter(validTappables, Presets.UNTAPPED);
|
validTappables = CardLists.filter(validTappables, CardPredicates.Presets.UNTAPPED);
|
||||||
|
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
@@ -69,18 +69,8 @@ public class TapAllAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Card> human = CardLists.filter(validTappables, new Predicate<Card>() {
|
final List<Card> human = CardLists.filterControlledBy(validTappables, opp);
|
||||||
@Override
|
final List<Card> compy = CardLists.filterControlledBy(validTappables, ai);
|
||||||
public boolean apply(final Card c) {
|
|
||||||
return c.getController().equals(opp);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final List<Card> compy = CardLists.filter(validTappables, new Predicate<Card>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(final Card c) {
|
|
||||||
return c.getController().equals(ai);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (human.size() <= compy.size()) {
|
if (human.size() <= compy.size()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -102,7 +92,7 @@ public class TapAllAi extends SpellAbilityAi {
|
|||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
CardCollectionView tmpList = game.getCardsIn(ZoneType.Battlefield);
|
CardCollectionView tmpList = game.getCardsIn(ZoneType.Battlefield);
|
||||||
tmpList = CardLists.getValidCards(tmpList, valid, source.getController(), source, sa);
|
tmpList = CardLists.getValidCards(tmpList, valid, source.getController(), source, sa);
|
||||||
tmpList = CardLists.filter(tmpList, Presets.UNTAPPED);
|
tmpList = CardLists.filter(tmpList, CardPredicates.Presets.UNTAPPED);
|
||||||
return tmpList;
|
return tmpList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ public class TapOrUntapAi extends TapAiBase {
|
|||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
// assume we are looking to tap human's stuff
|
// assume we are looking to tap human's stuff
|
||||||
// TODO - check for things with untap abilities, and don't tap
|
// TODO - check for things with untap abilities, and don't tap those.
|
||||||
// those.
|
|
||||||
|
|
||||||
boolean bFlag = false;
|
boolean bFlag = false;
|
||||||
for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) {
|
for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) {
|
||||||
@@ -40,6 +39,4 @@ public class TapOrUntapAi extends TapAiBase {
|
|||||||
return randomReturn;
|
return randomReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
final List<Card> pDefined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
final List<Card> pDefined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai;
|
return pDefined.isEmpty() || (pDefined.get(0).isTapped() && pDefined.get(0).getController() == ai);
|
||||||
} else {
|
} else {
|
||||||
return untapPrefTargeting(ai, sa, false);
|
return untapPrefTargeting(ai, sa, false);
|
||||||
}
|
}
|
||||||
@@ -82,9 +82,8 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use Defined to determine, if this is an unfavorable result
|
|
||||||
final List<Card> pDefined = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
final List<Card> pDefined = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai;
|
return pDefined.isEmpty() || (pDefined.get(0).isTapped() && pDefined.get(0).getController() == ai);
|
||||||
} else {
|
} else {
|
||||||
if (untapPrefTargeting(ai, sa, mandatory)) {
|
if (untapPrefTargeting(ai, sa, mandatory)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import forge.game.card.CardLists;
|
|||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerCollection;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -29,6 +30,10 @@ public class UntapAllAi extends SpellAbilityAi {
|
|||||||
valid = sa.getParam("ValidCards");
|
valid = sa.getParam("ValidCards");
|
||||||
}
|
}
|
||||||
list = CardLists.getValidCards(list, valid.split(","), source.getController(), source, sa);
|
list = CardLists.getValidCards(list, valid.split(","), source.getController(), source, sa);
|
||||||
|
// don't untap if only opponent benefits
|
||||||
|
PlayerCollection goodControllers = aiPlayer.getAllies();
|
||||||
|
goodControllers.add(aiPlayer);
|
||||||
|
list = CardLists.filter(list, CardPredicates.isControlledByAnyOf(goodControllers));
|
||||||
return !list.isEmpty();
|
return !list.isEmpty();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ public class GameCopier {
|
|||||||
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());
|
||||||
newPlayer.setPreventNextDamage(origPlayer.getPreventNextDamage());
|
|
||||||
newPlayer.getManaPool().add(origPlayer.getManaPool());
|
newPlayer.getManaPool().add(origPlayer.getManaPool());
|
||||||
newPlayer.setCommanders(origPlayer.getCommanders()); // will be fixed up below
|
newPlayer.setCommanders(origPlayer.getCommanders()); // will be fixed up below
|
||||||
playerMap.put(origPlayer, newPlayer);
|
playerMap.put(origPlayer, newPlayer);
|
||||||
|
|||||||
@@ -151,6 +151,11 @@ public class StaticData {
|
|||||||
return this.editions;
|
return this.editions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final CardEdition.Collection getCustomEditions(){
|
||||||
|
return this.customEditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private List<CardEdition> sortedEditions;
|
private List<CardEdition> sortedEditions;
|
||||||
public final List<CardEdition> getSortedEditions() {
|
public final List<CardEdition> getSortedEditions() {
|
||||||
if (sortedEditions == null) {
|
if (sortedEditions == null) {
|
||||||
@@ -158,12 +163,24 @@ public class StaticData {
|
|||||||
for (CardEdition set : editions) {
|
for (CardEdition set : editions) {
|
||||||
sortedEditions.add(set);
|
sortedEditions.add(set);
|
||||||
}
|
}
|
||||||
|
if (customEditions.size() > 0){
|
||||||
|
for (CardEdition set : customEditions) {
|
||||||
|
sortedEditions.add(set);
|
||||||
|
}
|
||||||
|
}
|
||||||
Collections.sort(sortedEditions);
|
Collections.sort(sortedEditions);
|
||||||
Collections.reverse(sortedEditions); //put newer sets at the top
|
Collections.reverse(sortedEditions); //put newer sets at the top
|
||||||
}
|
}
|
||||||
return sortedEditions;
|
return sortedEditions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CardEdition getCardEdition(String setCode){
|
||||||
|
CardEdition edition = this.editions.get(setCode);
|
||||||
|
if (edition == null) // try custom editions
|
||||||
|
edition = this.customEditions.get(setCode);
|
||||||
|
return edition;
|
||||||
|
}
|
||||||
|
|
||||||
public PaperCard getOrLoadCommonCard(String cardName, String setCode, int artIndex, boolean foil) {
|
public PaperCard getOrLoadCommonCard(String cardName, String setCode, int artIndex, boolean foil) {
|
||||||
PaperCard card = commonCards.getCard(cardName, setCode, artIndex);
|
PaperCard card = commonCards.getCard(cardName, setCode, artIndex);
|
||||||
boolean isCustom = false;
|
boolean isCustom = false;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package forge.card;
|
|||||||
*/
|
*/
|
||||||
public class CardAiHints {
|
public class CardAiHints {
|
||||||
|
|
||||||
|
|
||||||
private final boolean isRemovedFromAIDecks;
|
private final boolean isRemovedFromAIDecks;
|
||||||
private final boolean isRemovedFromRandomDecks;
|
private final boolean isRemovedFromRandomDecks;
|
||||||
private final boolean isRemovedFromNonCommanderDecks;
|
private final boolean isRemovedFromNonCommanderDecks;
|
||||||
@@ -15,7 +14,6 @@ public class CardAiHints {
|
|||||||
private final DeckHints deckNeeds;
|
private final DeckHints deckNeeds;
|
||||||
private final DeckHints deckHas;
|
private final DeckHints deckHas;
|
||||||
|
|
||||||
|
|
||||||
public CardAiHints(boolean remAi, boolean remRandom, boolean remUnlessCommander, DeckHints dh, DeckHints dn, DeckHints has) {
|
public CardAiHints(boolean remAi, boolean remRandom, boolean remUnlessCommander, DeckHints dh, DeckHints dn, DeckHints has) {
|
||||||
isRemovedFromAIDecks = remAi;
|
isRemovedFromAIDecks = remAi;
|
||||||
isRemovedFromRandomDecks = remRandom;
|
isRemovedFromRandomDecks = remRandom;
|
||||||
@@ -90,5 +88,4 @@ public class CardAiHints {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -533,6 +533,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
@Override
|
@Override
|
||||||
public int getPrintCount(String cardName, String edition) {
|
public int getPrintCount(String cardName, String edition) {
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
|
if (edition == null || cardName == null)
|
||||||
|
return cnt;
|
||||||
for (PaperCard pc : getAllCards(cardName)) {
|
for (PaperCard pc : getAllCards(cardName)) {
|
||||||
if (pc.getEdition().equals(edition)) {
|
if (pc.getEdition().equals(edition)) {
|
||||||
cnt++;
|
cnt++;
|
||||||
@@ -544,6 +546,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
@Override
|
@Override
|
||||||
public int getMaxPrintCount(String cardName) {
|
public int getMaxPrintCount(String cardName) {
|
||||||
int max = -1;
|
int max = -1;
|
||||||
|
if (cardName == null)
|
||||||
|
return max;
|
||||||
for (PaperCard pc : getAllCards(cardName)) {
|
for (PaperCard pc : getAllCards(cardName)) {
|
||||||
if (max < pc.getArtIndex()) {
|
if (max < pc.getArtIndex()) {
|
||||||
max = pc.getArtIndex();
|
max = pc.getArtIndex();
|
||||||
@@ -555,6 +559,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
@Override
|
@Override
|
||||||
public int getArtCount(String cardName, String setName) {
|
public int getArtCount(String cardName, String setName) {
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
|
if (cardName == null || setName == null)
|
||||||
|
return cnt;
|
||||||
|
|
||||||
Collection<PaperCard> cards = getAllCards(cardName);
|
Collection<PaperCard> cards = getAllCards(cardName);
|
||||||
if (null == cards) {
|
if (null == cards) {
|
||||||
@@ -628,6 +634,23 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collection<PaperCard> getAllNonPromosNonReprintsNoAlt() {
|
||||||
|
return Lists.newArrayList(Iterables.filter(getAllCardsNoAlt(), new Predicate<PaperCard>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final PaperCard paperCard) {
|
||||||
|
CardEdition edition = null;
|
||||||
|
try {
|
||||||
|
edition = editions.getEditionByCodeOrThrow(paperCard.getEdition());
|
||||||
|
if (edition.getType() == Type.PROMOS||edition.getType() == Type.REPRINT)
|
||||||
|
return false;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
public String getName(final String cardName) {
|
public String getName(final String cardName) {
|
||||||
if (alternateName.containsKey(cardName)) {
|
if (alternateName.containsKey(cardName)) {
|
||||||
return alternateName.get(cardName);
|
return alternateName.get(cardName);
|
||||||
@@ -749,7 +772,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PaperCard createUnsupportedCard(String cardName) {
|
public PaperCard createUnsupportedCard(String cardName) {
|
||||||
|
|
||||||
CardRequest request = CardRequest.fromString(cardName);
|
CardRequest request = CardRequest.fromString(cardName);
|
||||||
CardEdition cardEdition = CardEdition.UNKNOWN;
|
CardEdition cardEdition = CardEdition.UNKNOWN;
|
||||||
CardRarity cardRarity = CardRarity.Unknown;
|
CardRarity cardRarity = CardRarity.Unknown;
|
||||||
@@ -790,7 +812,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new PaperCard(CardRules.getUnsupportedCardNamed(request.cardName), cardEdition.getCode(), cardRarity, 1);
|
return new PaperCard(CardRules.getUnsupportedCardNamed(request.cardName), cardEdition.getCode(), cardRarity, 1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Editor editor = new Editor();
|
private final Editor editor = new Editor();
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package forge.card;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -121,13 +120,16 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
// commonly used printsheets with collector number
|
// commonly used printsheets with collector number
|
||||||
public enum EditionSectionWithCollectorNumbers {
|
public enum EditionSectionWithCollectorNumbers {
|
||||||
CARDS("cards"),
|
CARDS("cards"),
|
||||||
|
SPECIAL_SLOT("special slot"), //to help with convoluted boosters
|
||||||
PRECON_PRODUCT("precon product"),
|
PRECON_PRODUCT("precon product"),
|
||||||
BORDERLESS("borderless"),
|
BORDERLESS("borderless"),
|
||||||
SHOWCASE("showcase"),
|
SHOWCASE("showcase"),
|
||||||
EXTENDED_ART("extended art"),
|
EXTENDED_ART("extended art"),
|
||||||
ALTERNATE_ART("alternate art"),
|
ALTERNATE_ART("alternate art"),
|
||||||
|
ALTERNATE_FRAME("alternate frame"),
|
||||||
BUY_A_BOX("buy a box"),
|
BUY_A_BOX("buy a box"),
|
||||||
PROMO("promo"),
|
PROMO("promo"),
|
||||||
|
BUNDLE("bundle"),
|
||||||
BOX_TOPPER("box topper");
|
BOX_TOPPER("box topper");
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
@@ -148,7 +150,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CardInSet {
|
public static class CardInSet implements Comparable<CardInSet> {
|
||||||
public final CardRarity rarity;
|
public final CardRarity rarity;
|
||||||
public final String collectorNumber;
|
public final String collectorNumber;
|
||||||
public final String name;
|
public final String name;
|
||||||
@@ -172,6 +174,56 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
sb.append(name);
|
sb.append(name);
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method implements the main strategy to allow for natural ordering of collectorNumber
|
||||||
|
* (i.e. "1" < "10"), overloading the default lexicographic order (i.e. "10" < "1").
|
||||||
|
* Any non-numerical parts in the input collectorNumber will be also accounted for, and attached to the
|
||||||
|
* resulting sorting key, accordingly.
|
||||||
|
*
|
||||||
|
* @param collectorNumber: Input collectorNumber tro transform in a Sorting Key
|
||||||
|
* @return A 5-digits zero-padded collector number + any non-numerical parts attached.
|
||||||
|
*/
|
||||||
|
public static String getSortableCollectorNumber(final String collectorNumber){
|
||||||
|
String sortableCollNr = collectorNumber;
|
||||||
|
if (sortableCollNr == null || sortableCollNr.length() == 0)
|
||||||
|
sortableCollNr = "50000"; // very big number of 5 digits to have them in last positions
|
||||||
|
|
||||||
|
// Now, for proper sorting, let's zero-pad the collector number (if integer)
|
||||||
|
int collNr;
|
||||||
|
try {
|
||||||
|
collNr = Integer.parseInt(sortableCollNr);
|
||||||
|
sortableCollNr = String.format("%05d", collNr);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
String nonNumeric = sortableCollNr.replaceAll("[0-9]", "");
|
||||||
|
String onlyNumeric = sortableCollNr.replaceAll("[^0-9]", "");
|
||||||
|
try {
|
||||||
|
collNr = Integer.parseInt(onlyNumeric);
|
||||||
|
} catch (NumberFormatException exon) {
|
||||||
|
collNr = 0; // this is the case of ONLY-letters collector numbers
|
||||||
|
}
|
||||||
|
if ((collNr > 0) && (sortableCollNr.startsWith(onlyNumeric))) // e.g. 12a, 37+, 2018f,
|
||||||
|
sortableCollNr = String.format("%05d", collNr) + nonNumeric;
|
||||||
|
else // e.g. WS6, S1
|
||||||
|
sortableCollNr = nonNumeric + String.format("%05d", collNr);
|
||||||
|
}
|
||||||
|
return sortableCollNr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(CardInSet o) {
|
||||||
|
final int nameCmp = name.compareToIgnoreCase(o.name);
|
||||||
|
if (0 != nameCmp) {
|
||||||
|
return nameCmp;
|
||||||
|
}
|
||||||
|
String thisCollNr = getSortableCollectorNumber(collectorNumber);
|
||||||
|
String othrCollNr = getSortableCollectorNumber(o.collectorNumber);
|
||||||
|
final int collNrCmp = thisCollNr.compareTo(othrCollNr);
|
||||||
|
if (0 != collNrCmp) {
|
||||||
|
return collNrCmp;
|
||||||
|
}
|
||||||
|
return rarity.compareTo(o.rarity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
|
private final static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
@@ -206,6 +258,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
private String[] chaosDraftThemes = new String[0];
|
private String[] chaosDraftThemes = new String[0];
|
||||||
|
|
||||||
private final ListMultimap<String, CardInSet> cardMap;
|
private final ListMultimap<String, CardInSet> cardMap;
|
||||||
|
private final List<CardInSet> cardsInSet;
|
||||||
private final Map<String, Integer> tokenNormalized;
|
private final Map<String, Integer> tokenNormalized;
|
||||||
// custom print sheets that will be loaded lazily
|
// custom print sheets that will be loaded lazily
|
||||||
private final Map<String, List<String>> customPrintSheetsToParse;
|
private final Map<String, List<String>> customPrintSheetsToParse;
|
||||||
@@ -215,13 +268,18 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
|
|
||||||
private CardEdition(ListMultimap<String, CardInSet> cardMap, Map<String, Integer> tokens, Map<String, List<String>> customPrintSheetsToParse) {
|
private CardEdition(ListMultimap<String, CardInSet> cardMap, Map<String, Integer> tokens, Map<String, List<String>> customPrintSheetsToParse) {
|
||||||
this.cardMap = cardMap;
|
this.cardMap = cardMap;
|
||||||
|
this.cardsInSet = new ArrayList<>(cardMap.values());
|
||||||
|
Collections.sort(cardsInSet);
|
||||||
this.tokenNormalized = tokens;
|
this.tokenNormalized = tokens;
|
||||||
this.customPrintSheetsToParse = customPrintSheetsToParse;
|
this.customPrintSheetsToParse = customPrintSheetsToParse;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CardEdition(CardInSet[] cards, Map<String, Integer> tokens) {
|
private CardEdition(CardInSet[] cards, Map<String, Integer> tokens) {
|
||||||
|
List<CardInSet> cardsList = Arrays.asList(cards);
|
||||||
this.cardMap = ArrayListMultimap.create();
|
this.cardMap = ArrayListMultimap.create();
|
||||||
this.cardMap.replaceValues("cards", Arrays.asList(cards));
|
this.cardMap.replaceValues("cards", cardsList);
|
||||||
|
this.cardsInSet = new ArrayList<>(cardsList);
|
||||||
|
Collections.sort(cardsInSet);
|
||||||
this.tokenNormalized = tokens;
|
this.tokenNormalized = tokens;
|
||||||
this.customPrintSheetsToParse = new HashMap<>();
|
this.customPrintSheetsToParse = new HashMap<>();
|
||||||
}
|
}
|
||||||
@@ -256,7 +314,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
date = date + "-01";
|
date = date + "-01";
|
||||||
try {
|
try {
|
||||||
return formatter.parse(date);
|
return formatter.parse(date);
|
||||||
} catch (ParseException e) {
|
} catch (Exception e) {
|
||||||
return new Date();
|
return new Date();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,7 +345,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
|
|
||||||
public List<CardInSet> getCards() { return cardMap.get("cards"); }
|
public List<CardInSet> getCards() { return cardMap.get("cards"); }
|
||||||
public List<CardInSet> getAllCardsInSet() {
|
public List<CardInSet> getAllCardsInSet() {
|
||||||
return Lists.newArrayList(cardMap.values());
|
return cardsInSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
|
public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
|
||||||
@@ -306,7 +364,10 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
if (o == null) {
|
if (o == null) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return date.compareTo(o.date);
|
int dateComp = date.compareTo(o.date);
|
||||||
|
if (0 != dateComp)
|
||||||
|
return dateComp;
|
||||||
|
return name.compareTo(o.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -340,7 +401,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLargeSet() {
|
public boolean isLargeSet() {
|
||||||
return getAllCardsInSet().size() > 200 && !smallSetOverride;
|
return this.cardsInSet.size() > 200 && !smallSetOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCntBoosterPictures() {
|
public int getCntBoosterPictures() {
|
||||||
@@ -414,7 +475,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
* rarity - grouping #4
|
* rarity - grouping #4
|
||||||
* name - grouping #5
|
* name - grouping #5
|
||||||
*/
|
*/
|
||||||
"(^([0-9]+.?) )?(([SCURML]) )?(.*)$"
|
"(^([0-9A-Z]+.?) )?(([SCURML]) )?(.*)$"
|
||||||
);
|
);
|
||||||
|
|
||||||
ListMultimap<String, CardInSet> cardMap = ArrayListMultimap.create();
|
ListMultimap<String, CardInSet> cardMap = ArrayListMultimap.create();
|
||||||
|
|||||||
@@ -84,6 +84,12 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
boolean isReminder = false;
|
boolean isReminder = false;
|
||||||
boolean isSymbol = false;
|
boolean isSymbol = false;
|
||||||
String oracleText = face.getOracleText();
|
String oracleText = face.getOracleText();
|
||||||
|
// CR 903.4 colors defined by its characteristic-defining abilities
|
||||||
|
for (String staticAbility : face.getStaticAbilities()) {
|
||||||
|
if (staticAbility.contains("CharacteristicDefining$ True") && staticAbility.contains("SetColor$ All")) {
|
||||||
|
res |= MagicColor.ALL_COLORS;
|
||||||
|
}
|
||||||
|
}
|
||||||
int len = oracleText.length();
|
int len = oracleText.length();
|
||||||
for(int i = 0; i < len; i++) {
|
for(int i = 0; i < len; i++) {
|
||||||
char c = oracleText.charAt(i); // This is to avoid needless allocations performed by toCharArray()
|
char c = oracleText.charAt(i); // This is to avoid needless allocations performed by toCharArray()
|
||||||
@@ -388,7 +394,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
this.removedFromNonCommanderDecks = "NonCommander".equalsIgnoreCase(value);
|
this.removedFromNonCommanderDecks = "NonCommander".equalsIgnoreCase(value);
|
||||||
}
|
}
|
||||||
} else if ("AlternateMode".equals(key)) {
|
} else if ("AlternateMode".equals(key)) {
|
||||||
//System.out.println(faces[curFace].getName());
|
|
||||||
this.altMode = CardSplitType.smartValueOf(value);
|
this.altMode = CardSplitType.smartValueOf(value);
|
||||||
} else if ("ALTERNATE".equals(key)) {
|
} else if ("ALTERNATE".equals(key)) {
|
||||||
this.curFace = 1;
|
this.curFace = 1;
|
||||||
@@ -539,7 +544,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
@Override
|
@Override
|
||||||
public final ManaCostShard next() {
|
public final ManaCostShard next() {
|
||||||
final String unparsed = st.nextToken();
|
final String unparsed = st.nextToken();
|
||||||
// System.out.println(unparsed);
|
|
||||||
if (StringUtils.isNumeric(unparsed)) {
|
if (StringUtils.isNumeric(unparsed)) {
|
||||||
this.genericCost += Integer.parseInt(unparsed);
|
this.genericCost += Integer.parseInt(unparsed);
|
||||||
return null;
|
return null;
|
||||||
@@ -555,7 +559,7 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void remove() {
|
public void remove() {
|
||||||
} // unsuported
|
} // unsupported
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ public class ManaCostParser implements IParserManaCost {
|
|||||||
*/
|
*/
|
||||||
public ManaCostParser(final String cost) {
|
public ManaCostParser(final String cost) {
|
||||||
this.cost = cost.split(" ");
|
this.cost = cost.split(" ");
|
||||||
// System.out.println(cost);
|
|
||||||
this.nextToken = 0;
|
this.nextToken = 0;
|
||||||
this.genericCost = 0;
|
this.genericCost = 0;
|
||||||
}
|
}
|
||||||
@@ -66,7 +65,6 @@ public class ManaCostParser implements IParserManaCost {
|
|||||||
@Override
|
@Override
|
||||||
public final ManaCostShard next() {
|
public final ManaCostShard next() {
|
||||||
final String unparsed = this.cost[this.nextToken++];
|
final String unparsed = this.cost[this.nextToken++];
|
||||||
// System.out.println(unparsed);
|
|
||||||
if (StringUtils.isNumeric(unparsed)) {
|
if (StringUtils.isNumeric(unparsed)) {
|
||||||
this.genericCost += Integer.parseInt(unparsed);
|
this.genericCost += Integer.parseInt(unparsed);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ import forge.card.MagicColor;
|
|||||||
import forge.util.PredicateCard;
|
import forge.util.PredicateCard;
|
||||||
import forge.util.PredicateString;
|
import forge.util.PredicateString;
|
||||||
|
|
||||||
//import forge.Card;
|
|
||||||
|
|
||||||
public interface IPaperCard extends InventoryItem, Serializable {
|
public interface IPaperCard extends InventoryItem, Serializable {
|
||||||
|
|
||||||
|
String NO_COLLECTOR_NUMBER = "N.A."; // Placeholder for No-Collection number available
|
||||||
|
int DEFAULT_ART_INDEX = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of filters based on CardPrinted values.
|
* Number of filters based on CardPrinted values.
|
||||||
*/
|
*/
|
||||||
@@ -228,6 +229,7 @@ public interface IPaperCard extends InventoryItem, Serializable {
|
|||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
String getEdition();
|
String getEdition();
|
||||||
|
String getCollectorNumber();
|
||||||
int getArtIndex();
|
int getArtIndex();
|
||||||
boolean isFoil();
|
boolean isFoil();
|
||||||
boolean isToken();
|
boolean isToken();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import com.google.common.base.Function;
|
|||||||
import forge.ImageKeys;
|
import forge.ImageKeys;
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
import forge.card.CardDb;
|
import forge.card.CardDb;
|
||||||
|
import forge.card.CardEdition;
|
||||||
import forge.card.CardRarity;
|
import forge.card.CardRarity;
|
||||||
import forge.card.CardRules;
|
import forge.card.CardRules;
|
||||||
import forge.util.CardTranslation;
|
import forge.util.CardTranslation;
|
||||||
@@ -48,12 +49,19 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
|||||||
// These fields are kinda PK for PrintedCard
|
// These fields are kinda PK for PrintedCard
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String edition;
|
private final String edition;
|
||||||
|
/* [NEW] Attribute to store reference to CollectorNumber of each PaperCard.
|
||||||
|
By default the attribute is marked as "unset" so that it could be retrieved and set.
|
||||||
|
(see getCollectorNumber())
|
||||||
|
*/
|
||||||
|
private String collectorNumber = null;
|
||||||
private final int artIndex;
|
private final int artIndex;
|
||||||
private final boolean foil;
|
private final boolean foil;
|
||||||
private Boolean hasImage;
|
private Boolean hasImage;
|
||||||
|
|
||||||
// Calculated fields are below:
|
// Calculated fields are below:
|
||||||
private transient CardRarity rarity; // rarity is given in ctor when set is assigned
|
private transient CardRarity rarity; // rarity is given in ctor when set is assigned
|
||||||
|
// Reference to a new instance of Self, but foiled!
|
||||||
|
private transient PaperCard foiledVersion = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@@ -65,6 +73,23 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
|||||||
return edition;
|
return edition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCollectorNumber() {
|
||||||
|
/* The collectorNumber attribute is managed in a property-like fashion.
|
||||||
|
By default it is marked as "unset" (-1), which integrates with all constructors
|
||||||
|
invocations not including this as an extra parameter. In this way, the new
|
||||||
|
attribute could be added to the API with minimum disruption to code
|
||||||
|
throughout the other packages.
|
||||||
|
If "unset", the corresponding collectorNumber will be retrieved
|
||||||
|
from the corresponding CardEdition (see retrieveCollectorNumber)
|
||||||
|
* */
|
||||||
|
if (collectorNumber == null) {
|
||||||
|
collectorNumber = this.retrieveCollectorNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectorNumber;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getArtIndex() {
|
public int getArtIndex() {
|
||||||
return artIndex;
|
return artIndex;
|
||||||
@@ -90,6 +115,20 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
|||||||
return rarity;
|
return rarity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* FIXME: At the moment, every card can get Foiled, with no restriction on the
|
||||||
|
corresponding Edition - so we could Foil even Alpha cards.
|
||||||
|
*/
|
||||||
|
public PaperCard getFoiled() {
|
||||||
|
if (this.foil)
|
||||||
|
return this;
|
||||||
|
|
||||||
|
if (this.foiledVersion == null) {
|
||||||
|
this.foiledVersion = new PaperCard(this.rules, this.edition, this.rarity,
|
||||||
|
this.artIndex, true, String.valueOf(collectorNumber));
|
||||||
|
}
|
||||||
|
return this.foiledVersion;
|
||||||
|
}
|
||||||
|
|
||||||
// @Override
|
// @Override
|
||||||
// public String getImageKey() {
|
// public String getImageKey() {
|
||||||
// return getImageLocator(getImageName(), getArtIndex(), true, false);
|
// return getImageLocator(getImageName(), getArtIndex(), true, false);
|
||||||
@@ -124,11 +163,16 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0) {
|
||||||
|
this(rules0, edition0, rarity0, IPaperCard.DEFAULT_ART_INDEX, false);
|
||||||
|
}
|
||||||
|
|
||||||
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0, final int artIndex0) {
|
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0, final int artIndex0) {
|
||||||
this(rules0, edition0, rarity0, artIndex0, false);
|
this(rules0, edition0, rarity0, artIndex0, false);
|
||||||
}
|
}
|
||||||
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0, final int artIndex0, final boolean foil0) {
|
|
||||||
|
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0, final int artIndex0,
|
||||||
|
final boolean foil0) {
|
||||||
if (rules0 == null || edition0 == null || rarity0 == null) {
|
if (rules0 == null || edition0 == null || rarity0 == null) {
|
||||||
throw new IllegalArgumentException("Cannot create card without rules, edition or rarity");
|
throw new IllegalArgumentException("Cannot create card without rules, edition or rarity");
|
||||||
}
|
}
|
||||||
@@ -140,6 +184,17 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
|||||||
rarity = rarity0;
|
rarity = rarity0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0, final int artIndex0,
|
||||||
|
final String collectorNumber0) {
|
||||||
|
this(rules0, edition0, rarity0, artIndex0, false, collectorNumber0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0,
|
||||||
|
final int artIndex0, final boolean foil0, final String collectorNumber0) {
|
||||||
|
this(rules0, edition0, rarity0, artIndex0, foil0);
|
||||||
|
collectorNumber = collectorNumber0;
|
||||||
|
}
|
||||||
|
|
||||||
// Want this class to be a key for HashTable
|
// Want this class to be a key for HashTable
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object obj) {
|
public boolean equals(final Object obj) {
|
||||||
@@ -160,11 +215,9 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
|||||||
if (!edition.equals(other.edition)) {
|
if (!edition.equals(other.edition)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ((other.foil != foil) || (other.artIndex != artIndex)) {
|
if (!getCollectorNumber().equals(other.getCollectorNumber()))
|
||||||
return false;
|
return false;
|
||||||
}
|
return (other.foil == foil) && (other.artIndex == artIndex);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -174,18 +227,15 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int code = (name.hashCode() * 11) + (edition.hashCode() * 59) + (artIndex * 2);
|
final int code = (name.hashCode() * 11) + (edition.hashCode() * 59) +
|
||||||
|
(artIndex * 2) + (getCollectorNumber().hashCode() * 383);
|
||||||
if (foil) {
|
if (foil) {
|
||||||
return code + 1;
|
return code + 1;
|
||||||
}
|
}
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// FIXME: Check
|
||||||
* (non-Javadoc)
|
|
||||||
*
|
|
||||||
* @see java.lang.Object#toString()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return CardTranslation.getTranslatedName(name);
|
return CardTranslation.getTranslatedName(name);
|
||||||
@@ -194,21 +244,49 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* This (utility) method transform a collectorNumber String into a key string for sorting.
|
||||||
|
* This method proxies the same strategy implemented in CardEdition.CardInSet class from which the
|
||||||
|
* collectorNumber of PaperCard instances are originally retrieved.
|
||||||
|
* This is also to centralise the criterion, whilst avoiding code duplication.
|
||||||
*
|
*
|
||||||
* @see java.lang.Comparable#compareTo(java.lang.Object)
|
* Note: The method has been made private as this is for internal API use **only**, to allow
|
||||||
|
* for generalised comparison with IPaperCard instances (see compareTo)
|
||||||
|
*
|
||||||
|
* The public API of PaperCard includes a method (i.e. getCollectorNumberSortingKey) which applies
|
||||||
|
* this method on instance's own collector number.
|
||||||
|
*
|
||||||
|
* @return a zero-padded 5-digits String + any non-numerical content in the input String, properly attached.
|
||||||
*/
|
*/
|
||||||
|
private static String makeCollectorNumberSortingKey(final String collectorNumber0){
|
||||||
|
String collectorNumber = collectorNumber0;
|
||||||
|
if (collectorNumber.equals(NO_COLLECTOR_NUMBER))
|
||||||
|
collectorNumber = null;
|
||||||
|
return CardEdition.CardInSet.getSortableCollectorNumber(collectorNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCollectorNumberSortingKey(){
|
||||||
|
// Hardly the case, but just invoke getter rather than direct
|
||||||
|
// attribute to be sure that collectorNumber has been retrieved already!
|
||||||
|
return makeCollectorNumberSortingKey(getCollectorNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(final IPaperCard o) {
|
public int compareTo(final IPaperCard o) {
|
||||||
final int nameCmp = getName().compareToIgnoreCase(o.getName());
|
final int nameCmp = name.compareToIgnoreCase(o.getName());
|
||||||
if (0 != nameCmp) {
|
if (0 != nameCmp) {
|
||||||
return nameCmp;
|
return nameCmp;
|
||||||
}
|
}
|
||||||
// TODO compare sets properly
|
//FIXME: compare sets properly
|
||||||
int setDiff = edition.compareTo(o.getEdition());
|
int setDiff = edition.compareTo(o.getEdition());
|
||||||
if ( 0 != setDiff )
|
if (0 != setDiff)
|
||||||
return setDiff;
|
return setDiff;
|
||||||
|
String thisCollNrKey = getCollectorNumberSortingKey();
|
||||||
|
String othrCollNrKey = makeCollectorNumberSortingKey(o.getCollectorNumber());
|
||||||
|
final int collNrCmp = thisCollNrKey.compareTo(othrCollNrKey);
|
||||||
|
if (0 != collNrCmp) {
|
||||||
|
return collNrCmp;
|
||||||
|
}
|
||||||
return Integer.compare(artIndex, o.getArtIndex());
|
return Integer.compare(artIndex, o.getArtIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,8 +294,7 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
|||||||
// default deserialization
|
// default deserialization
|
||||||
ois.defaultReadObject();
|
ois.defaultReadObject();
|
||||||
|
|
||||||
IPaperCard pc = null;
|
IPaperCard pc = StaticData.instance().getCommonCards().getCard(name, edition, artIndex);
|
||||||
pc = StaticData.instance().getCommonCards().getCard(name, edition, artIndex);
|
|
||||||
if (pc == null) {
|
if (pc == null) {
|
||||||
pc = StaticData.instance().getVariantCards().getCard(name, edition, artIndex);
|
pc = StaticData.instance().getVariantCards().getCard(name, edition, artIndex);
|
||||||
if (pc == null) {
|
if (pc == null) {
|
||||||
@@ -228,9 +305,35 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
|||||||
rarity = pc.getRarity();
|
rarity = pc.getRarity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String retrieveCollectorNumber() {
|
||||||
|
StaticData data = StaticData.instance();
|
||||||
|
CardEdition edition = data.getEditions().get(this.edition);
|
||||||
|
if (edition == null) {
|
||||||
|
edition = data.getCustomEditions().get(this.edition);
|
||||||
|
if (edition == null) // don't bother continuing - non-existing card!
|
||||||
|
return NO_COLLECTOR_NUMBER;
|
||||||
|
}
|
||||||
|
int artIndexCount = 0;
|
||||||
|
String collectorNumberInEdition = "";
|
||||||
|
for (CardEdition.CardInSet card : edition.getAllCardsInSet()) {
|
||||||
|
if (card.name.equalsIgnoreCase(this.name)) {
|
||||||
|
artIndexCount += 1;
|
||||||
|
if (artIndexCount == this.artIndex) {
|
||||||
|
collectorNumberInEdition = card.collectorNumber;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// CardEdition stores collectorNumber as a String, which is null if there isn't any.
|
||||||
|
// In this case, the NO_COLLECTOR_NUMBER value (i.e. 0) is returned.
|
||||||
|
return ((collectorNumberInEdition != null) && (collectorNumberInEdition.length() > 0)) ?
|
||||||
|
collectorNumberInEdition : NO_COLLECTOR_NUMBER;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getImageKey(boolean altState) {
|
public String getImageKey(boolean altState) {
|
||||||
String imageKey = ImageKeys.CARD_PREFIX + name + CardDb.NameSetSeparator + edition + CardDb.NameSetSeparator + artIndex;
|
String imageKey = ImageKeys.CARD_PREFIX + name + CardDb.NameSetSeparator
|
||||||
|
+ edition + CardDb.NameSetSeparator + artIndex;
|
||||||
if (altState) {
|
if (altState) {
|
||||||
imageKey += ImageKeys.BACKFACE_POSTFIX;
|
imageKey += ImageKeys.BACKFACE_POSTFIX;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,12 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
|||||||
|
|
||||||
@Override public String toString() { return name; }
|
@Override public String toString() { return name; }
|
||||||
@Override public String getEdition() { return edition != null ? edition.getCode() : "???"; }
|
@Override public String getEdition() { return edition != null ? edition.getCode() : "???"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCollectorNumber() {
|
||||||
|
return IPaperCard.NO_COLLECTOR_NUMBER;
|
||||||
|
}
|
||||||
|
|
||||||
@Override public int getArtIndex() { return artIndex; }
|
@Override public int getArtIndex() { return artIndex; }
|
||||||
@Override public boolean isFoil() { return false; }
|
@Override public boolean isFoil() { return false; }
|
||||||
@Override public CardRules getRules() { return card; }
|
@Override public CardRules getRules() { return card; }
|
||||||
|
|||||||
@@ -70,12 +70,14 @@ public class BoosterGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static PaperCard generateFoilCard(PrintSheet sheet) {
|
private static PaperCard generateFoilCard(PrintSheet sheet) {
|
||||||
return StaticData.instance().getCommonCards().getFoiled(sheet.random(1, true).get(0));
|
PaperCard randomCard = sheet.random(1, true).get(0);
|
||||||
|
return randomCard.getFoiled();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PaperCard generateFoilCard(List<PaperCard> cardList) {
|
private static PaperCard generateFoilCard(List<PaperCard> cardList) {
|
||||||
Collections.shuffle(cardList, MyRandom.getRandom());
|
Collections.shuffle(cardList, MyRandom.getRandom());
|
||||||
return StaticData.instance().getCommonCards().getFoiled(cardList.get(0));
|
PaperCard randomCard = cardList.get(0);
|
||||||
|
return randomCard.getFoiled();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<PaperCard> getBoosterPack(SealedProduct.Template template) {
|
public static List<PaperCard> getBoosterPack(SealedProduct.Template template) {
|
||||||
@@ -461,7 +463,7 @@ public class BoosterGenerator {
|
|||||||
if (toReplace != null) {
|
if (toReplace != null) {
|
||||||
// Keep the foil state
|
// Keep the foil state
|
||||||
if (toReplace.isFoil()) {
|
if (toReplace.isFoil()) {
|
||||||
toAdd = StaticData.instance().getCommonCards().getFoiled(toAdd);
|
toAdd = toAdd.getFoiled();
|
||||||
}
|
}
|
||||||
booster.remove(toReplace);
|
booster.remove(toReplace);
|
||||||
booster.add(toAdd);
|
booster.add(toAdd);
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import java.io.BufferedReader;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
@@ -208,7 +207,7 @@ public final class FileUtil {
|
|||||||
if ((file == null) || !file.exists()) {
|
if ((file == null) || !file.exists()) {
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
return FileUtil.readAllLines(new FileReader(file), false);
|
return FileUtil.readAllLines(file, false);
|
||||||
} catch (final Exception ex) {
|
} catch (final Exception ex) {
|
||||||
throw new RuntimeException("FileUtil : readFile() error, " + ex);
|
throw new RuntimeException("FileUtil : readFile() error, " + ex);
|
||||||
}
|
}
|
||||||
@@ -248,6 +247,31 @@ public final class FileUtil {
|
|||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Reads all lines from given file to a list of strings.
|
||||||
|
*
|
||||||
|
* @param file is the File to read.
|
||||||
|
* @param mayTrim defines whether to trim lines.
|
||||||
|
* @return list of strings
|
||||||
|
*/
|
||||||
|
public static List<String> readAllLines(final File file, final boolean mayTrim) {
|
||||||
|
final List<String> list = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
final BufferedReader in = new BufferedReader(
|
||||||
|
new InputStreamReader(new FileInputStream(file), "UTF-8"));
|
||||||
|
String line;
|
||||||
|
while ((line = in.readLine()) != null) {
|
||||||
|
if (mayTrim) {
|
||||||
|
line = line.trim();
|
||||||
|
}
|
||||||
|
list.add(line);
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
} catch (final IOException ex) {
|
||||||
|
throw new RuntimeException("FileUtil : readAllLines() error, " + ex);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
// returns a list of <name, url> pairs. if the name is not in the file, it is synthesized from the url
|
// returns a list of <name, url> pairs. if the name is not in the file, it is synthesized from the url
|
||||||
public static List<Pair<String, String>> readNameUrlFile(String nameUrlFile) {
|
public static List<Pair<String, String>> readNameUrlFile(String nameUrlFile) {
|
||||||
|
|||||||
@@ -118,6 +118,27 @@ public class ImageUtil {
|
|||||||
return getImageRelativePath(cp, backFace, true, true);
|
return getImageRelativePath(cp, backFace, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getScryfallDownloadUrl(PaperCard cp, boolean backFace, String setCode){
|
||||||
|
return getScryfallDownloadUrl(cp, backFace, setCode, "en");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getScryfallDownloadUrl(PaperCard cp, boolean backFace, String setCode, String langCode){
|
||||||
|
String editionCode;
|
||||||
|
if ((setCode != null) && (setCode.length() > 0))
|
||||||
|
editionCode = setCode;
|
||||||
|
else
|
||||||
|
editionCode = cp.getEdition().toLowerCase();
|
||||||
|
String cardCollectorNumber = cp.getCollectorNumber();
|
||||||
|
// Hack to account for variations in Arabian Nights
|
||||||
|
cardCollectorNumber = cardCollectorNumber.replace("+", "†");
|
||||||
|
String faceParam = "";
|
||||||
|
if (cp.getRules().getOtherPart() != null) {
|
||||||
|
faceParam = (backFace ? "&face=back" : "&face=front");
|
||||||
|
}
|
||||||
|
return String.format("%s/%s/%s?format=image&version=normal%s", editionCode, cardCollectorNumber,
|
||||||
|
langCode, faceParam);
|
||||||
|
}
|
||||||
|
|
||||||
public static String toMWSFilename(String in) {
|
public static String toMWSFilename(String in) {
|
||||||
final StringBuilder out = new StringBuilder();
|
final StringBuilder out = new StringBuilder();
|
||||||
char c;
|
char c;
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ public class Localizer {
|
|||||||
return StandardCharsets.UTF_8.name();
|
return StandardCharsets.UTF_8.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getMessageorUseDefault(final String key, final String defaultValue, final Object... messageArguments) {
|
||||||
|
try {
|
||||||
|
return getMessage(key, messageArguments);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//FIXME: localizer should return default value from english locale or it will crash some GUI element like the NewGameMenu->NewGameScreen Popup when returned null...
|
||||||
public String getMessage(final String key, final Object... messageArguments) {
|
public String getMessage(final String key, final Object... messageArguments) {
|
||||||
MessageFormat formatter = null;
|
MessageFormat formatter = null;
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,9 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public boolean matchesValid(final Object o, final String[] valids, final Card srcCard) {
|
public boolean matchesValid(final Object o, final String[] valids, final Card srcCard) {
|
||||||
|
if (srcCard == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return matchesValid(o, valids, srcCard, srcCard.getController());
|
return matchesValid(o, valids, srcCard, srcCard.getController());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,13 +191,17 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
|||||||
return matchesValid(o, valids, getHostCard());
|
return matchesValid(o, valids, getHostCard());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean matchesValidParam(String param, final Object o) {
|
public boolean matchesValidParam(String param, final Object o, final Card srcCard) {
|
||||||
if (hasParam(param) && !matchesValid(o, getParam(param).split(","))) {
|
if (hasParam(param) && !matchesValid(o, getParam(param).split(","), srcCard)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean matchesValidParam(String param, final Object o) {
|
||||||
|
return matchesValidParam(param, o, getHostCard());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the suppressed.
|
* Sets the suppressed.
|
||||||
*
|
*
|
||||||
@@ -341,7 +348,6 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
|||||||
}
|
}
|
||||||
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), this);
|
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), this);
|
||||||
|
|
||||||
|
|
||||||
final String rightString = presentCompare.substring(2);
|
final String rightString = presentCompare.substring(2);
|
||||||
int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this);
|
int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this);
|
||||||
final int left = list.size();
|
final int left = list.size();
|
||||||
@@ -434,7 +440,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (params.containsKey("WerewolfTransformCondition")) {
|
if (params.containsKey("WerewolfTransformCondition")) {
|
||||||
if (!CardUtil.getLastTurnCast("Card", this.getHostCard()).isEmpty()) {
|
if (!CardUtil.getLastTurnCast("Card", this.getHostCard(), this).isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -756,7 +756,7 @@ public class Game {
|
|||||||
// Rule 800.4 Losing a Multiplayer game
|
// Rule 800.4 Losing a Multiplayer game
|
||||||
CardCollectionView cards = this.getCardsInGame();
|
CardCollectionView cards = this.getCardsInGame();
|
||||||
boolean planarControllerLost = false;
|
boolean planarControllerLost = false;
|
||||||
boolean isMultiplayer = this.getPlayers().size() > 2;
|
boolean isMultiplayer = getPlayers().size() > 2;
|
||||||
|
|
||||||
// 702.142f & 707.9
|
// 702.142f & 707.9
|
||||||
// If a player leaves the game, all face-down cards that player owns must be revealed to all players.
|
// If a player leaves the game, all face-down cards that player owns must be revealed to all players.
|
||||||
@@ -772,7 +772,7 @@ public class Game {
|
|||||||
for (Card c : cards) {
|
for (Card c : cards) {
|
||||||
// CR 800.4d if card is controlled by opponent, LTB should trigger
|
// CR 800.4d if card is controlled by opponent, LTB should trigger
|
||||||
if (c.getOwner().equals(p) && c.getController().equals(p)) {
|
if (c.getOwner().equals(p) && c.getController().equals(p)) {
|
||||||
c.getCurrentState().clearTriggers();
|
c.getGame().getTriggerHandler().clearActiveTriggers(c, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -793,16 +793,16 @@ public class Game {
|
|||||||
}
|
}
|
||||||
getAction().ceaseToExist(c, false);
|
getAction().ceaseToExist(c, false);
|
||||||
// CR 603.2f owner of trigger source lost game
|
// CR 603.2f owner of trigger source lost game
|
||||||
triggerHandler.clearDelayedTrigger(c);
|
getTriggerHandler().clearDelayedTrigger(c);
|
||||||
} else {
|
} else {
|
||||||
// return stolen permanents
|
// return stolen permanents
|
||||||
if (c.getController().equals(p) && c.isInZone(ZoneType.Battlefield)) {
|
if ((c.getController().equals(p) || c.getZone().getPlayer().equals(p)) && c.isInZone(ZoneType.Battlefield)) {
|
||||||
c.removeTempController(p);
|
c.removeTempController(p);
|
||||||
getAction().controllerChangeZoneCorrection(c);
|
getAction().controllerChangeZoneCorrection(c);
|
||||||
}
|
}
|
||||||
c.removeTempController(p);
|
c.removeTempController(p);
|
||||||
if (c.getController().equals(p)) {
|
if (c.getController().equals(p)) {
|
||||||
this.getAction().exile(c, null);
|
getAction().exile(c, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -830,7 +830,7 @@ public class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove leftover items from
|
// Remove leftover items from
|
||||||
this.getStack().removeInstancesControlledBy(p);
|
getStack().removeInstancesControlledBy(p);
|
||||||
|
|
||||||
getTriggerHandler().onPlayerLost(p);
|
getTriggerHandler().onPlayerLost(p);
|
||||||
|
|
||||||
@@ -1054,7 +1054,8 @@ public class Game {
|
|||||||
|
|
||||||
public void onCleanupPhase() {
|
public void onCleanupPhase() {
|
||||||
clearCounterAddedThisTurn();
|
clearCounterAddedThisTurn();
|
||||||
for (Player player : getPlayers()) {
|
// some cards need this info updated even after a player lost, so don't skip them
|
||||||
|
for (Player player : getRegisteredPlayers()) {
|
||||||
player.onCleanupPhase();
|
player.onCleanupPhase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import forge.game.ability.effects.AttachEffect;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
|
import forge.game.card.CardDamageMap;
|
||||||
import forge.game.card.CardFactory;
|
import forge.game.card.CardFactory;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
@@ -61,6 +62,7 @@ import forge.game.event.GameEventCardTapped;
|
|||||||
import forge.game.event.GameEventFlipCoin;
|
import forge.game.event.GameEventFlipCoin;
|
||||||
import forge.game.event.GameEventGameStarted;
|
import forge.game.event.GameEventGameStarted;
|
||||||
import forge.game.event.GameEventScry;
|
import forge.game.event.GameEventScry;
|
||||||
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.mulligan.MulliganService;
|
import forge.game.mulligan.MulliganService;
|
||||||
import forge.game.player.GameLossReason;
|
import forge.game.player.GameLossReason;
|
||||||
@@ -144,8 +146,8 @@ public class GameAction {
|
|||||||
boolean fromBattlefield = zoneFrom != null && zoneFrom.is(ZoneType.Battlefield);
|
boolean fromBattlefield = zoneFrom != null && zoneFrom.is(ZoneType.Battlefield);
|
||||||
boolean wasFacedown = c.isFaceDown();
|
boolean wasFacedown = c.isFaceDown();
|
||||||
|
|
||||||
//Rule 110.5g: A token that has left the battlefield can't move to another zone
|
// Rule 111.8: A token that has left the battlefield can't move to another zone
|
||||||
if (c.isToken() && zoneFrom != null && !fromBattlefield && !zoneFrom.is(ZoneType.Stack)) {
|
if (!c.isSpell() && c.isToken() && zoneFrom != null && !fromBattlefield && !zoneFrom.is(ZoneType.Stack)) {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +243,7 @@ public class GameAction {
|
|||||||
copied = CardFactory.copyCard(c, false);
|
copied = CardFactory.copyCard(c, false);
|
||||||
|
|
||||||
if (zoneTo.is(ZoneType.Stack)) {
|
if (zoneTo.is(ZoneType.Stack)) {
|
||||||
// when moving to stack, copy changed card infomation
|
// when moving to stack, copy changed card information
|
||||||
copied.setChangedCardColors(c.getChangedCardColors());
|
copied.setChangedCardColors(c.getChangedCardColors());
|
||||||
copied.setChangedCardKeywords(c.getChangedCardKeywords());
|
copied.setChangedCardKeywords(c.getChangedCardKeywords());
|
||||||
copied.setChangedCardTypes(c.getChangedCardTypesMap());
|
copied.setChangedCardTypes(c.getChangedCardTypesMap());
|
||||||
@@ -342,6 +344,7 @@ public class GameAction {
|
|||||||
c.updateStateForView();
|
c.updateStateForView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,7 +374,7 @@ public class GameAction {
|
|||||||
if (saTargeting != null) {
|
if (saTargeting != null) {
|
||||||
saTargeting.getTargets().replaceTargetCard(c, cards);
|
saTargeting.getTargets().replaceTargetCard(c, cards);
|
||||||
}
|
}
|
||||||
// Replace host rememberd cards
|
// Replace host remembered cards
|
||||||
// But not replace RememberLKI, since it wants to refer to the last known info.
|
// But not replace RememberLKI, since it wants to refer to the last known info.
|
||||||
Card hostCard = cause.getHostCard();
|
Card hostCard = cause.getHostCard();
|
||||||
if (!cause.hasParam("RememberLKI") && hostCard.isRemembered(c)) {
|
if (!cause.hasParam("RememberLKI") && hostCard.isRemembered(c)) {
|
||||||
@@ -438,7 +441,7 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mergedCards != null) {
|
if (mergedCards != null) {
|
||||||
// Move components of merged permanet here
|
// Move components of merged permanent here
|
||||||
// Also handle 721.3e and 903.9a
|
// Also handle 721.3e and 903.9a
|
||||||
boolean wasToken = c.isToken();
|
boolean wasToken = c.isToken();
|
||||||
if (commanderEffect != null) {
|
if (commanderEffect != null) {
|
||||||
@@ -938,7 +941,10 @@ public class GameAction {
|
|||||||
game.getCombat().removeFromCombat(c);
|
game.getCombat().removeFromCombat(c);
|
||||||
game.getCombat().saveLKI(lki);
|
game.getCombat().saveLKI(lki);
|
||||||
}
|
}
|
||||||
|
// again, make sure no triggers run from cards leaving controlled by loser
|
||||||
|
if (!lki.getController().equals(lki.getOwner())) {
|
||||||
game.getTriggerHandler().registerActiveLTBTrigger(lki);
|
game.getTriggerHandler().registerActiveLTBTrigger(lki);
|
||||||
|
}
|
||||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
|
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
|
||||||
runParams.put(AbilityKey.CardLKI, lki);
|
runParams.put(AbilityKey.CardLKI, lki);
|
||||||
runParams.put(AbilityKey.Origin, c.getZone().getZoneType().name());
|
runParams.put(AbilityKey.Origin, c.getZone().getZoneType().name());
|
||||||
@@ -1352,8 +1358,7 @@ public class GameAction {
|
|||||||
if (c.getCounters(CounterEnumType.LORE) < c.getFinalChapterNr()) {
|
if (c.getCounters(CounterEnumType.LORE) < c.getFinalChapterNr()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!game.getStack().hasSimultaneousStackEntries() &&
|
if (!game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isChapter())) {
|
||||||
!game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isChapter())) {
|
|
||||||
sacrifice(c, null, table);
|
sacrifice(c, null, table);
|
||||||
checkAgain = true;
|
checkAgain = true;
|
||||||
}
|
}
|
||||||
@@ -1680,6 +1685,8 @@ public class GameAction {
|
|||||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
|
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
|
||||||
runParams.put(AbilityKey.Causer, activator);
|
runParams.put(AbilityKey.Causer, activator);
|
||||||
game.getTriggerHandler().runTrigger(TriggerType.Destroyed, runParams, false);
|
game.getTriggerHandler().runTrigger(TriggerType.Destroyed, runParams, false);
|
||||||
|
// in case the destroyed card has such a trigger
|
||||||
|
game.getTriggerHandler().registerActiveLTBTrigger(c);
|
||||||
|
|
||||||
final Card sacrificed = sacrificeDestroy(c, sa, table);
|
final Card sacrificed = sacrificeDestroy(c, sa, table);
|
||||||
return sacrificed != null;
|
return sacrificed != null;
|
||||||
@@ -1695,7 +1702,7 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Card newCard = moveToGraveyard(c, cause, null);
|
final Card newCard = moveToGraveyard(c, cause, null);
|
||||||
if (table != null) {
|
if (table != null && newCard != null && newCard.getZone() != null) {
|
||||||
table.put(ZoneType.Battlefield, newCard.getZone().getZoneType(), newCard);
|
table.put(ZoneType.Battlefield, newCard.getZone().getZoneType(), newCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2121,4 +2128,44 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void dealDamage(final boolean isCombat, final CardDamageMap damageMap, final CardDamageMap preventMap,
|
||||||
|
final GameEntityCounterTable counterTable, final SpellAbility cause) {
|
||||||
|
// Clear assigned damage if is combat
|
||||||
|
for (Map.Entry<GameEntity, Map<Card, Integer>> et : damageMap.columnMap().entrySet()) {
|
||||||
|
final GameEntity ge = et.getKey();
|
||||||
|
if (isCombat && ge instanceof Card) {
|
||||||
|
((Card) ge).clearAssignedDamage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run replacement effect for each entity dealt damage
|
||||||
|
game.getReplacementHandler().runReplaceDamage(isCombat, damageMap, preventMap, counterTable, cause);
|
||||||
|
|
||||||
|
// Actually deal damage according to replaced damage map
|
||||||
|
for (Map.Entry<Card, Map<GameEntity, Integer>> et : damageMap.rowMap().entrySet()) {
|
||||||
|
final Card sourceLKI = et.getKey();
|
||||||
|
int sum = 0;
|
||||||
|
for (Map.Entry<GameEntity, Integer> e : et.getValue().entrySet()) {
|
||||||
|
if (e.getValue() <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sum += e.getValue();
|
||||||
|
e.getKey().addDamageAfterPrevention(e.getValue(), sourceLKI, isCombat, counterTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceLKI.hasKeyword(Keyword.LIFELINK)) {
|
||||||
|
sourceLKI.getController().gainLife(sum, sourceLKI, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preventMap.triggerPreventDamage(isCombat);
|
||||||
|
preventMap.clear();
|
||||||
|
|
||||||
|
damageMap.triggerDamageDoneOnce(isCombat, game);
|
||||||
|
damageMap.clear();
|
||||||
|
|
||||||
|
counterTable.triggerCountersPutAll(game);
|
||||||
|
counterTable.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ import com.google.common.collect.Iterables;
|
|||||||
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 forge.game.ability.AbilityKey;
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardDamageMap;
|
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
@@ -35,19 +35,17 @@ import forge.game.card.CounterType;
|
|||||||
import forge.game.event.GameEventCardAttachment;
|
import forge.game.event.GameEventCardAttachment;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementType;
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.trigger.TriggerType;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public abstract class GameEntity extends GameObject implements IIdentifiable {
|
public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||||
protected final int id;
|
protected final int id;
|
||||||
private String name = "";
|
private String name = "";
|
||||||
private int preventNextDamage = 0;
|
|
||||||
protected CardCollection attachedCards = new CardCollection();
|
protected CardCollection attachedCards = new CardCollection();
|
||||||
private Map<Card, Map<String, String>> preventionShieldsWithEffects = Maps.newTreeMap();
|
|
||||||
protected Map<CounterType, Integer> counters = Maps.newHashMap();
|
protected Map<CounterType, Integer> counters = Maps.newHashMap();
|
||||||
|
|
||||||
protected GameEntity(int id0) {
|
protected GameEntity(int id0) {
|
||||||
@@ -67,216 +65,61 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
getView().updateName(this);
|
getView().updateName(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final int addDamage(final int damage, final Card source, boolean isCombat, boolean noPrevention,
|
// This function handles damage after replacement and prevention effects are applied
|
||||||
final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
|
public abstract int addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat, GameEntityCounterTable counterTable);
|
||||||
if (noPrevention) {
|
|
||||||
return addDamageWithoutPrevention(damage, source, damageMap, preventMap, counterTable, cause);
|
// This should be also usable by the AI to forecast an effect (so it must
|
||||||
} else if (isCombat) {
|
// not change the game state)
|
||||||
return addCombatDamage(damage, source, damageMap, preventMap, counterTable);
|
public int staticDamagePrevention(final int damage, final int possiblePrevention, final Card source, final boolean isCombat) {
|
||||||
} else {
|
if (damage <= 0) {
|
||||||
return addDamage(damage, source, damageMap, preventMap, counterTable, cause);
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
if (!source.canDamagePrevented(isCombat)) {
|
||||||
|
|
||||||
public int addDamage(final int damage, final Card source, final CardDamageMap damageMap,
|
|
||||||
final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
|
|
||||||
int damageToDo = damage;
|
|
||||||
|
|
||||||
damageToDo = replaceDamage(damageToDo, source, false, true, damageMap, preventMap, counterTable, cause);
|
|
||||||
damageToDo = preventDamage(damageToDo, source, false, preventMap, cause);
|
|
||||||
|
|
||||||
return addDamageAfterPrevention(damageToDo, source, false, damageMap, counterTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final int addCombatDamage(final int damage, final Card source, final CardDamageMap damageMap,
|
|
||||||
final CardDamageMap preventMap, GameEntityCounterTable counterTable) {
|
|
||||||
int damageToDo = damage;
|
|
||||||
|
|
||||||
damageToDo = replaceDamage(damageToDo, source, true, true, damageMap, preventMap, counterTable, null);
|
|
||||||
damageToDo = preventDamage(damageToDo, source, true, preventMap, null);
|
|
||||||
|
|
||||||
if (damageToDo > 0) {
|
|
||||||
source.getDamageHistory().registerCombatDamage(this);
|
|
||||||
}
|
|
||||||
// damage prevention is already checked
|
|
||||||
return addCombatDamageBase(damageToDo, source, damageMap, counterTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected int addCombatDamageBase(final int damage, final Card source, CardDamageMap damageMap, GameEntityCounterTable counterTable) {
|
|
||||||
return addDamageAfterPrevention(damage, source, true, damageMap, counterTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int addDamageWithoutPrevention(final int damage, final Card source, final CardDamageMap damageMap,
|
|
||||||
final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
|
|
||||||
int damageToDo = replaceDamage(damage, source, false, false, damageMap, preventMap, counterTable, cause);
|
|
||||||
return addDamageAfterPrevention(damageToDo, source, false, damageMap, counterTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int replaceDamage(final int damage, final Card source, final boolean isCombat, final boolean prevention,
|
|
||||||
final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
|
|
||||||
// Replacement effects
|
|
||||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
|
|
||||||
repParams.put(AbilityKey.DamageSource, source);
|
|
||||||
repParams.put(AbilityKey.DamageAmount, damage);
|
|
||||||
repParams.put(AbilityKey.IsCombat, isCombat);
|
|
||||||
repParams.put(AbilityKey.NoPreventDamage, !prevention);
|
|
||||||
repParams.put(AbilityKey.DamageMap, damageMap);
|
|
||||||
repParams.put(AbilityKey.PreventMap, preventMap);
|
|
||||||
repParams.put(AbilityKey.CounterTable, counterTable);
|
|
||||||
if (cause != null) {
|
|
||||||
repParams.put(AbilityKey.Cause, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (getGame().getReplacementHandler().run(ReplacementType.DamageDone, repParams)) {
|
|
||||||
case NotReplaced:
|
|
||||||
return damage;
|
return damage;
|
||||||
case Updated:
|
|
||||||
int newDamage = (int) repParams.get(AbilityKey.DamageAmount);
|
|
||||||
GameEntity newTarget = (GameEntity) repParams.get(AbilityKey.Affected);
|
|
||||||
// check if this is still the affected card or player
|
|
||||||
if (this.equals(newTarget)) {
|
|
||||||
return newDamage;
|
|
||||||
} else {
|
|
||||||
if (prevention) {
|
|
||||||
newDamage = newTarget.preventDamage(newDamage, source, isCombat, preventMap, cause);
|
|
||||||
}
|
}
|
||||||
newTarget.addDamageAfterPrevention(newDamage, source, isCombat, damageMap, counterTable);
|
|
||||||
|
if (isCombat && getGame().getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final Card ca : getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||||
|
for (final ReplacementEffect re : ca.getReplacementEffects()) {
|
||||||
|
if (!re.getMode().equals(ReplacementType.DamageDone) ||
|
||||||
|
(!re.hasParam("PreventionEffect") && !re.hasParam("Prevent"))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!re.matchesValidParam("ValidSource", source)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!re.matchesValidParam("ValidTarget", this)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (re.hasParam("IsCombat")) {
|
||||||
|
if (re.getParam("IsCombat").equals("True") != isCombat) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (re.hasParam("Prevent")) {
|
||||||
|
return 0;
|
||||||
|
} else if (re.getOverridingAbility() != null) {
|
||||||
|
SpellAbility repSA = re.getOverridingAbility();
|
||||||
|
if (repSA.getApi() == ApiType.ReplaceDamage) {
|
||||||
|
return Math.max(0, damage - AbilityUtils.calculateAmount(ca, repSA.getParam("Amount"), repSA));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function handles damage after replacement and prevention effects are applied
|
return Math.max(0, damage - possiblePrevention);
|
||||||
public abstract int addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat, CardDamageMap damageMap, GameEntityCounterTable counterTable);
|
}
|
||||||
|
|
||||||
// This should be also usable by the AI to forecast an effect (so it must
|
|
||||||
// not change the game state)
|
|
||||||
public abstract int staticDamagePrevention(final int damage, final Card source, final boolean isCombat, final boolean isTest);
|
|
||||||
|
|
||||||
// This should be also usable by the AI to forecast an effect (so it must
|
// This should be also usable by the AI to forecast an effect (so it must
|
||||||
// not change the game state)
|
// not change the game state)
|
||||||
public abstract int staticReplaceDamage(final int damage, final Card source, final boolean isCombat);
|
public abstract int staticReplaceDamage(final int damage, final Card source, final boolean isCombat);
|
||||||
|
|
||||||
public final int preventDamage(
|
|
||||||
final int damage, final Card source, final boolean isCombat, CardDamageMap preventMap,
|
|
||||||
final SpellAbility cause) {
|
|
||||||
if (!source.canDamagePrevented(isCombat)) {
|
|
||||||
return damage;
|
|
||||||
}
|
|
||||||
|
|
||||||
int restDamage = damage;
|
|
||||||
|
|
||||||
// first try to replace the damage
|
|
||||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
|
|
||||||
repParams.put(AbilityKey.DamageSource, source);
|
|
||||||
repParams.put(AbilityKey.DamageAmount, damage);
|
|
||||||
repParams.put(AbilityKey.IsCombat, isCombat);
|
|
||||||
repParams.put(AbilityKey.Prevention, true);
|
|
||||||
repParams.put(AbilityKey.PreventMap, preventMap);
|
|
||||||
if (cause != null) {
|
|
||||||
repParams.put(AbilityKey.Cause, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (getGame().getReplacementHandler().run(ReplacementType.DamageDone, repParams)) {
|
|
||||||
case NotReplaced:
|
|
||||||
restDamage = damage;
|
|
||||||
break;
|
|
||||||
case Updated:
|
|
||||||
restDamage = (int) repParams.get(AbilityKey.DamageAmount);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
restDamage = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// then apply static Damage Prevention effects
|
|
||||||
restDamage = staticDamagePrevention(restDamage, source, isCombat, false);
|
|
||||||
|
|
||||||
// then apply ShieldEffects with Special Effect
|
|
||||||
restDamage = preventShieldEffect(restDamage);
|
|
||||||
|
|
||||||
// then do Shield with only number
|
|
||||||
if (restDamage <= 0) {
|
|
||||||
restDamage = 0;
|
|
||||||
} else if (restDamage >= getPreventNextDamage()) {
|
|
||||||
restDamage = restDamage - getPreventNextDamage();
|
|
||||||
setPreventNextDamage(0);
|
|
||||||
} else {
|
|
||||||
setPreventNextDamage(getPreventNextDamage() - restDamage);
|
|
||||||
restDamage = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if damage is greater than restDamage, damage was prevented
|
|
||||||
if (damage > restDamage) {
|
|
||||||
int prevent = damage - restDamage;
|
|
||||||
preventMap.put(source, this, damage - restDamage);
|
|
||||||
|
|
||||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
|
||||||
runParams.put(AbilityKey.DamageTarget, this);
|
|
||||||
runParams.put(AbilityKey.DamageAmount, prevent);
|
|
||||||
runParams.put(AbilityKey.DamageSource, source);
|
|
||||||
runParams.put(AbilityKey.IsCombatDamage, isCombat);
|
|
||||||
|
|
||||||
getGame().getTriggerHandler().runTrigger(TriggerType.DamagePrevented, runParams, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return restDamage;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract int preventShieldEffect(final int damage);
|
|
||||||
|
|
||||||
public int getPreventNextDamage() {
|
|
||||||
return preventNextDamage;
|
|
||||||
}
|
|
||||||
public void setPreventNextDamage(final int n) {
|
|
||||||
preventNextDamage = n;
|
|
||||||
}
|
|
||||||
public void addPreventNextDamage(final int n) {
|
|
||||||
preventNextDamage += n;
|
|
||||||
}
|
|
||||||
public void subtractPreventNextDamage(final int n) {
|
|
||||||
preventNextDamage -= n;
|
|
||||||
}
|
|
||||||
public void resetPreventNextDamage() {
|
|
||||||
preventNextDamage = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreventNextDamageWithEffect
|
|
||||||
public Map<Card, Map<String, String>> getPreventNextDamageWithEffect() {
|
|
||||||
return preventionShieldsWithEffects;
|
|
||||||
}
|
|
||||||
public int getPreventNextDamageTotalShields() {
|
public int getPreventNextDamageTotalShields() {
|
||||||
int shields = preventNextDamage;
|
return getGame().getReplacementHandler().getTotalPreventionShieldAmount(this);
|
||||||
for (final Map<String, String> value : preventionShieldsWithEffects.values()) {
|
|
||||||
shields += Integer.valueOf(value.get("ShieldAmount"));
|
|
||||||
}
|
|
||||||
return shields;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Adds a damage prevention shield with an effect that happens at time of prevention.
|
|
||||||
* @param shieldSource - The source card which generated the shield
|
|
||||||
* @param effectMap - A map of the effect occurring with the damage prevention
|
|
||||||
*/
|
|
||||||
public void addPreventNextDamageWithEffect(final Card shieldSource, Map<String, String> effectMap) {
|
|
||||||
if (preventionShieldsWithEffects.containsKey(shieldSource)) {
|
|
||||||
int currentShields = Integer.valueOf(preventionShieldsWithEffects.get(shieldSource).get("ShieldAmount"));
|
|
||||||
currentShields += Integer.valueOf(effectMap.get("ShieldAmount"));
|
|
||||||
effectMap.put("ShieldAmount", Integer.toString(currentShields));
|
|
||||||
preventionShieldsWithEffects.put(shieldSource, effectMap);
|
|
||||||
} else {
|
|
||||||
preventionShieldsWithEffects.put(shieldSource, effectMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void subtractPreventNextDamageWithEffect(final Card shieldSource, final int n) {
|
|
||||||
int currentShields = Integer.valueOf(preventionShieldsWithEffects.get(shieldSource).get("ShieldAmount"));
|
|
||||||
if (currentShields > n) {
|
|
||||||
preventionShieldsWithEffects.get(shieldSource).put("ShieldAmount", String.valueOf(currentShields - n));
|
|
||||||
} else {
|
|
||||||
preventionShieldsWithEffects.remove(shieldSource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void resetPreventNextDamageWithEffect() {
|
|
||||||
preventionShieldsWithEffects.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract boolean hasKeyword(final String keyword);
|
public abstract boolean hasKeyword(final String keyword);
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
|
|||||||
|
|
||||||
private Table<Optional<Player>, GameEntity, Map<CounterType, Integer>> dataMap = HashBasedTable.create();
|
private Table<Optional<Player>, GameEntity, Map<CounterType, Integer>> dataMap = HashBasedTable.create();
|
||||||
|
|
||||||
|
public GameEntityCounterTable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameEntityCounterTable(Table<Optional<Player>, GameEntity, Map<CounterType, Integer>> counterTable) {
|
||||||
|
putAll(counterTable);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see com.google.common.collect.ForwardingTable#delegate()
|
* @see com.google.common.collect.ForwardingTable#delegate()
|
||||||
|
|||||||
@@ -101,9 +101,10 @@ public class GameFormat implements Comparable<GameFormat> {
|
|||||||
this.effectiveDate = effectiveDate;
|
this.effectiveDate = effectiveDate;
|
||||||
|
|
||||||
if(sets != null) {
|
if(sets != null) {
|
||||||
|
StaticData data = StaticData.instance();
|
||||||
Set<String> parsedSets = new HashSet<>();
|
Set<String> parsedSets = new HashSet<>();
|
||||||
for (String set : sets) {
|
for (String set : sets) {
|
||||||
if (StaticData.instance().getEditions().get(set) == null) {
|
if (data.getCardEdition(set) == null) {
|
||||||
System.out.println("Set " + set + " in format " + fName + " does not match any valid editions!");
|
System.out.println("Set " + set + " in format " + fName + " does not match any valid editions!");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ public class StaticEffect {
|
|||||||
// remove keywords
|
// remove keywords
|
||||||
// (Although nothing uses it at this time)
|
// (Although nothing uses it at this time)
|
||||||
if (hasParam("AddKeyword") || hasParam("RemoveKeyword")
|
if (hasParam("AddKeyword") || hasParam("RemoveKeyword")
|
||||||
|| hasParam("RemoveAllAbilities")) {
|
|| hasParam("RemoveAllAbilities") || hasParam("RemoveLandTypes")) {
|
||||||
affectedCard.removeChangedCardKeywords(getTimestamp());
|
affectedCard.removeChangedCardKeywords(getTimestamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ public enum AbilityKey {
|
|||||||
DefendingPlayer("DefendingPlayer"),
|
DefendingPlayer("DefendingPlayer"),
|
||||||
Destination("Destination"),
|
Destination("Destination"),
|
||||||
Devoured("Devoured"),
|
Devoured("Devoured"),
|
||||||
|
DividedShieldAmount("DividedShieldAmount"),
|
||||||
EchoPaid("EchoPaid"),
|
EchoPaid("EchoPaid"),
|
||||||
EffectOnly("EffectOnly"),
|
EffectOnly("EffectOnly"),
|
||||||
Exploited("Exploited"),
|
Exploited("Exploited"),
|
||||||
@@ -96,12 +97,14 @@ public enum AbilityKey {
|
|||||||
PayingMana("PayingMana"),
|
PayingMana("PayingMana"),
|
||||||
Phase("Phase"),
|
Phase("Phase"),
|
||||||
Player("Player"),
|
Player("Player"),
|
||||||
|
PreventedAmount("PreventedAmount"),
|
||||||
PreventMap("PreventMap"),
|
PreventMap("PreventMap"),
|
||||||
Prevention("Prevention"),
|
Prevention("Prevention"),
|
||||||
Produced("Produced"),
|
Produced("Produced"),
|
||||||
Regeneration("Regeneration"),
|
Regeneration("Regeneration"),
|
||||||
ReplacementEffect("ReplacementEffect"),
|
ReplacementEffect("ReplacementEffect"),
|
||||||
ReplacementResult("ReplacementResult"),
|
ReplacementResult("ReplacementResult"),
|
||||||
|
ReplacementResultMap("ReplacementResultMap"),
|
||||||
Result("Result"),
|
Result("Result"),
|
||||||
Scheme("Scheme"),
|
Scheme("Scheme"),
|
||||||
Source("Source"),
|
Source("Source"),
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -97,6 +97,7 @@ public enum ApiType {
|
|||||||
LookAt (LookAtEffect.class),
|
LookAt (LookAtEffect.class),
|
||||||
LoseLife (LifeLoseEffect.class),
|
LoseLife (LifeLoseEffect.class),
|
||||||
LosesGame (GameLossEffect.class),
|
LosesGame (GameLossEffect.class),
|
||||||
|
MakeCard (MakeCardEffect.class),
|
||||||
Mana (ManaEffect.class),
|
Mana (ManaEffect.class),
|
||||||
ManaReflected (ManaReflectedEffect.class),
|
ManaReflected (ManaReflectedEffect.class),
|
||||||
Manifest (ManifestEffect.class),
|
Manifest (ManifestEffect.class),
|
||||||
|
|||||||
@@ -21,9 +21,10 @@ import forge.game.GameObject;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardFactoryUtil;
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.card.CardZoneTable;
|
import forge.game.card.CardZoneTable;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerCollection;
|
import forge.game.player.PlayerCollection;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
@@ -55,7 +56,7 @@ public abstract class SpellAbilityEffect {
|
|||||||
public abstract void resolve(SpellAbility sa);
|
public abstract void resolve(SpellAbility sa);
|
||||||
|
|
||||||
protected String getStackDescription(final SpellAbility sa) {
|
protected String getStackDescription(final SpellAbility sa) {
|
||||||
// Unless overriden, let the spell description also be the stack description
|
// Unless overridden, let the spell description also be the stack description
|
||||||
return sa.getDescription();
|
return sa.getDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@ public abstract class SpellAbilityEffect {
|
|||||||
|
|
||||||
if (sa.hasParam("Announce")) {
|
if (sa.hasParam("Announce")) {
|
||||||
String svar = sa.getParam("Announce");
|
String svar = sa.getParam("Announce");
|
||||||
int amount = CardFactoryUtil.xCount(sa.getHostCard(), sa.getSVar(svar));
|
int amount = AbilityUtils.calculateAmount(sa.getHostCard(), svar, sa);
|
||||||
sb.append(" ");
|
sb.append(" ");
|
||||||
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace(svar,"=",String.valueOf(amount))));
|
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace(svar,"=",String.valueOf(amount))));
|
||||||
} else{
|
} else{
|
||||||
@@ -198,7 +199,7 @@ public abstract class SpellAbilityEffect {
|
|||||||
// Players
|
// Players
|
||||||
protected final static PlayerCollection getTargetPlayers(final SpellAbility sa) { return getPlayers(false, "Defined", sa); }
|
protected final static PlayerCollection getTargetPlayers(final SpellAbility sa) { return getPlayers(false, "Defined", sa); }
|
||||||
protected final static PlayerCollection getTargetPlayers(final SpellAbility sa, final String definedParam) { return getPlayers(false, definedParam, sa); }
|
protected final static PlayerCollection getTargetPlayers(final SpellAbility sa, final String definedParam) { return getPlayers(false, definedParam, sa); }
|
||||||
protected final static PlayerCollection getDefinedPlayersOrTargeted(final SpellAbility sa ) { return getPlayers(true, "Defined", sa); }
|
protected final static PlayerCollection getDefinedPlayersOrTargeted(final SpellAbility sa) { return getPlayers(true, "Defined", sa); }
|
||||||
protected final static PlayerCollection getDefinedPlayersOrTargeted(final SpellAbility sa, final String definedParam) { return getPlayers(true, definedParam, sa); }
|
protected final static PlayerCollection getDefinedPlayersOrTargeted(final SpellAbility sa, final String definedParam) { return getPlayers(true, definedParam, sa); }
|
||||||
|
|
||||||
private static PlayerCollection getPlayers(final boolean definedFirst, final String definedParam, final SpellAbility sa) {
|
private static PlayerCollection getPlayers(final boolean definedFirst, final String definedParam, final SpellAbility sa) {
|
||||||
@@ -218,7 +219,6 @@ public abstract class SpellAbilityEffect {
|
|||||||
: AbilityUtils.getDefinedSpellAbilities(sa.getHostCard(), sa.getParam(definedParam), sa);
|
: AbilityUtils.getDefinedSpellAbilities(sa.getHostCard(), sa.getParam(definedParam), sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Targets of card or player type
|
// Targets of card or player type
|
||||||
protected final static List<GameEntity> getTargetEntities(final SpellAbility sa) { return getEntities(false, "Defined", sa); }
|
protected final static List<GameEntity> getTargetEntities(final SpellAbility sa) { return getEntities(false, "Defined", sa); }
|
||||||
protected final static List<GameEntity> getTargetEntities(final SpellAbility sa, final String definedParam) { return getEntities(false, definedParam, sa); }
|
protected final static List<GameEntity> getTargetEntities(final SpellAbility sa, final String definedParam) { return getEntities(false, definedParam, sa); }
|
||||||
@@ -297,7 +297,7 @@ public abstract class SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
delTrig.append("| TriggerDescription$ ").append(desc);
|
delTrig.append("| TriggerDescription$ ").append(desc);
|
||||||
|
|
||||||
final Trigger trig = TriggerHandler.parseTrigger(delTrig.toString(), sa.getHostCard(), intrinsic);
|
final Trigger trig = TriggerHandler.parseTrigger(delTrig.toString(), CardUtil.getLKICopy(sa.getHostCard()), intrinsic);
|
||||||
for (final Card c : crds) {
|
for (final Card c : crds) {
|
||||||
trig.addRemembered(c);
|
trig.addRemembered(c);
|
||||||
|
|
||||||
@@ -576,6 +576,8 @@ public abstract class SpellAbilityEffect {
|
|||||||
|
|
||||||
GameEntity defender = null;
|
GameEntity defender = null;
|
||||||
FCollection<GameEntity> defs = null;
|
FCollection<GameEntity> defs = null;
|
||||||
|
// important to update defenders here, maybe some PW got removed
|
||||||
|
combat.initConstraints();
|
||||||
if ("True".equalsIgnoreCase(attacking)) {
|
if ("True".equalsIgnoreCase(attacking)) {
|
||||||
defs = (FCollection<GameEntity>) combat.getDefenders();
|
defs = (FCollection<GameEntity>) combat.getDefenders();
|
||||||
} else if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
|
} else if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
|
||||||
@@ -697,4 +699,52 @@ public abstract class SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static void addUntilCommand(final SpellAbility sa, GameCommand until) {
|
||||||
|
Card host = sa.getHostCard();
|
||||||
|
final Game game = host.getGame();
|
||||||
|
final String duration = sa.getParam("Duration");
|
||||||
|
// in case host was LKI
|
||||||
|
if (host.isLKI()) {
|
||||||
|
host = game.getCardState(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("UntilEndOfCombat".equals(duration)) {
|
||||||
|
game.getEndOfCombat().addUntil(until);
|
||||||
|
} else if ("UntilYourNextUpkeep".equals(duration)) {
|
||||||
|
game.getUpkeep().addUntil(sa.getActivatingPlayer(), until);
|
||||||
|
} else if ("UntilTheEndOfYourNextUpkeep".equals(duration)) {
|
||||||
|
if (game.getPhaseHandler().is(PhaseType.UPKEEP)) {
|
||||||
|
game.getUpkeep().registerUntilEnd(host.getController(), until);
|
||||||
|
} else {
|
||||||
|
game.getUpkeep().addUntilEnd(host.getController(), until);
|
||||||
|
}
|
||||||
|
} else if ("UntilTheEndOfYourNextTurn".equals(duration)) {
|
||||||
|
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||||
|
game.getEndOfTurn().registerUntilEnd(sa.getActivatingPlayer(), until);
|
||||||
|
} else {
|
||||||
|
game.getEndOfTurn().addUntilEnd(sa.getActivatingPlayer(), until);
|
||||||
|
}
|
||||||
|
} else if (duration != null && duration.startsWith("UntilAPlayerCastSpell")) {
|
||||||
|
game.getStack().addCastCommand(duration.split(" ")[1], until);
|
||||||
|
} else if ("UntilHostLeavesPlay".equals(duration)) {
|
||||||
|
host.addLeavesPlayCommand(until);
|
||||||
|
} else if ("UntilHostLeavesPlayOrEOT".equals(duration)) {
|
||||||
|
host.addLeavesPlayCommand(until);
|
||||||
|
game.getEndOfTurn().addUntil(until);
|
||||||
|
} else if ("UntilLoseControlOfHost".equals(duration)) {
|
||||||
|
host.addLeavesPlayCommand(until);
|
||||||
|
host.addChangeControllerCommand(until);
|
||||||
|
} else if ("UntilYourNextTurn".equals(duration)) {
|
||||||
|
game.getCleanup().addUntil(sa.getActivatingPlayer(), until);
|
||||||
|
} else if ("UntilUntaps".equals(duration)) {
|
||||||
|
host.addUntapCommand(until);
|
||||||
|
} else if ("UntilUnattached".equals(duration)) {
|
||||||
|
sa.getHostCard().addUnattachCommand(until);
|
||||||
|
} else if ("UntilFacedown".equals(duration)) {
|
||||||
|
sa.getHostCard().addFacedownCommand(until);
|
||||||
|
}else {
|
||||||
|
game.getEndOfTurn().addUntil(until);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,11 @@ public class AddTurnEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getStackDescription(SpellAbility sa) {
|
protected String getStackDescription(SpellAbility sa) {
|
||||||
|
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
final int numTurns = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumTurns"), sa);
|
final int numTurns = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumTurns"), sa);
|
||||||
|
|
||||||
List<Player> tgtPlayers = getTargetPlayers(sa);
|
List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||||
|
|
||||||
|
|
||||||
for (final Player player : tgtPlayers) {
|
for (final Player player : tgtPlayers) {
|
||||||
sb.append(player).append(" ");
|
sb.append(player).append(" ");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
|||||||
// Every Animate event needs a unique time stamp
|
// Every Animate event needs a unique time stamp
|
||||||
final long timestamp = game.getNextTimestamp();
|
final long timestamp = game.getNextTimestamp();
|
||||||
|
|
||||||
final boolean permanent = sa.hasParam("Permanent");
|
final boolean permanent = "Permanent".equals(sa.getParam("Duration"));
|
||||||
|
|
||||||
final CardType types = new CardType(true);
|
final CardType types = new CardType(true);
|
||||||
if (sa.hasParam("Types")) {
|
if (sa.hasParam("Types")) {
|
||||||
@@ -158,13 +158,7 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!permanent) {
|
if (!permanent) {
|
||||||
if (sa.hasParam("UntilEndOfCombat")) {
|
addUntilCommand(sa, unanimate);
|
||||||
game.getEndOfCombat().addUntil(unanimate);
|
|
||||||
} else if (sa.hasParam("UntilYourNextTurn")) {
|
|
||||||
game.getCleanup().addUntil(host.getController(), unanimate);
|
|
||||||
} else {
|
|
||||||
game.getEndOfTurn().addUntil(unanimate);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // animateAllResolve
|
} // animateAllResolve
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ public class AnimateEffect extends AnimateEffectBase {
|
|||||||
String animateImprinted = null;
|
String animateImprinted = null;
|
||||||
|
|
||||||
//if host is not on the battlefield don't apply
|
//if host is not on the battlefield don't apply
|
||||||
if ((sa.hasParam("UntilHostLeavesPlay") || sa.hasParam("UntilLoseControlOfHost"))
|
if (("UntilHostLeavesPlay".equals(sa.getParam("Duration")) || "UntilLoseControlOfHost".equals(sa.getParam("Duration")))
|
||||||
&& !sa.getHostCard().isInPlay()) {
|
&& !source.isInPlay()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ public class AnimateEffect extends AnimateEffectBase {
|
|||||||
toughness = AbilityUtils.calculateAmount(host, sa.getParam("Toughness"), sa);
|
toughness = AbilityUtils.calculateAmount(host, sa.getParam("Toughness"), sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean permanent = sa.hasParam("Permanent");
|
final boolean permanent = "Permanent".equals(sa.getParam("Duration"));
|
||||||
final List<String> types = Lists.newArrayList();
|
final List<String> types = Lists.newArrayList();
|
||||||
if (sa.hasParam("Types")) {
|
if (sa.hasParam("Types")) {
|
||||||
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
|
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
|
||||||
@@ -296,15 +296,16 @@ public class AnimateEffect extends AnimateEffectBase {
|
|||||||
// sb.append(abilities)
|
// sb.append(abilities)
|
||||||
// sb.append(triggers)
|
// sb.append(triggers)
|
||||||
if (!permanent) {
|
if (!permanent) {
|
||||||
if (sa.hasParam("UntilEndOfCombat")) {
|
final String duration = sa.getParam("Duration");
|
||||||
|
if ("UntilEndOfCombat".equals(duration)) {
|
||||||
sb.append(" until end of combat.");
|
sb.append(" until end of combat.");
|
||||||
} else if (sa.hasParam("UntilHostLeavesPlay")) {
|
} else if ("UntilHostLeavesPlay".equals(duration)) {
|
||||||
sb.append(" until ").append(host).append(" leaves the battlefield.");
|
sb.append(" until ").append(host).append(" leaves the battlefield.");
|
||||||
} else if (sa.hasParam("UntilYourNextUpkeep")) {
|
} else if ("UntilYourNextUpkeep".equals(duration)) {
|
||||||
sb.append(" until your next upkeep.");
|
sb.append(" until your next upkeep.");
|
||||||
} else if (sa.hasParam("UntilYourNextTurn")) {
|
} else if ("UntilYourNextTurn".equals(duration)) {
|
||||||
sb.append(" until your next turn.");
|
sb.append(" until your next turn.");
|
||||||
} else if (sa.hasParam("UntilControllerNextUntap")) {
|
} else if ("UntilControllerNextUntap".equals(duration)) {
|
||||||
sb.append(" until its controller's next untap step.");
|
sb.append(" until its controller's next untap step.");
|
||||||
} else {
|
} else {
|
||||||
sb.append(" until end of turn.");
|
sb.append(" until end of turn.");
|
||||||
@@ -313,7 +314,6 @@ public class AnimateEffect extends AnimateEffectBase {
|
|||||||
sb.append(".");
|
sb.append(".");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import forge.game.ability.SpellAbilityEffect;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.event.GameEventCardStatsChanged;
|
import forge.game.event.GameEventCardStatsChanged;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseType;
|
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementHandler;
|
import forge.game.replacement.ReplacementHandler;
|
||||||
import forge.game.spellability.AbilityStatic;
|
import forge.game.spellability.AbilityStatic;
|
||||||
@@ -111,6 +110,10 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
|||||||
removeEnchantmentTypes = true;
|
removeEnchantmentTypes = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("RememberAnimated")) {
|
||||||
|
source.addRemembered(c);
|
||||||
|
}
|
||||||
|
|
||||||
if ((power != null) || (toughness != null)) {
|
if ((power != null) || (toughness != null)) {
|
||||||
c.addNewPT(power, toughness, timestamp);
|
c.addNewPT(power, toughness, timestamp);
|
||||||
}
|
}
|
||||||
@@ -214,30 +217,11 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
|||||||
addedStaticAbilities, removeAll, false, removeLandTypes, timestamp);
|
addedStaticAbilities, removeAll, false, removeLandTypes, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.hasParam("Permanent")) {
|
if (!"Permanent".equals(sa.getParam("Duration"))) {
|
||||||
if (sa.hasParam("UntilEndOfCombat")) {
|
if ("UntilControllerNextUntap".equals(sa.getParam("Duration"))) {
|
||||||
game.getEndOfCombat().addUntil(unanimate);
|
|
||||||
} else if (sa.hasParam("UntilHostLeavesPlay")) {
|
|
||||||
source.addLeavesPlayCommand(unanimate);
|
|
||||||
} else if (sa.hasParam("UntilLoseControlOfHost")) {
|
|
||||||
sa.getHostCard().addLeavesPlayCommand(unanimate);
|
|
||||||
sa.getHostCard().addChangeControllerCommand(unanimate);
|
|
||||||
} else if (sa.hasParam("UntilYourNextUpkeep")) {
|
|
||||||
game.getUpkeep().addUntil(source.getController(), unanimate);
|
|
||||||
} else if (sa.hasParam("UntilTheEndOfYourNextUpkeep")) {
|
|
||||||
if (game.getPhaseHandler().is(PhaseType.UPKEEP)) {
|
|
||||||
game.getUpkeep().registerUntilEnd(source.getController(), unanimate);
|
|
||||||
} else {
|
|
||||||
game.getUpkeep().addUntilEnd(source.getController(), unanimate);
|
|
||||||
}
|
|
||||||
} else if (sa.hasParam("UntilControllerNextUntap")) {
|
|
||||||
game.getUntap().addUntil(c.getController(), unanimate);
|
game.getUntap().addUntil(c.getController(), unanimate);
|
||||||
} else if (sa.hasParam("UntilAPlayerCastSpell")) {
|
|
||||||
game.getStack().addCastCommand(sa.getParam("UntilAPlayerCastSpell"), unanimate);
|
|
||||||
} else if (sa.hasParam("UntilYourNextTurn")) {
|
|
||||||
game.getCleanup().addUntil(source.getController(), unanimate);
|
|
||||||
} else {
|
} else {
|
||||||
game.getEndOfTurn().addUntil(unanimate);
|
addUntilCommand(sa, unanimate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class AssignGroupEffect extends SpellAbilityEffect {
|
|||||||
Player chooser = sa.getActivatingPlayer();
|
Player chooser = sa.getActivatingPlayer();
|
||||||
if (sa.hasParam("Chooser")) {
|
if (sa.hasParam("Chooser")) {
|
||||||
final String choose = sa.getParam("Chooser");
|
final String choose = sa.getParam("Chooser");
|
||||||
chooser = AbilityUtils.getDefinedPlayers(sa.getHostCard(), choose, sa).get(0);
|
chooser = AbilityUtils.getDefinedPlayers(host, choose, sa).get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Multimap<SpellAbility, GameObject> result = ArrayListMultimap.create();
|
Multimap<SpellAbility, GameObject> result = ArrayListMultimap.create();
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class BlockEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
List<Card> attackers = new ArrayList<>();
|
List<Card> attackers = new ArrayList<>();
|
||||||
if (sa.hasParam("DefinedAttacker")) {
|
if (sa.hasParam("DefinedAttacker")) {
|
||||||
for (final Card attacker : AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa)) {
|
for (final Card attacker : AbilityUtils.getDefinedCards(host, sa.getParam("DefinedAttacker"), sa)) {
|
||||||
if (combat.isAttacking(attacker))
|
if (combat.isAttacking(attacker))
|
||||||
attackers.add(attacker);
|
attackers.add(attacker);
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ public class BlockEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
List<Card> blockers = new ArrayList<>();
|
List<Card> blockers = new ArrayList<>();
|
||||||
if (sa.hasParam("DefinedBlocker")) {
|
if (sa.hasParam("DefinedBlocker")) {
|
||||||
for (final Card blocker : AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedBlocker"), sa)) {
|
for (final Card blocker : AbilityUtils.getDefinedCards(host, sa.getParam("DefinedBlocker"), sa)) {
|
||||||
if (blocker.isCreature() && blocker.isInZone(ZoneType.Battlefield))
|
if (blocker.isCreature() && blocker.isInZone(ZoneType.Battlefield))
|
||||||
blockers.add(blocker);
|
blockers.add(blocker);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ public class CamouflageEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.") &&
|
if (blockers.size() < CombatUtil.getMinNumBlockersForAttacker(attacker, defender)) {
|
||||||
blockers.size() < defender.getCreaturesInPlay().size() ||
|
|
||||||
blockers.size() < CombatUtil.needsBlockers(attacker)) {
|
|
||||||
// If not enough remaining creatures to block, don't add them as blocker
|
// If not enough remaining creatures to block, don't add them as blocker
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,21 +78,17 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
|||||||
TargetChoices newTargetBlock = oldTargetBlock.clone();
|
TargetChoices newTargetBlock = oldTargetBlock.clone();
|
||||||
// gets the divied value from old target
|
// gets the divied value from old target
|
||||||
Integer div = oldTargetBlock.getDividedValue(oldTarget);
|
Integer div = oldTargetBlock.getDividedValue(oldTarget);
|
||||||
newTargetBlock.remove(oldTarget);
|
|
||||||
replaceIn.updateTarget(newTargetBlock, sa.getHostCard());
|
|
||||||
// 3. test if updated choices would be correct.
|
// 3. test if updated choices would be correct.
|
||||||
GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa, "DefinedMagnet"), null);
|
GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa, "DefinedMagnet"), null);
|
||||||
|
|
||||||
if (replaceIn.getSpellAbility(true).canTarget(newTarget)) {
|
if (replaceIn.getSpellAbility(true).canTarget(newTarget)) {
|
||||||
|
newTargetBlock.remove(oldTarget);
|
||||||
newTargetBlock.add(newTarget);
|
newTargetBlock.add(newTarget);
|
||||||
if (div != null) {
|
if (div != null) {
|
||||||
newTargetBlock.addDividedAllocation(newTarget, div);
|
newTargetBlock.addDividedAllocation(newTarget, div);
|
||||||
}
|
}
|
||||||
replaceIn.updateTarget(newTargetBlock, sa.getHostCard());
|
replaceIn.updateTarget(newTargetBlock, sa.getHostCard());
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
replaceIn.updateTarget(oldTargetBlock, sa.getHostCard());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
while(changingTgtSI != null) {
|
while(changingTgtSI != null) {
|
||||||
@@ -103,6 +99,14 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
|||||||
int div = changingTgtSA.getTotalDividedValue();
|
int div = changingTgtSA.getTotalDividedValue();
|
||||||
changingTgtSA.resetTargets();
|
changingTgtSA.resetTargets();
|
||||||
List<GameEntity> candidates = changingTgtSA.getTargetRestrictions().getAllCandidates(changingTgtSA, true);
|
List<GameEntity> candidates = changingTgtSA.getTargetRestrictions().getAllCandidates(changingTgtSA, true);
|
||||||
|
if (sa.hasParam("RandomTargetRestriction")) {
|
||||||
|
candidates.removeIf(new java.util.function.Predicate<GameEntity>() {
|
||||||
|
@Override
|
||||||
|
public boolean test(GameEntity c) {
|
||||||
|
return !c.isValid(sa.getParam("RandomTargetRestriction").split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
GameEntity choice = Aggregates.random(candidates);
|
GameEntity choice = Aggregates.random(candidates);
|
||||||
changingTgtSA.getTargets().add(choice);
|
changingTgtSA.getTargets().add(choice);
|
||||||
if (changingTgtSA.isDividedAsYouChoose()) {
|
if (changingTgtSA.isDividedAsYouChoose()) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class ChangeTextEffect extends SpellAbilityEffect {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
final Long timestamp = Long.valueOf(game.getNextTimestamp());
|
final Long timestamp = Long.valueOf(game.getNextTimestamp());
|
||||||
final boolean permanent = sa.hasParam("Permanent");
|
final boolean permanent = "Permanent".equals(sa.getParam("Duration"));
|
||||||
|
|
||||||
final String changedColorWordOriginal, changedColorWordNew;
|
final String changedColorWordOriginal, changedColorWordNew;
|
||||||
if (sa.hasParam("ChangeColorWord")) {
|
if (sa.hasParam("ChangeColorWord")) {
|
||||||
@@ -151,7 +151,7 @@ public class ChangeTextEffect extends SpellAbilityEffect {
|
|||||||
changedTypeWordNew = null;
|
changedTypeWordNew = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean permanent = sa.hasParam("Permanent");
|
final boolean permanent = "Permanent".equals(sa.getParam("Duration"));
|
||||||
|
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
sb.append("Change the text of ");
|
sb.append("Change the text of ");
|
||||||
|
|||||||
@@ -42,8 +42,10 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
//if host is not on the battlefield don't apply
|
//if host is not on the battlefield don't apply
|
||||||
if (sa.hasParam("UntilHostLeavesPlay") && !sa.getHostCard().isInPlay()) {
|
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration")) && !source.isInPlay()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +55,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
|||||||
CardCollection cards;
|
CardCollection cards;
|
||||||
List<Player> tgtPlayers = getTargetPlayers(sa);
|
List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||||
final Game game = sa.getActivatingPlayer().getGame();
|
final Game game = sa.getActivatingPlayer().getGame();
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
|
|
||||||
if ((!sa.usesTargeting() && !sa.hasParam("Defined")) || sa.hasParam("UseAllOriginZones")) {
|
if ((!sa.usesTargeting() && !sa.hasParam("Defined")) || sa.hasParam("UseAllOriginZones")) {
|
||||||
cards = new CardCollection(game.getCardsIn(origin));
|
cards = new CardCollection(game.getCardsIn(origin));
|
||||||
@@ -122,7 +123,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("ForgetOtherRemembered")) {
|
if (sa.hasParam("ForgetOtherRemembered")) {
|
||||||
sa.getHostCard().clearRemembered();
|
source.clearRemembered();
|
||||||
}
|
}
|
||||||
|
|
||||||
final String remember = sa.getParam("RememberChanged");
|
final String remember = sa.getParam("RememberChanged");
|
||||||
@@ -203,9 +204,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
|||||||
if (sa.hasParam("ExileFaceDown")) {
|
if (sa.hasParam("ExileFaceDown")) {
|
||||||
movedCard.turnFaceDown(true);
|
movedCard.turnFaceDown(true);
|
||||||
}
|
}
|
||||||
if (sa.hasParam("Tapped")) {
|
|
||||||
movedCard.setTapped(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remember != null) {
|
if (remember != null) {
|
||||||
@@ -270,8 +268,8 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
triggerList.triggerChangesZoneAll(game, sa);
|
triggerList.triggerChangesZoneAll(game, sa);
|
||||||
|
|
||||||
if (sa.hasParam("UntilHostLeavesPlay")) {
|
if (sa.hasParam("Duration")) {
|
||||||
source.addLeavesPlayCommand(untilHostLeavesPlayCommand(triggerList, source));
|
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, source));
|
||||||
}
|
}
|
||||||
|
|
||||||
// if Shuffle parameter exists, and any amount of cards were owned by
|
// if Shuffle parameter exists, and any amount of cards were owned by
|
||||||
|
|||||||
@@ -57,17 +57,9 @@ import forge.util.collect.FCollectionView;
|
|||||||
|
|
||||||
public class ChangeZoneEffect extends SpellAbilityEffect {
|
public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||||
|
|
||||||
private boolean isHidden(SpellAbility sa) {
|
|
||||||
boolean hidden = sa.hasParam("Hidden");
|
|
||||||
if (!hidden && sa.hasParam("Origin")) {
|
|
||||||
hidden = ZoneType.isHidden(sa.getParam("Origin"));
|
|
||||||
}
|
|
||||||
return hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getStackDescription(SpellAbility sa) {
|
protected String getStackDescription(SpellAbility sa) {
|
||||||
if (isHidden(sa)) {
|
if (sa.isHidden()) {
|
||||||
return changeHiddenOriginStackDescription(sa);
|
return changeHiddenOriginStackDescription(sa);
|
||||||
}
|
}
|
||||||
return changeKnownOriginStackDescription(sa);
|
return changeKnownOriginStackDescription(sa);
|
||||||
@@ -97,13 +89,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
// Player whose cards will change zones
|
// Player whose cards will change zones
|
||||||
List<Player> fetchers = null;
|
List<Player> fetchers = null;
|
||||||
if (sa.hasParam("DefinedPlayer")) {
|
if (sa.hasParam("DefinedPlayer")) {
|
||||||
fetchers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("DefinedPlayer"), sa);
|
fetchers = AbilityUtils.getDefinedPlayers(host, sa.getParam("DefinedPlayer"), sa);
|
||||||
}
|
}
|
||||||
if (fetchers == null && sa.hasParam("ValidTgts") && sa.usesTargeting()) {
|
if (fetchers == null && sa.hasParam("ValidTgts") && sa.usesTargeting()) {
|
||||||
fetchers = Lists.newArrayList(sa.getTargets().getTargetPlayers());
|
fetchers = Lists.newArrayList(sa.getTargets().getTargetPlayers());
|
||||||
}
|
}
|
||||||
if (fetchers == null) {
|
if (fetchers == null) {
|
||||||
fetchers = Lists.newArrayList(sa.getHostCard().getController());
|
fetchers = Lists.newArrayList(host.getController());
|
||||||
}
|
}
|
||||||
|
|
||||||
final String fetcherNames = Lang.joinHomogenous(fetchers, Player.Accessors.FN_GET_NAME);
|
final String fetcherNames = Lang.joinHomogenous(fetchers, Player.Accessors.FN_GET_NAME);
|
||||||
@@ -111,7 +103,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
// Player who chooses the cards to move
|
// Player who chooses the cards to move
|
||||||
List<Player> choosers = Lists.newArrayList();
|
List<Player> choosers = Lists.newArrayList();
|
||||||
if (sa.hasParam("Chooser")) {
|
if (sa.hasParam("Chooser")) {
|
||||||
choosers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Chooser"), sa);
|
choosers = AbilityUtils.getDefinedPlayers(host, sa.getParam("Chooser"), sa);
|
||||||
}
|
}
|
||||||
if (choosers.isEmpty()) {
|
if (choosers.isEmpty()) {
|
||||||
choosers.add(sa.getActivatingPlayer());
|
choosers.add(sa.getActivatingPlayer());
|
||||||
@@ -420,11 +412,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
@Override
|
@Override
|
||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
//if host is not on the battlefield don't apply
|
//if host is not on the battlefield don't apply
|
||||||
if (sa.hasParam("UntilHostLeavesPlay") && !sa.getHostCard().isInPlay()) {
|
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration")) && !sa.getHostCard().isInPlay()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHidden(sa) && !sa.hasParam("Ninjutsu")) {
|
if (sa.isHidden() && !sa.hasParam("Ninjutsu")) {
|
||||||
changeHiddenOriginResolve(sa);
|
changeHiddenOriginResolve(sa);
|
||||||
} else {
|
} else {
|
||||||
//else if (isKnown(origin) || sa.containsKey("Ninjutsu")) {
|
//else if (isKnown(origin) || sa.containsKey("Ninjutsu")) {
|
||||||
@@ -502,7 +494,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
Player chooser = player;
|
Player chooser = player;
|
||||||
if (sa.hasParam("Chooser")) {
|
if (sa.hasParam("Chooser")) {
|
||||||
chooser = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Chooser"), sa).get(0);
|
chooser = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("Chooser"), sa).get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Card tgtC : tgtCards) {
|
for (final Card tgtC : tgtCards) {
|
||||||
@@ -510,7 +502,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
// gameCard is LKI in that case, the card is not in game anymore
|
// gameCard is LKI in that case, the card is not in game anymore
|
||||||
// or the timestamp did change
|
// or the timestamp did change
|
||||||
// this should check Self too
|
// this should check Self too
|
||||||
if (gameCard == null || !tgtC.equalsWithTimestamp(gameCard)) {
|
if (gameCard == null || !tgtC.equalsWithTimestamp(gameCard) || gameCard.isPhasedOut()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (sa.usesTargeting() && !gameCard.canBeTargetedBy(sa)) {
|
if (sa.usesTargeting() && !gameCard.canBeTargetedBy(sa)) {
|
||||||
@@ -636,8 +628,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
hostCard.removeRemembered(gameCard);
|
hostCard.removeRemembered(gameCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auras without Candidates stay in their current
|
// Auras without Candidates stay in their current location
|
||||||
// location
|
|
||||||
if (gameCard.isAura()) {
|
if (gameCard.isAura()) {
|
||||||
final SpellAbility saAura = gameCard.getFirstAttachSpell();
|
final SpellAbility saAura = gameCard.getFirstAttachSpell();
|
||||||
if (saAura != null) {
|
if (saAura != null) {
|
||||||
@@ -679,12 +670,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
game.getCombat().getBandOfAttacker(movedCard).setBlocked(false);
|
game.getCombat().getBandOfAttacker(movedCard).setBlocked(false);
|
||||||
combatChanged = true;
|
combatChanged = true;
|
||||||
}
|
}
|
||||||
if (sa.hasParam("Tapped") || sa.hasParam("Ninjutsu")) {
|
|
||||||
movedCard.setTapped(true);
|
|
||||||
}
|
|
||||||
if (sa.hasParam("Untapped")) {
|
|
||||||
movedCard.setTapped(false);
|
|
||||||
}
|
|
||||||
movedCard.setTimestamp(ts);
|
movedCard.setTimestamp(ts);
|
||||||
} else {
|
} else {
|
||||||
// might set before card is moved only for nontoken
|
// might set before card is moved only for nontoken
|
||||||
@@ -812,12 +797,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
triggerList.triggerChangesZoneAll(game, sa);
|
triggerList.triggerChangesZoneAll(game, sa);
|
||||||
counterTable.triggerCountersPutAll(game);
|
counterTable.triggerCountersPutAll(game);
|
||||||
|
|
||||||
|
|
||||||
if (sa.hasParam("AtEOT") && !triggerList.isEmpty()) {
|
if (sa.hasParam("AtEOT") && !triggerList.isEmpty()) {
|
||||||
registerDelayedTrigger(sa, sa.getParam("AtEOT"), triggerList.allCards());
|
registerDelayedTrigger(sa, sa.getParam("AtEOT"), triggerList.allCards());
|
||||||
}
|
}
|
||||||
if (sa.hasParam("UntilHostLeavesPlay")) {
|
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
||||||
hostCard.addLeavesPlayCommand(untilHostLeavesPlayCommand(triggerList, hostCard));
|
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, hostCard));
|
||||||
}
|
}
|
||||||
|
|
||||||
// for things like Gaea's Blessing
|
// for things like Gaea's Blessing
|
||||||
@@ -1292,11 +1276,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
setFaceDownState(c, sa);
|
setFaceDownState(c, sa);
|
||||||
}
|
}
|
||||||
movedCard = game.getAction().moveToPlay(c, c.getController(), sa, moveParams);
|
movedCard = game.getAction().moveToPlay(c, c.getController(), sa, moveParams);
|
||||||
if (sa.hasParam("Tapped")) {
|
|
||||||
movedCard.setTapped(true);
|
|
||||||
} else if (sa.hasParam("Untapped")) {
|
|
||||||
c.setTapped(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
movedCard.setTimestamp(ts);
|
movedCard.setTimestamp(ts);
|
||||||
}
|
}
|
||||||
@@ -1402,8 +1381,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
triggerList.triggerChangesZoneAll(game, sa);
|
triggerList.triggerChangesZoneAll(game, sa);
|
||||||
|
|
||||||
if (sa.hasParam("UntilHostLeavesPlay")) {
|
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
||||||
source.addLeavesPlayCommand(untilHostLeavesPlayCommand(triggerList, source));
|
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, source));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class ChoosePlayerEffect extends SpellAbilityEffect {
|
|||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
|
||||||
final FCollectionView<Player> choices = sa.hasParam("Choices") ? AbilityUtils.getDefinedPlayers(
|
final FCollectionView<Player> choices = sa.hasParam("Choices") ? AbilityUtils.getDefinedPlayers(
|
||||||
sa.getHostCard(), sa.getParam("Choices"), sa) : sa.getActivatingPlayer().getGame().getPlayersInTurnOrder();
|
card, sa.getParam("Choices"), sa) : sa.getActivatingPlayer().getGame().getPlayersInTurnOrder();
|
||||||
|
|
||||||
final String choiceDesc = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChoosePlayer");
|
final String choiceDesc = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChoosePlayer");
|
||||||
final boolean random = sa.hasParam("Random");
|
final boolean random = sa.hasParam("Random");
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import java.util.List;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -84,7 +84,6 @@ public class ChooseSourceEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
if (sa.hasParam("Choices")) {
|
if (sa.hasParam("Choices")) {
|
||||||
permanentSources = CardLists.getValidCards(permanentSources, sa.getParam("Choices"), host.getController(), host, sa);
|
permanentSources = CardLists.getValidCards(permanentSources, sa.getParam("Choices"), host.getController(), host, sa);
|
||||||
|
|
||||||
stackSources = CardLists.getValidCards(stackSources, sa.getParam("Choices"), host.getController(), host, sa);
|
stackSources = CardLists.getValidCards(stackSources, sa.getParam("Choices"), host.getController(), host, sa);
|
||||||
referencedSources = CardLists.getValidCards(referencedSources, sa.getParam("Choices"), host.getController(), host, sa);
|
referencedSources = CardLists.getValidCards(referencedSources, sa.getParam("Choices"), host.getController(), host, sa);
|
||||||
commandZoneSources = CardLists.getValidCards(commandZoneSources, sa.getParam("Choices"), host.getController(), host, sa);
|
commandZoneSources = CardLists.getValidCards(commandZoneSources, sa.getParam("Choices"), host.getController(), host, sa);
|
||||||
@@ -127,7 +126,7 @@ public class ChooseSourceEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String numericAmount = sa.getParamOrDefault("Amount", "1");
|
final String numericAmount = sa.getParamOrDefault("Amount", "1");
|
||||||
final int validAmount = StringUtils.isNumeric(numericAmount) ? Integer.parseInt(numericAmount) : CardFactoryUtil.xCount(host, host.getSVar(numericAmount));
|
final int validAmount = StringUtils.isNumeric(numericAmount) ? Integer.parseInt(numericAmount) : AbilityUtils.calculateAmount(host, numericAmount, sa);
|
||||||
|
|
||||||
for (final Player p : tgtPlayers) {
|
for (final Player p : tgtPlayers) {
|
||||||
final CardCollection chosen = new CardCollection();
|
final CardCollection chosen = new CardCollection();
|
||||||
@@ -137,7 +136,7 @@ public class ChooseSourceEffect extends SpellAbilityEffect {
|
|||||||
Card o = null;
|
Card o = null;
|
||||||
do {
|
do {
|
||||||
o = p.getController().chooseSingleEntityForEffect(sourcesToChooseFrom, sa, choiceTitle, null);
|
o = p.getController().chooseSingleEntityForEffect(sourcesToChooseFrom, sa, choiceTitle, null);
|
||||||
} while (o == null);
|
} while (o == null || o.getName().startsWith("--"));
|
||||||
chosen.add(o);
|
chosen.add(o);
|
||||||
sourcesToChooseFrom.remove(o);
|
sourcesToChooseFrom.remove(o);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,11 @@ public class ClashEffect extends SpellAbilityEffect {
|
|||||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||||
runParams.put(AbilityKey.Player, player);
|
runParams.put(AbilityKey.Player, player);
|
||||||
runParams.put(AbilityKey.Won, player.equals(winner) ? "True" : "False");
|
runParams.put(AbilityKey.Won, player.equals(winner) ? "True" : "False");
|
||||||
sa.getHostCard().getGame().getTriggerHandler().runTrigger(TriggerType.Clashed, runParams, false);
|
source.getGame().getTriggerHandler().runTrigger(TriggerType.Clashed, runParams, false);
|
||||||
runParams.put(AbilityKey.Player, opponent);
|
final Map<AbilityKey, Object> runParams2 = AbilityKey.newMap();
|
||||||
runParams.put(AbilityKey.Won, opponent.equals(winner) ? "True" : "False");
|
runParams2.put(AbilityKey.Player, opponent);
|
||||||
sa.getHostCard().getGame().getTriggerHandler().runTrigger(TriggerType.Clashed, runParams, false);
|
runParams2.put(AbilityKey.Won, opponent.equals(winner) ? "True" : "False");
|
||||||
|
source.getGame().getTriggerHandler().runTrigger(TriggerType.Clashed, runParams2, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package forge.game.ability.effects;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.base.Predicates;
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
@@ -143,6 +142,12 @@ public class CloneEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
tgtCard.updateStateForView();
|
tgtCard.updateStateForView();
|
||||||
|
|
||||||
|
// when clone is itself, cleanup from old abilities
|
||||||
|
if (host.equals(tgtCard)) {
|
||||||
|
tgtCard.clearImprintedCards();
|
||||||
|
tgtCard.clearRemembered();
|
||||||
|
}
|
||||||
|
|
||||||
// check if clone is now an Aura that needs to be attached
|
// check if clone is now an Aura that needs to be attached
|
||||||
if (tgtCard.isAura() && !tgtCard.isInZone(ZoneType.Battlefield)) {
|
if (tgtCard.isAura() && !tgtCard.isInZone(ZoneType.Battlefield)) {
|
||||||
AttachEffect.attachAuraOnIndirectEnterBattlefield(tgtCard);
|
AttachEffect.attachAuraOnIndirectEnterBattlefield(tgtCard);
|
||||||
@@ -150,7 +155,7 @@ public class CloneEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
if (sa.hasParam("Duration")) {
|
if (sa.hasParam("Duration")) {
|
||||||
final Card cloneCard = tgtCard;
|
final Card cloneCard = tgtCard;
|
||||||
// if clone is temporary, target needs old values back after
|
// if clone is temporary, target needs old values back after (keep Death-Mask Duplicant working)
|
||||||
final Iterable<Card> clonedImprinted = new CardCollection(tgtCard.getImprintedCards());
|
final Iterable<Card> clonedImprinted = new CardCollection(tgtCard.getImprintedCards());
|
||||||
final Iterable<Object> clonedRemembered = new FCollection<>(tgtCard.getRemembered());
|
final Iterable<Object> clonedRemembered = new FCollection<>(tgtCard.getRemembered());
|
||||||
|
|
||||||
@@ -164,7 +169,7 @@ public class CloneEffect extends SpellAbilityEffect {
|
|||||||
cloneCard.clearImprintedCards();
|
cloneCard.clearImprintedCards();
|
||||||
cloneCard.clearRemembered();
|
cloneCard.clearRemembered();
|
||||||
// restore original Remembered and Imprinted, ignore cards from players who lost
|
// restore original Remembered and Imprinted, ignore cards from players who lost
|
||||||
cloneCard.addImprintedCards(Iterables.filter(clonedImprinted, Predicates.not(CardPredicates.inZone(ZoneType.None))));
|
cloneCard.addImprintedCards(Iterables.filter(clonedImprinted, CardPredicates.ownerLives()));
|
||||||
cloneCard.addRemembered(Iterables.filter(clonedRemembered, Player.class));
|
cloneCard.addRemembered(Iterables.filter(clonedRemembered, Player.class));
|
||||||
cloneCard.addRemembered(Iterables.filter(Iterables.filter(clonedRemembered, Card.class), CardPredicates.ownerLives()));
|
cloneCard.addRemembered(Iterables.filter(Iterables.filter(clonedRemembered, Card.class), CardPredicates.ownerLives()));
|
||||||
cloneCard.updateStateForView();
|
cloneCard.updateStateForView();
|
||||||
@@ -173,22 +178,10 @@ public class CloneEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final String duration = sa.getParam("Duration");
|
addUntilCommand(sa, unclone);
|
||||||
if (duration.equals("UntilEndOfTurn")) {
|
|
||||||
game.getEndOfTurn().addUntil(unclone);
|
|
||||||
}
|
|
||||||
else if (duration.equals("UntilYourNextTurn")) {
|
|
||||||
game.getCleanup().addUntil(host.getController(), unclone);
|
|
||||||
}
|
|
||||||
else if (duration.equals("UntilUnattached")) {
|
|
||||||
sa.getHostCard().addUnattachCommand(unclone);
|
|
||||||
}
|
|
||||||
else if (duration.equals("UntilFacedown")) {
|
|
||||||
sa.getHostCard().addFacedownCommand(unclone);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Clear Remembered and Imprint lists
|
// now we can also cleanup in case target was another card
|
||||||
tgtCard.clearRemembered();
|
tgtCard.clearRemembered();
|
||||||
tgtCard.clearImprintedCards();
|
tgtCard.clearImprintedCards();
|
||||||
|
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ public class ControlExchangeEffect extends SpellAbilityEffect {
|
|||||||
object2.setController(player1, tStamp);
|
object2.setController(player1, tStamp);
|
||||||
object1.setController(player2, tStamp);
|
object1.setController(player2, tStamp);
|
||||||
if (sa.hasParam("RememberExchanged")) {
|
if (sa.hasParam("RememberExchanged")) {
|
||||||
sa.getHostCard().addRemembered(object1);
|
host.addRemembered(object1);
|
||||||
sa.getHostCard().addRemembered(object2);
|
host.addRemembered(object2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
|||||||
protected String getStackDescription(SpellAbility sa) {
|
protected String getStackDescription(SpellAbility sa) {
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
final List<Player> newController = getTargetPlayers(sa, "NewController");
|
final List<Player> newController = getDefinedPlayersOrTargeted(sa, "NewController");
|
||||||
if (newController.isEmpty()) {
|
if (newController.isEmpty()) {
|
||||||
newController.add(sa.getActivatingPlayer());
|
newController.add(sa.getActivatingPlayer());
|
||||||
}
|
}
|
||||||
@@ -108,8 +108,8 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tgtC.equals(sa.getHostCard()) && !sa.getHostCard().getGainControlTargets().contains(tgtC)) {
|
if (!tgtC.equals(source) && !source.getGainControlTargets().contains(tgtC)) {
|
||||||
sa.getHostCard().addGainControlTarget(tgtC);
|
source.addGainControlTarget(tgtC);
|
||||||
}
|
}
|
||||||
|
|
||||||
long tStamp = game.getNextTimestamp();
|
long tStamp = game.getNextTimestamp();
|
||||||
@@ -139,24 +139,24 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
|||||||
game.fireEvent(new GameEventCardStatsChanged(tgtC));
|
game.fireEvent(new GameEventCardStatsChanged(tgtC));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remember && !sa.getHostCard().isRemembered(tgtC)) {
|
if (remember && !source.isRemembered(tgtC)) {
|
||||||
sa.getHostCard().addRemembered(tgtC);
|
source.addRemembered(tgtC);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forget && sa.getHostCard().isRemembered(tgtC)) {
|
if (forget && source.isRemembered(tgtC)) {
|
||||||
sa.getHostCard().removeRemembered(tgtC);
|
source.removeRemembered(tgtC);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lose != null) {
|
if (lose != null) {
|
||||||
final GameCommand loseControl = getLoseControlCommand(tgtC, tStamp, bTapOnLose, source);
|
final GameCommand loseControl = getLoseControlCommand(tgtC, tStamp, bTapOnLose, source);
|
||||||
if (lose.contains("LeavesPlay") && sa.getHostCard() != tgtC) { // Only return control if host and target are different cards
|
if (lose.contains("LeavesPlay") && source != tgtC) { // Only return control if host and target are different cards
|
||||||
sa.getHostCard().addLeavesPlayCommand(loseControl);
|
source.addLeavesPlayCommand(loseControl);
|
||||||
}
|
}
|
||||||
if (lose.contains("Untap")) {
|
if (lose.contains("Untap")) {
|
||||||
sa.getHostCard().addUntapCommand(loseControl);
|
source.addUntapCommand(loseControl);
|
||||||
}
|
}
|
||||||
if (lose.contains("LoseControl")) {
|
if (lose.contains("LoseControl")) {
|
||||||
sa.getHostCard().addChangeControllerCommand(loseControl);
|
source.addChangeControllerCommand(loseControl);
|
||||||
}
|
}
|
||||||
if (lose.contains("EOT")) {
|
if (lose.contains("EOT")) {
|
||||||
game.getEndOfTurn().addUntil(loseControl);
|
game.getEndOfTurn().addUntil(loseControl);
|
||||||
@@ -169,7 +169,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
|||||||
if (lose.contains("StaticCommandCheck")) {
|
if (lose.contains("StaticCommandCheck")) {
|
||||||
String leftVar = sa.getSVar(sa.getParam("StaticCommandCheckSVar"));
|
String leftVar = sa.getSVar(sa.getParam("StaticCommandCheckSVar"));
|
||||||
String rightVar = sa.getParam("StaticCommandSVarCompare");
|
String rightVar = sa.getParam("StaticCommandSVarCompare");
|
||||||
sa.getHostCard().addStaticCommandList(new Object[]{leftVar, rightVar, tgtC, loseControl});
|
source.addStaticCommandList(new Object[]{leftVar, rightVar, tgtC, loseControl});
|
||||||
}
|
}
|
||||||
if (lose.contains("UntilTheEndOfYourNextTurn")) {
|
if (lose.contains("UntilTheEndOfYourNextTurn")) {
|
||||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package forge.game.ability.effects;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
@@ -21,6 +22,7 @@ import forge.game.player.Player;
|
|||||||
import forge.game.replacement.ReplacementType;
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.util.Aggregates;
|
||||||
import forge.util.CardTranslation;
|
import forge.util.CardTranslation;
|
||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
|
|
||||||
@@ -162,6 +164,19 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
|||||||
if (sa.hasParam("MayChooseTarget")) {
|
if (sa.hasParam("MayChooseTarget")) {
|
||||||
copy.setMayChooseNewTargets(true);
|
copy.setMayChooseNewTargets(true);
|
||||||
}
|
}
|
||||||
|
if (sa.hasParam("RandomTarget")){
|
||||||
|
List<GameEntity> candidates = copy.getTargetRestrictions().getAllCandidates(chosenSA, true);
|
||||||
|
if (sa.hasParam("RandomTargetRestriction")) {
|
||||||
|
candidates.removeIf(new Predicate<GameEntity>() {
|
||||||
|
@Override
|
||||||
|
public boolean test(GameEntity c) {
|
||||||
|
return !c.isValid(sa.getParam("RandomTargetRestriction").split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
GameEntity choice = Aggregates.random(candidates);
|
||||||
|
resetFirstTargetOnCopy(copy, choice, chosenSA);
|
||||||
|
}
|
||||||
|
|
||||||
// extra case for Epic to remove the keyword and the last part of the SpellAbility
|
// extra case for Epic to remove the keyword and the last part of the SpellAbility
|
||||||
if (sa.hasParam("Epic")) {
|
if (sa.hasParam("Epic")) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
|
|||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Player activator = sa.getActivatingPlayer();
|
final Player activator = sa.getActivatingPlayer();
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
final int counterAmount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
|
final int counterAmount = AbilityUtils.calculateAmount(host, sa.getParam("CounterNum"), sa);
|
||||||
final String valid = sa.getParam("ValidCards");
|
final String valid = sa.getParam("ValidCards");
|
||||||
final ZoneType zone = sa.hasParam("ValidZone") ? ZoneType.smartValueOf(sa.getParam("ValidZone")) : ZoneType.Battlefield;
|
final ZoneType zone = sa.hasParam("ValidZone") ? ZoneType.smartValueOf(sa.getParam("ValidZone")) : ZoneType.Battlefield;
|
||||||
final boolean etbcounter = sa.hasParam("ETB");
|
final boolean etbcounter = sa.hasParam("ETB");
|
||||||
@@ -52,7 +52,7 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CardCollectionView cards = game.getCardsIn(zone);
|
CardCollectionView cards = game.getCardsIn(zone);
|
||||||
cards = CardLists.getValidCards(cards, valid, host.getController(), sa.getHostCard(), sa);
|
cards = CardLists.getValidCards(cards, valid, host.getController(), host, sa);
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
final Player pl = sa.getTargets().getFirstTargetedPlayer();
|
final Player pl = sa.getTargets().getFirstTargetedPlayer();
|
||||||
|
|||||||
@@ -155,16 +155,16 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
Player chooser = activator;
|
Player chooser = activator;
|
||||||
if (sa.hasParam("Chooser")) {
|
if (sa.hasParam("Chooser")) {
|
||||||
List<Player> choosers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Chooser"), sa);
|
List<Player> choosers = AbilityUtils.getDefinedPlayers(card, sa.getParam("Chooser"), sa);
|
||||||
if (choosers.isEmpty()) {
|
if (choosers.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chooser = choosers.get(0);
|
chooser = choosers.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
int n = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParamOrDefault("ChoiceAmount",
|
int n = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("ChoiceAmount",
|
||||||
"1"), sa);
|
"1"), sa);
|
||||||
int m = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParamOrDefault("MinChoiceAmount",
|
int m = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("MinChoiceAmount",
|
||||||
sa.getParamOrDefault("ChoiceAmount", "1")), sa);
|
sa.getParamOrDefault("ChoiceAmount", "1")), sa);
|
||||||
|
|
||||||
// no choices allowed
|
// no choices allowed
|
||||||
@@ -405,10 +405,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
|||||||
Player placer = activator;
|
Player placer = activator;
|
||||||
if (sa.hasParam("Placer")) {
|
if (sa.hasParam("Placer")) {
|
||||||
final String pstr = sa.getParam("Placer");
|
final String pstr = sa.getParam("Placer");
|
||||||
placer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), pstr, sa).get(0);
|
placer = AbilityUtils.getDefinedPlayers(card, pstr, sa).get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
int counterAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amount, sa);
|
int counterAmount = AbilityUtils.calculateAmount(card, amount, sa);
|
||||||
|
|
||||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||||
|
|
||||||
@@ -422,7 +422,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
|||||||
List<String> keywords = Arrays.asList(sa.getParam("SharedKeywords").split(" & "));
|
List<String> keywords = Arrays.asList(sa.getParam("SharedKeywords").split(" & "));
|
||||||
List<ZoneType> zones = ZoneType.listValueOf(sa.getParam("SharedKeywordsZone"));
|
List<ZoneType> zones = ZoneType.listValueOf(sa.getParam("SharedKeywordsZone"));
|
||||||
String[] restrictions = sa.hasParam("SharedRestrictions") ? sa.getParam("SharedRestrictions").split(",") : new String[]{"Card"};
|
String[] restrictions = sa.hasParam("SharedRestrictions") ? sa.getParam("SharedRestrictions").split(",") : new String[]{"Card"};
|
||||||
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard());
|
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, card);
|
||||||
for (String k : keywords) {
|
for (String k : keywords) {
|
||||||
resolvePerType(sa, placer, CounterType.getType(k), counterAmount, table);
|
resolvePerType(sa, placer, CounterType.getType(k), counterAmount, table);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
int cntToRemove = 0;
|
int cntToRemove = 0;
|
||||||
if (!num.equals("All") && !num.equals("Any")) {
|
if (!num.equals("All") && !num.equals("Any")) {
|
||||||
cntToRemove = AbilityUtils.calculateAmount(sa.getHostCard(), num, sa);
|
cntToRemove = AbilityUtils.calculateAmount(card, num, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("Optional")) {
|
if (sa.hasParam("Optional")) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user