mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 18:28: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
|
||||
*.log
|
||||
|
||||
# Ignore macOS Spotlight rubbish
|
||||
.DS_Store
|
||||
|
||||
# TODO: specify what these ignores are for (releasing?)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[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)
|
||||
|
||||
@@ -168,11 +168,11 @@ The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desk
|
||||
|
||||
## 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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
|
||||
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
|
||||
/**
|
||||
* <p>
|
||||
* ComputerUtil_Attack2 class.
|
||||
@@ -412,12 +411,10 @@ public class AiAttackController {
|
||||
|
||||
final Player opp = this.defendingOpponent;
|
||||
|
||||
// Increase the total number of blockers needed by 1 if Finest Hour in
|
||||
// play
|
||||
// Increase the total number of blockers needed by 1 if Finest Hour in play
|
||||
// (human will get an extra first attack with a creature that untaps)
|
||||
// In addition, if the computer guesses it needs no blockers, make sure
|
||||
// that
|
||||
// it won't be surprised by Exalted
|
||||
// that it won't be surprised by Exalted
|
||||
final int humanExaltedBonus = opp.countExaltedBonus();
|
||||
|
||||
if (humanExaltedBonus > 0) {
|
||||
@@ -427,8 +424,7 @@ public class AiAttackController {
|
||||
// total attack = biggest creature + exalted, *2 if Rafiq is in play
|
||||
int humanBasePower = getAttack(this.oppList.get(0)) + humanExaltedBonus;
|
||||
if (finestHour) {
|
||||
// For Finest Hour, one creature could attack and get the
|
||||
// bonus TWICE
|
||||
// For Finest Hour, one creature could attack and get the bonus TWICE
|
||||
humanBasePower = humanBasePower + humanExaltedBonus;
|
||||
}
|
||||
final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBasePower
|
||||
@@ -449,7 +445,6 @@ public class AiAttackController {
|
||||
return notNeededAsBlockers;
|
||||
}
|
||||
|
||||
// this uses a global variable, which isn't perfect
|
||||
public final boolean doesHumanAttackAndWin(final Player ai, final int nBlockingCreatures) {
|
||||
int totalAttack = 0;
|
||||
int totalPoison = 0;
|
||||
@@ -681,7 +676,6 @@ public class AiAttackController {
|
||||
* @return a {@link forge.game.combat.Combat} object.
|
||||
*/
|
||||
public final void declareAttackers(final Combat combat) {
|
||||
|
||||
if (this.attackers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -716,7 +710,7 @@ public class AiAttackController {
|
||||
int attackMax = restrict.getMax();
|
||||
if (attackMax == -1) {
|
||||
// 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) {
|
||||
@@ -776,7 +770,6 @@ public class AiAttackController {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (bAssault) {
|
||||
if (LOG_AI_ATTACKS)
|
||||
System.out.println("Assault");
|
||||
@@ -850,7 +843,6 @@ public class AiAttackController {
|
||||
// no more creatures to attack
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// *******************
|
||||
// Evaluate the creature forces
|
||||
@@ -935,12 +927,9 @@ public class AiAttackController {
|
||||
|
||||
// *********************
|
||||
// if outnumber and superior ratio work out whether attritional all out
|
||||
// attacking will work
|
||||
// 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
|
||||
// carefully, the accuracy
|
||||
// can probably be improved
|
||||
// attacking will work 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
|
||||
// carefully, the accuracy can probably be improved
|
||||
// *********************
|
||||
boolean doAttritionalAttack = false;
|
||||
// get list of attackers ordered from low power to high
|
||||
@@ -974,7 +963,6 @@ public class AiAttackController {
|
||||
doAttritionalAttack = true;
|
||||
}
|
||||
}
|
||||
// System.out.println(doAttritionalAttack + " = do attritional attack");
|
||||
// *********************
|
||||
// end attritional attack calculation
|
||||
// *********************
|
||||
@@ -1024,11 +1012,9 @@ public class AiAttackController {
|
||||
// end see how long until unblockable attackers will be fatal
|
||||
// *****************
|
||||
|
||||
|
||||
// decide on attack aggression based on a comparison of forces, life
|
||||
// totals and other considerations
|
||||
// some bad "magic numbers" here, TODO replace with nice descriptive
|
||||
// variable names
|
||||
// totals and other considerations some bad "magic numbers" here
|
||||
// TODO replace with nice descriptive variable names
|
||||
if (ratioDiff > 0 && doAttritionalAttack) {
|
||||
this.aiAggression = 5; // attack at all costs
|
||||
} 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
|
||||
// number of factors about the attacking
|
||||
// context that will be relevant to the attackers decision according to
|
||||
// the selected strategy
|
||||
// number of factors about the attacking context that will be relevant
|
||||
// to the attackers decision according to the selected strategy
|
||||
for (final Card defender : validBlockers) {
|
||||
// if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check
|
||||
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
|
||||
@@ -1299,14 +1284,11 @@ public class AiAttackController {
|
||||
}
|
||||
|
||||
if (numberOfPossibleBlockers > 2
|
||||
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, combat))
|
||||
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, combat))) {
|
||||
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, this.defendingOpponent))
|
||||
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, this.defendingOpponent))) {
|
||||
canBeBlocked = true;
|
||||
}
|
||||
/*System.out.println(attacker + " canBeKilledByOne: " + canBeKilledByOne + " canKillAll: "
|
||||
+ canKillAll + " isWorthLessThanAllKillers: " + isWorthLessThanAllKillers + " canBeBlocked: " + canBeBlocked);*/
|
||||
// decide if the creature should attack based on the prevailing strategy
|
||||
// choice in aiAggression
|
||||
// decide if the creature should attack based on the prevailing strategy choice in aiAggression
|
||||
switch (this.aiAggression) {
|
||||
case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked
|
||||
if ((canKillAll && isWorthLessThanAllKillers) || !canBeBlocked) {
|
||||
@@ -1441,7 +1423,7 @@ public class AiAttackController {
|
||||
public String toProtectAttacker(SpellAbility sa) {
|
||||
//AiAttackController is created with the selected attacker as the only entry in "attackers"
|
||||
if (sa.getApi() != ApiType.Protection || oppList.isEmpty() || getPossibleBlockers(oppList, attackers).isEmpty()) {
|
||||
return null; //not protection sa or attacker is already unblockable
|
||||
return null; //not protection sa or attacker is already unblockable
|
||||
}
|
||||
final List<String> choices = ProtectEffect.getProtectionList(sa);
|
||||
String color = ComputerUtilCard.getMostProminentColor(getPossibleBlockers(oppList, attackers)), artifact = null;
|
||||
@@ -1451,7 +1433,7 @@ public class AiAttackController {
|
||||
if (!choices.contains(color)) {
|
||||
color = null;
|
||||
}
|
||||
for (Card c : oppList) { //find a blocker that ignores the currently selected protection
|
||||
for (Card c : oppList) { //find a blocker that ignores the currently selected protection
|
||||
if (artifact != null && !c.isArtifact()) {
|
||||
artifact = null;
|
||||
}
|
||||
@@ -1484,7 +1466,7 @@ public class AiAttackController {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (color == null && artifact == null) { //nothing can make the attacker unblockable
|
||||
if (color == null && artifact == null) { //nothing can make the attacker unblockable
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1562,4 +1544,4 @@ public class AiAttackController {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // end class ComputerUtil_Attack2
|
||||
}
|
||||
|
||||
@@ -180,11 +180,9 @@ public class AiBlockController {
|
||||
|
||||
// Good Blocks means a good trade or no trade
|
||||
private void makeGoodBlocks(final Combat combat) {
|
||||
|
||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||
|
||||
for (final Card attacker : attackersLeft) {
|
||||
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|
||||
|| attacker.hasKeyword(Keyword.MENACE)) {
|
||||
@@ -192,7 +190,6 @@ public class AiBlockController {
|
||||
}
|
||||
|
||||
Card blocker = null;
|
||||
|
||||
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||
|
||||
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
|
||||
@@ -305,7 +302,6 @@ public class AiBlockController {
|
||||
}
|
||||
|
||||
Card blocker = null;
|
||||
|
||||
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||
|
||||
for (Card b : blockers) {
|
||||
@@ -366,10 +362,9 @@ public class AiBlockController {
|
||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
||||
// if the total damage of the blockgang was not enough
|
||||
// without but is enough with this blocker finish the
|
||||
// blockgang
|
||||
// without but is enough with this blocker finish the blockgang
|
||||
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) < damageNeeded
|
||||
|| CombatUtil.needsBlockers(attacker) > blockGang.size()) {
|
||||
|| CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > blockGang.size()) {
|
||||
blockGang.add(blocker);
|
||||
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) >= damageNeeded) {
|
||||
currentAttackers.remove(attacker);
|
||||
@@ -407,7 +402,7 @@ public class AiBlockController {
|
||||
boolean foundDoubleBlock = false; // if true, a good double block is found
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -444,7 +439,7 @@ public class AiBlockController {
|
||||
final int addedValue = ComputerUtilCard.evaluateCreature(blocker);
|
||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||
+ 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)
|
||||
// The attacker will be killed
|
||||
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
|
||||
@@ -454,8 +449,7 @@ public class AiBlockController {
|
||||
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
|
||||
// or life is in danger
|
||||
&& CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||
// this is needed for attackers that can't be blocked by
|
||||
// more than 1
|
||||
// this is needed for attackers that can't be blocked by more than 1
|
||||
currentAttackers.remove(attacker);
|
||||
combat.addBlocker(attacker, blocker);
|
||||
if (CombatUtil.canBlock(attacker, leader, combat)) {
|
||||
@@ -496,7 +490,7 @@ public class AiBlockController {
|
||||
final int addedValue3 = ComputerUtilCard.evaluateCreature(secondBlocker);
|
||||
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)
|
||||
// The attacker will be killed
|
||||
&& ((absorbedDamage2 + absorbedDamage > netCombatDamage && absorbedDamage3 + absorbedDamage > netCombatDamage
|
||||
@@ -510,8 +504,7 @@ public class AiBlockController {
|
||||
// or life is in danger
|
||||
&& CombatUtil.canBlock(attacker, secondBlocker, combat)
|
||||
&& CombatUtil.canBlock(attacker, thirdBlocker, combat)) {
|
||||
// this is needed for attackers that can't be blocked by
|
||||
// more than 1
|
||||
// this is needed for attackers that can't be blocked by more than 1
|
||||
currentAttackers.remove(attacker);
|
||||
combat.addBlocker(attacker, thirdBlocker);
|
||||
if (CombatUtil.canBlock(attacker, secondBlocker, combat)) {
|
||||
@@ -587,12 +580,10 @@ public class AiBlockController {
|
||||
* @param combat a {@link forge.game.combat.Combat} object.
|
||||
*/
|
||||
private void makeTradeBlocks(final Combat combat) {
|
||||
|
||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||
List<Card> killingBlockers;
|
||||
|
||||
for (final Card attacker : attackersLeft) {
|
||||
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||
|| attacker.hasKeyword(Keyword.MENACE)
|
||||
|| 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)
|
||||
private void makeChumpBlocks(final Combat combat) {
|
||||
|
||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||
|
||||
makeChumpBlocks(combat, currentAttackers);
|
||||
@@ -639,7 +629,6 @@ public class AiBlockController {
|
||||
}
|
||||
|
||||
private void makeChumpBlocks(final Combat combat, List<Card> attackers) {
|
||||
|
||||
if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
return;
|
||||
}
|
||||
@@ -694,11 +683,9 @@ public class AiBlockController {
|
||||
|
||||
// Block creatures with "can't be blocked except by two or more creatures"
|
||||
private void makeMultiChumpBlocks(final Combat combat) {
|
||||
|
||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||
|
||||
for (final Card attacker : currentAttackers) {
|
||||
|
||||
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||
&& !attacker.hasKeyword(Keyword.MENACE)
|
||||
&& !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) */
|
||||
private void reinforceBlockersAgainstTrample(final Combat combat) {
|
||||
|
||||
List<Card> chumpBlockers;
|
||||
|
||||
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
|
||||
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||
|
||||
// TODO - should check here for a "rampage-like" trigger that replaced
|
||||
// the keyword:
|
||||
// TODO - should check here for a "rampage-like" trigger that replaced the keyword:
|
||||
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
|
||||
|
||||
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 */
|
||||
private void reinforceBlockersToKill(final Combat combat) {
|
||||
|
||||
List<Card> safeBlockers;
|
||||
List<Card> blockers;
|
||||
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||
@@ -1036,27 +1020,21 @@ public class AiBlockController {
|
||||
} else {
|
||||
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)) {
|
||||
reinforceBlockersAgainstTrample(combat);
|
||||
} else {
|
||||
lifeInDanger = false;
|
||||
}
|
||||
// Support blockers not destroying the attacker with more blockers
|
||||
// to
|
||||
// try to kill the attacker
|
||||
// to try to kill the attacker
|
||||
if (!lifeInDanger) {
|
||||
reinforceBlockersToKill(combat);
|
||||
}
|
||||
|
||||
// == 2. If the AI life would still be in danger make a safer
|
||||
// approach ==
|
||||
// == 2. If the AI life would still be in danger make a safer approach ==
|
||||
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
clearBlockers(combat, possibleBlockers); // reset every block
|
||||
// assignment
|
||||
clearBlockers(combat, possibleBlockers); // reset every block assignment
|
||||
makeTradeBlocks(combat); // choose necessary trade blocks
|
||||
// if life is in danger
|
||||
makeGoodBlocks(combat);
|
||||
@@ -1066,8 +1044,7 @@ public class AiBlockController {
|
||||
} else {
|
||||
lifeInDanger = false;
|
||||
}
|
||||
// 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)) {
|
||||
reinforceBlockersAgainstTrample(combat);
|
||||
} else {
|
||||
@@ -1077,11 +1054,9 @@ public class AiBlockController {
|
||||
reinforceBlockersToKill(combat);
|
||||
}
|
||||
|
||||
// == 3. If the AI life would be in serious danger make an even
|
||||
// safer approach ==
|
||||
// == 3. If the AI life would be in serious danger make an even safer approach ==
|
||||
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
||||
clearBlockers(combat, possibleBlockers); // reset every block
|
||||
// assignment
|
||||
clearBlockers(combat, possibleBlockers); // reset every block assignment
|
||||
makeChumpBlocks(combat); // choose chump blocks
|
||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
makeTradeBlocks(combat); // choose necessary trade
|
||||
@@ -1090,15 +1065,13 @@ public class AiBlockController {
|
||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
makeGoodBlocks(combat);
|
||||
}
|
||||
// Reinforce blockers blocking attackers with trample if life is
|
||||
// still in danger
|
||||
// Reinforce blockers blocking attackers with trample if life is still in danger
|
||||
else {
|
||||
reinforceBlockersAgainstTrample(combat);
|
||||
}
|
||||
makeGangBlocks(combat);
|
||||
// Support blockers not destroying the attacker with more
|
||||
// blockers
|
||||
// to try to kill the attacker
|
||||
// blockers to try to kill the attacker
|
||||
reinforceBlockersToKill(combat);
|
||||
}
|
||||
}
|
||||
@@ -1108,7 +1081,7 @@ public class AiBlockController {
|
||||
chumpBlockers.addAll(CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."));
|
||||
// if an attacker with lure attacks - all that can block
|
||||
for (final Card blocker : blockersLeft) {
|
||||
if (CombatUtil.mustBlockAnAttacker(blocker, combat)) {
|
||||
if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) {
|
||||
chumpBlockers.add(blocker);
|
||||
}
|
||||
}
|
||||
@@ -1118,7 +1091,7 @@ public class AiBlockController {
|
||||
blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
|
||||
for (final Card blocker : blockers) {
|
||||
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 combat if able."))) {
|
||||
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,
|
||||
// unless life is low enough to be more worried about saving preserving the life total
|
||||
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.LearnAi;
|
||||
import forge.ai.simulation.SpellAbilityPicker;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.deck.CardPool;
|
||||
@@ -46,6 +47,7 @@ import forge.game.Game;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.SpellApiBased;
|
||||
@@ -78,6 +80,7 @@ import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.replacement.ReplaceMoved;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementLayer;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.LandAbility;
|
||||
@@ -307,6 +310,8 @@ public class AiController {
|
||||
}
|
||||
|
||||
exSA.setTrigger(tr);
|
||||
// need to set TriggeredObject
|
||||
exSA.setTriggeringObject(AbilityKey.Card, card);
|
||||
|
||||
// for trigger test, need to ignore the conditions
|
||||
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);
|
||||
CardCollection lands = new CardCollection(battlefield);
|
||||
lands.addAll(hand);
|
||||
lands = CardLists.filter(lands, CardPredicates.Presets.LANDS);
|
||||
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
|
||||
for (final SpellAbility sa : spellAbilities) {
|
||||
if (sa.isCycling()) {
|
||||
if (lands.size() >= Math.max(maxCmcInHand, 6)) {
|
||||
|
||||
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) {
|
||||
if (sa.isCycling()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -503,20 +513,35 @@ public class AiController {
|
||||
|
||||
//try to skip lands that enter the battlefield tapped
|
||||
if (!nonLandsInHand.isEmpty()) {
|
||||
CardCollection nonTappeddLands = new CardCollection();
|
||||
CardCollection nonTappedLands = new CardCollection();
|
||||
for (Card land : landList) {
|
||||
// Is this the best way to check if a land ETB Tapped?
|
||||
if (land.hasSVar("ETBTappedSVar")) {
|
||||
// check replacement effects if land would enter tapped or not
|
||||
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;
|
||||
}
|
||||
reSA.setActivatingPlayer(reSA.getHostCard().getController());
|
||||
if (reSA.metConditions()) {
|
||||
foundTapped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundTapped) {
|
||||
continue;
|
||||
}
|
||||
// Glacial Fortress and friends
|
||||
if (land.hasSVar("ETBCheckSVar") && CardFactoryUtil.xCount(land, land.getSVar("ETBCheckSVar")) == 0) {
|
||||
continue;
|
||||
}
|
||||
nonTappeddLands.add(land);
|
||||
|
||||
nonTappedLands.add(land);
|
||||
}
|
||||
if (!nonTappeddLands.isEmpty()) {
|
||||
landList = nonTappeddLands;
|
||||
if (!nonTappedLands.isEmpty()) {
|
||||
landList = nonTappedLands;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,8 +612,7 @@ public class AiController {
|
||||
SpellAbility currentSA = sa;
|
||||
sa.setActivatingPlayer(player);
|
||||
// check everything necessary
|
||||
|
||||
|
||||
|
||||
AiPlayDecision opinion = canPlayAndPayFor(currentSA);
|
||||
//PhaseHandler ph = game.getPhaseHandler();
|
||||
// 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1408,8 +1423,6 @@ public class AiController {
|
||||
private List<SpellAbility> singleSpellAbilityList(SpellAbility sa) {
|
||||
if (sa == null) { return null; }
|
||||
|
||||
// System.out.println("Chosen to play: " + sa);
|
||||
|
||||
final List<SpellAbility> abilities = Lists.newArrayList();
|
||||
abilities.add(sa);
|
||||
return abilities;
|
||||
@@ -1454,6 +1467,13 @@ public class AiController {
|
||||
if (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land)) {
|
||||
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);
|
||||
la.setCardState(land.getCurrentState());
|
||||
if (la.canPlay()) {
|
||||
@@ -1704,10 +1724,8 @@ public class AiController {
|
||||
for (int i = 0; i < numToExile; i++) {
|
||||
Card chosen = null;
|
||||
for (final Card c : grave) { // Exile noncreatures first in
|
||||
// case we can revive. Might
|
||||
// wanna do some additional
|
||||
// checking here for Flashback
|
||||
// and the like.
|
||||
// case we can revive. Might wanna do some additional
|
||||
// checking here for Flashback and the like.
|
||||
if (!c.isCreature()) {
|
||||
chosen = c;
|
||||
break;
|
||||
@@ -1746,12 +1764,21 @@ public class AiController {
|
||||
* @param sa the sa
|
||||
* @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();
|
||||
if (hostCard.hasAlternateState()) {
|
||||
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")) {
|
||||
System.out.println("aiShouldRun?" + sa);
|
||||
final String svarToCheck = effect.getParam("AICheckSVar");
|
||||
@@ -1766,7 +1793,7 @@ public class AiController {
|
||||
compareTo = Integer.parseInt(strCmpTo);
|
||||
} catch (final Exception ignored) {
|
||||
if (sa == null) {
|
||||
compareTo = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(strCmpTo));
|
||||
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), effect);
|
||||
} else {
|
||||
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
|
||||
}
|
||||
@@ -1776,7 +1803,7 @@ public class AiController {
|
||||
int left = 0;
|
||||
|
||||
if (sa == null) {
|
||||
left = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(svarToCheck));
|
||||
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, effect);
|
||||
} else {
|
||||
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
||||
}
|
||||
@@ -1866,11 +1893,13 @@ public class AiController {
|
||||
} else if ("LowestLoseLife".equals(logic)) {
|
||||
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
||||
} 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)) {
|
||||
return MyRandom.getRandom().nextInt(3);
|
||||
} else if (source.hasSVar("EnergyToPay")) {
|
||||
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;
|
||||
}
|
||||
@@ -1965,7 +1994,6 @@ public class AiController {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// this is where the computer cheats
|
||||
// changes AllZone.getComputerPlayer().getZone(Zone.Library)
|
||||
|
||||
@@ -2245,10 +2273,8 @@ public class AiController {
|
||||
}
|
||||
}
|
||||
|
||||
// AI logic for choosing which replacement effect to apply
|
||||
// happens here.
|
||||
// AI logic for choosing which replacement effect to apply happens here.
|
||||
return Iterables.getFirst(list, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -280,7 +280,7 @@ public class ComputerUtil {
|
||||
SpellAbility newSA = sa.copyWithNoManaCost();
|
||||
newSA.setActivatingPlayer(ai);
|
||||
|
||||
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) {
|
||||
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA) || !ComputerUtilMana.canPayManaCost(newSA, ai, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1125,7 +1125,7 @@ public class ComputerUtil {
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
@@ -2253,10 +2253,8 @@ public class ComputerUtil {
|
||||
String chosen = "";
|
||||
if (kindOfType.equals("Card")) {
|
||||
// TODO
|
||||
// computer will need to choose a type
|
||||
// based on whether it needs a creature or land,
|
||||
// otherwise, lib search for most common type left
|
||||
// then, reveal chosenType to Human
|
||||
// computer will need to choose a type based on whether it needs a creature or land,
|
||||
// otherwise, lib search for most common type left then, reveal chosenType to Human
|
||||
if (game.getPhaseHandler().is(PhaseType.UNTAP) && logic == null) { // Storage Matrix
|
||||
double amount = 0;
|
||||
for (String type : CardType.getAllCardTypes()) {
|
||||
@@ -2434,8 +2432,7 @@ public class ComputerUtil {
|
||||
if (!source.canReceiveCounters(p1p1Type)) {
|
||||
return opponent ? "Feather" : "Quill";
|
||||
}
|
||||
// if source is not on the battlefield anymore, choose +1/+1
|
||||
// ones
|
||||
// if source is not on the battlefield anymore, choose +1/+1 ones
|
||||
if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
|
||||
return opponent ? "Feather" : "Quill";
|
||||
}
|
||||
@@ -2477,8 +2474,7 @@ public class ComputerUtil {
|
||||
return opponent ? "Numbers" : "Strength";
|
||||
}
|
||||
|
||||
// TODO check for ETB to +1/+1 counters
|
||||
// or over another trigger like lifegain
|
||||
// TODO check for ETB to +1/+1 counters or over another trigger like lifegain
|
||||
|
||||
int tokenScore = ComputerUtilCard.evaluateCreature(token);
|
||||
|
||||
@@ -2556,8 +2552,7 @@ public class ComputerUtil {
|
||||
return "Taxes";
|
||||
} else {
|
||||
// ai is first voter or ally of controller
|
||||
// both are not affected, but if opponents controll creatures,
|
||||
// sacrifice is worse
|
||||
// both are not affected, but if opponents control creatures, sacrifice is worse
|
||||
return controller.getOpponents().getCreaturesInPlay().isEmpty() ? "Taxes" : "Death";
|
||||
}
|
||||
default:
|
||||
@@ -2854,7 +2849,6 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
public static boolean lifegainNegative(final Player player, final Card source, final int n) {
|
||||
|
||||
if (!player.canGainLife()) {
|
||||
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.
|
||||
*/
|
||||
public static Card getBestAI(final Iterable<Card> list) {
|
||||
// Get Best will filter by appropriate getBest list if ALL of the list
|
||||
// is of that type
|
||||
// Get Best will filter by appropriate getBest list if ALL of the list is of that type
|
||||
if (Iterables.all(list, CardPredicates.Presets.CREATURES)) {
|
||||
return ComputerUtilCard.getBestCreatureAI(list);
|
||||
}
|
||||
if (Iterables.all(list, CardPredicates.Presets.LANDS)) {
|
||||
return getBestLandAI(list);
|
||||
}
|
||||
// TODO - Once we get an EvaluatePermanent this should call
|
||||
// getBestPermanent()
|
||||
// TODO - Once we get an EvaluatePermanent this should call getBestPermanent()
|
||||
return ComputerUtilCard.getMostExpensivePermanentAI(list);
|
||||
}
|
||||
|
||||
@@ -383,7 +381,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -393,7 +391,7 @@ public class ComputerUtilCard {
|
||||
|
||||
List<Card> lands = CardLists.filter(list, CardPredicates.Presets.LANDS);
|
||||
if (lands.size() > 6) {
|
||||
return ComputerUtilCard.getWorstLand(lands);
|
||||
return getWorstLand(lands);
|
||||
}
|
||||
|
||||
if (hasEnchantmants || hasArtifacts) {
|
||||
@@ -410,8 +408,7 @@ public class ComputerUtilCard {
|
||||
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
|
||||
}
|
||||
|
||||
// Planeswalkers fall through to here, lands will fall through if there
|
||||
// aren't very many
|
||||
// Planeswalkers fall through to here, lands will fall through if there aren't very many
|
||||
return getCheapestPermanentAI(list, null, false);
|
||||
}
|
||||
|
||||
@@ -444,7 +441,7 @@ public class ComputerUtilCard {
|
||||
public static final Comparator<Card> EvaluateCreatureComparator = new Comparator<Card>() {
|
||||
@Override
|
||||
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>() {
|
||||
@Override
|
||||
public boolean apply(Deck d) {
|
||||
for(Entry<DeckSection, CardPool> cp: d) {
|
||||
for(Entry<PaperCard, Integer> e : cp.getValue()) {
|
||||
if ( e.getKey().getRules().getAiHints().getRemAIDecks() )
|
||||
for (Entry<DeckSection, CardPool> cp: d) {
|
||||
for (Entry<PaperCard, Integer> e : cp.getValue()) {
|
||||
if (e.getKey().getRules().getAiHints().getRemAIDecks())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -982,7 +979,7 @@ public class ComputerUtilCard {
|
||||
for(byte c : MagicColor.WUBRG) {
|
||||
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()) {
|
||||
curDevotion = devotion;
|
||||
chosenColor = MagicColor.toLongString(c);
|
||||
@@ -1198,7 +1195,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
String kws = params.get("AddKeyword");
|
||||
if (kws != null) {
|
||||
bonusPT += 4 * (1 + StringUtils.countMatches(kws, "&")); //treat each added keyword as a +2/+2 for now
|
||||
bonusPT += 4 * (1 + StringUtils.countMatches(kws, "&")); //treat each added keyword as a +2/+2 for now
|
||||
}
|
||||
if (bonusPT > 0) {
|
||||
threat = bonusPT * (1 + opp.getCreaturesInPlay().size()) / 10.0f;
|
||||
@@ -1212,7 +1209,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
|
||||
final float valueNow = Math.max(valueTempo, threat);
|
||||
if (valueNow < 0.2) { //hard floor to reduce ridiculous odds for instants over time
|
||||
if (valueNow < 0.2) { //hard floor to reduce ridiculous odds for instants over time
|
||||
return false;
|
||||
}
|
||||
final float chance = MyRandom.getRandom().nextFloat();
|
||||
@@ -1310,7 +1307,7 @@ public class ComputerUtilCard {
|
||||
threat *= 2;
|
||||
}
|
||||
if (c.getNetPower() == 0 && c == sa.getHostCard() && power > 0 ) {
|
||||
threat *= 4; //over-value self +attack for 0 power creatures which may be pumped further after attacking
|
||||
threat *= 4; //over-value self +attack for 0 power creatures which may be pumped further after attacking
|
||||
}
|
||||
chance += threat;
|
||||
|
||||
@@ -1644,11 +1641,12 @@ public class ComputerUtilCard {
|
||||
copiedKeywords.insertAll(pumped.getKeywords());
|
||||
List<KeywordInterface> toCopy = Lists.newArrayList();
|
||||
for (KeywordInterface k : c.getKeywords()) {
|
||||
if (!copiedKeywords.contains(k.getOriginal())) {
|
||||
if (k.getHidden()) {
|
||||
pumped.addHiddenExtrinsicKeyword(k);
|
||||
KeywordInterface copiedKI = k.copy(c, true);
|
||||
if (!copiedKeywords.contains(copiedKI.getOriginal())) {
|
||||
if (copiedKI.getHidden()) {
|
||||
pumped.addHiddenExtrinsicKeyword(copiedKI);
|
||||
} else {
|
||||
toCopy.add(k);
|
||||
toCopy.add(copiedKI);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1678,37 +1676,27 @@ public class ComputerUtilCard {
|
||||
// remove old boost that might be copied
|
||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||
vCard.removePTBoost(c.getTimestamp(), stAb.getId());
|
||||
final Map<String, String> params = stAb.getMapParams();
|
||||
if (!params.get("Mode").equals("Continuous")) {
|
||||
if (!stAb.getParam("Mode").equals("Continuous")) {
|
||||
continue;
|
||||
}
|
||||
if (!params.containsKey("Affected")) {
|
||||
if (!stAb.hasParam("Affected")) {
|
||||
continue;
|
||||
}
|
||||
if (!params.containsKey("AddPower") && !params.containsKey("AddToughness")) {
|
||||
if (!stAb.hasParam("AddPower") && !stAb.hasParam("AddToughness")) {
|
||||
continue;
|
||||
}
|
||||
final String valid = params.get("Affected");
|
||||
if (!vCard.isValid(valid, c.getController(), c, null)) {
|
||||
if (!vCard.isValid(stAb.getParam("Affected").split(","), c.getController(), c, stAb)) {
|
||||
continue;
|
||||
}
|
||||
int att = 0;
|
||||
if (params.containsKey("AddPower")) {
|
||||
String addP = params.get("AddPower");
|
||||
if (addP.equals("AffectedX")) {
|
||||
att = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addP));
|
||||
} else {
|
||||
att = AbilityUtils.calculateAmount(c, addP, stAb);
|
||||
}
|
||||
if (stAb.hasParam("AddPower")) {
|
||||
String addP = stAb.getParam("AddPower");
|
||||
att = AbilityUtils.calculateAmount(addP.startsWith("Affected") ? vCard : c, addP, stAb, true);
|
||||
}
|
||||
int def = 0;
|
||||
if (params.containsKey("AddToughness")) {
|
||||
String addT = params.get("AddToughness");
|
||||
if (addT.equals("AffectedY")) {
|
||||
def = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addT));
|
||||
} else {
|
||||
def = AbilityUtils.calculateAmount(c, addT, stAb);
|
||||
}
|
||||
if (stAb.hasParam("AddToughness")) {
|
||||
String addT = stAb.getParam("AddToughness");
|
||||
def = AbilityUtils.calculateAmount(addT.startsWith("Affected") ? vCard : c, addT, stAb, true);
|
||||
}
|
||||
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.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardUtil;
|
||||
@@ -335,7 +334,7 @@ public class ComputerUtilCombat {
|
||||
*/
|
||||
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)) {
|
||||
return ai.getPoisonCounters();
|
||||
}
|
||||
@@ -413,7 +412,6 @@ public class ComputerUtilCombat {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// check for creatures that must be blocked
|
||||
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) {
|
||||
// life in danger only cares about the player's life. Not about a
|
||||
// Planeswalkers life
|
||||
// life in danger only cares about the player's life. Not about a Planeswalkers life
|
||||
if (ai.cantLose() || combat == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -569,8 +566,7 @@ public class ComputerUtilCombat {
|
||||
return damage;
|
||||
}
|
||||
|
||||
// This calculates the amount of damage a blocker in a blockgang can deal to
|
||||
// the attacker
|
||||
// This calculates the amount of damage a blocker in a blockgang can deal to the attacker
|
||||
/**
|
||||
* <p>
|
||||
* dealsDamageAsBlocker.
|
||||
@@ -583,7 +579,6 @@ public class ComputerUtilCombat {
|
||||
* @return a int.
|
||||
*/
|
||||
public static int dealsDamageAsBlocker(final Card attacker, final Card defender) {
|
||||
|
||||
int defenderDamage = predictDamageByBlockerWithoutDoubleStrike(attacker, defender);
|
||||
|
||||
if (defender.hasKeyword(Keyword.DOUBLE_STRIKE)) {
|
||||
@@ -648,7 +643,6 @@ public class ComputerUtilCombat {
|
||||
* @return a int.
|
||||
*/
|
||||
public static int totalShieldDamage(final Card attacker, final List<Card> defenders) {
|
||||
|
||||
int defenderDefense = 0;
|
||||
|
||||
for (final Card defender : defenders) {
|
||||
@@ -672,7 +666,6 @@ public class ComputerUtilCombat {
|
||||
* @return a int.
|
||||
*/
|
||||
public static int shieldDamage(final Card attacker, final Card blocker) {
|
||||
|
||||
if (ComputerUtilCombat.canDestroyBlockerBeforeFirstStrike(blocker, attacker, false)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -777,7 +770,6 @@ public class ComputerUtilCombat {
|
||||
public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger,
|
||||
Combat combat, final List<Card> plannedAttackers) {
|
||||
final Game game = attacker.getGame();
|
||||
final Map<String, String> trigParams = trigger.getMapParams();
|
||||
boolean willTrigger = false;
|
||||
final Card source = trigger.getHostCard();
|
||||
if (combat == null) {
|
||||
@@ -800,29 +792,27 @@ public class ComputerUtilCombat {
|
||||
if (combat.isAttacking(attacker)) {
|
||||
return false; // The trigger should have triggered already
|
||||
}
|
||||
if (trigParams.containsKey("ValidCard")) {
|
||||
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))
|
||||
&& !(combat.isAttacking(source) && trigger.matchesValid(source,
|
||||
trigParams.get("ValidCard").split(","))
|
||||
&& !trigParams.containsKey("Alone"))) {
|
||||
if (trigger.hasParam("ValidCard")) {
|
||||
if (!trigger.matchesValidParam("ValidCard", attacker)
|
||||
&& !(combat.isAttacking(source) && trigger.matchesValidParam("ValidCard", source)
|
||||
&& !trigger.hasParam("Alone"))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (trigParams.containsKey("Attacked")) {
|
||||
if (combat.isAttacking(attacker)) {
|
||||
GameEntity attacked = combat.getDefenderByAttacker(attacker);
|
||||
if (!trigger.matchesValid(attacked, trigParams.get("Attacked").split(","))) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if ("You,Planeswalker.YouCtrl".equals(trigParams.get("Attacked"))) {
|
||||
if (source.getController() == attacker.getController()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (trigger.hasParam("Attacked")) {
|
||||
if (combat.isAttacking(attacker)) {
|
||||
if (!trigger.matchesValidParam("Attacked", combat.getDefenderByAttacker(attacker))) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if ("You,Planeswalker.YouCtrl".equals(trigger.getParam("Attacked"))) {
|
||||
if (source.getController() == attacker.getController()) {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -830,10 +820,8 @@ public class ComputerUtilCombat {
|
||||
// defender == null means unblocked
|
||||
if ((defender == null) && mode == TriggerType.AttackerUnblocked) {
|
||||
willTrigger = true;
|
||||
if (trigParams.containsKey("ValidCard")) {
|
||||
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))) {
|
||||
return false;
|
||||
}
|
||||
if (!trigger.matchesValidParam("ValidCard", attacker)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -843,8 +831,8 @@ public class ComputerUtilCombat {
|
||||
|
||||
if (mode == TriggerType.Blocks) {
|
||||
willTrigger = true;
|
||||
if (trigParams.containsKey("ValidBlocked")) {
|
||||
String validBlocked = trigParams.get("ValidBlocked");
|
||||
if (trigger.hasParam("ValidBlocked")) {
|
||||
String validBlocked = trigger.getParam("ValidBlocked");
|
||||
if (validBlocked.contains(".withLesserPower")) {
|
||||
// Have to check this restriction here as triggering objects aren't set yet, so
|
||||
// ValidBlocked$Creature.powerLTX where X:TriggeredBlocker$CardPower crashes with NPE
|
||||
@@ -857,8 +845,8 @@ public class ComputerUtilCombat {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (trigParams.containsKey("ValidCard")) {
|
||||
String validBlocker = trigParams.get("ValidCard");
|
||||
if (trigger.hasParam("ValidCard")) {
|
||||
String validBlocker = trigger.getParam("ValidCard");
|
||||
if (validBlocker.contains(".withLesserPower")) {
|
||||
// Have to check this restriction here as triggering objects aren't set yet, so
|
||||
// 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) {
|
||||
willTrigger = true;
|
||||
if (trigParams.containsKey("ValidBlocker")) {
|
||||
if (!trigger.matchesValid(defender, trigParams.get("ValidBlocker").split(","))) {
|
||||
return false;
|
||||
}
|
||||
if (!trigger.matchesValidParam("ValidBlocker", defender)) {
|
||||
return false;
|
||||
}
|
||||
if (trigParams.containsKey("ValidCard")) {
|
||||
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))) {
|
||||
return false;
|
||||
}
|
||||
if (!trigger.matchesValidParam("ValidCard", attacker)) {
|
||||
return false;
|
||||
}
|
||||
} else if (mode == TriggerType.DamageDone) {
|
||||
willTrigger = true;
|
||||
if (trigParams.containsKey("ValidSource")) {
|
||||
if (!(trigger.matchesValid(defender, trigParams.get("ValidSource").split(","))
|
||||
if (trigger.hasParam("ValidSource")) {
|
||||
if (!(trigger.matchesValidParam("ValidSource", defender)
|
||||
&& defender.getNetCombatDamage() > 0
|
||||
&& (!trigParams.containsKey("ValidTarget")
|
||||
|| trigger.matchesValid(attacker, trigParams.get("ValidTarget").split(","))))) {
|
||||
&& trigger.matchesValidParam("ValidTarget", attacker))) {
|
||||
return false;
|
||||
}
|
||||
if (!(trigger.matchesValid(attacker, trigParams.get("ValidSource").split(","))
|
||||
if (!(trigger.matchesValidParam("ValidSource", attacker)
|
||||
&& attacker.getNetCombatDamage() > 0
|
||||
&& (!trigParams.containsKey("ValidTarget")
|
||||
|| trigger.matchesValid(defender, trigParams.get("ValidTarget").split(","))))) {
|
||||
&& trigger.matchesValidParam("ValidTarget", defender))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -944,30 +926,22 @@ public class ComputerUtilCombat {
|
||||
}
|
||||
|
||||
final Game game = attacker.getGame();
|
||||
// look out for continuous static abilities that only care for blocking
|
||||
// creatures
|
||||
// look out for continuous static abilities that only care for blocking creatures
|
||||
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
||||
for (final Card card : cardList) {
|
||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||
final Map<String, String> params = stAb.getMapParams();
|
||||
if (!params.get("Mode").equals("Continuous")) {
|
||||
if (!stAb.getParam("Mode").equals("Continuous")) {
|
||||
continue;
|
||||
}
|
||||
if (!params.containsKey("Affected") || !params.get("Affected").contains("blocking")) {
|
||||
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("blocking")) {
|
||||
continue;
|
||||
}
|
||||
final String valid = TextUtil.fastReplace(params.get("Affected"), "blocking", "Creature");
|
||||
if (!blocker.isValid(valid, card.getController(), card, null)) {
|
||||
final String valid = TextUtil.fastReplace(stAb.getParam("Affected"), "blocking", "Creature");
|
||||
if (!blocker.isValid(valid, card.getController(), card, stAb)) {
|
||||
continue;
|
||||
}
|
||||
if (params.containsKey("AddPower")) {
|
||||
if (params.get("AddPower").equals("X")) {
|
||||
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"));
|
||||
}
|
||||
if (stAb.hasParam("AddPower")) {
|
||||
power += AbilityUtils.calculateAmount(card, stAb.getParam("AddPower"), stAb);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1252,31 +1226,23 @@ public class ComputerUtilCombat {
|
||||
theTriggers.addAll(blocker.getTriggers());
|
||||
}
|
||||
|
||||
// look out for continuous static abilities that only care for attacking
|
||||
// creatures
|
||||
// look out for continuous static abilities that only care for attacking creatures
|
||||
if (!withoutCombatStaticAbilities) {
|
||||
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
||||
for (final Card card : cardList) {
|
||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||
final Map<String, String> params = stAb.getMapParams();
|
||||
if (!params.get("Mode").equals("Continuous")) {
|
||||
if (!stAb.getParam("Mode").equals("Continuous")) {
|
||||
continue;
|
||||
}
|
||||
if (!params.containsKey("Affected") || !params.get("Affected").contains("attacking")) {
|
||||
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("attacking")) {
|
||||
continue;
|
||||
}
|
||||
final String valid = TextUtil.fastReplace(params.get("Affected"), "attacking", "Creature");
|
||||
if (!attacker.isValid(valid, card.getController(), card, null)) {
|
||||
final String valid = TextUtil.fastReplace(stAb.getParam("Affected"), "attacking", "Creature");
|
||||
if (!attacker.isValid(valid, card.getController(), card, stAb)) {
|
||||
continue;
|
||||
}
|
||||
if (params.containsKey("AddPower")) {
|
||||
if (params.get("AddPower").equals("X")) {
|
||||
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"));
|
||||
}
|
||||
if (stAb.hasParam("AddPower")) {
|
||||
power += AbilityUtils.calculateAmount(card, stAb.getParam("AddPower"), stAb);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1350,7 +1316,7 @@ public class ComputerUtilCombat {
|
||||
} else if (bonus.contains("TriggeredAttacker$CardToughness")) {
|
||||
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());
|
||||
}
|
||||
|
||||
// look out for continuous static abilities that only care for attacking
|
||||
// creatures
|
||||
// look out for continuous static abilities that only care for attacking creatures
|
||||
if (!withoutCombatStaticAbilities) {
|
||||
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
|
||||
for (final Card card : cardList) {
|
||||
@@ -1540,7 +1505,7 @@ public class ComputerUtilCombat {
|
||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||
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())) {
|
||||
|
||||
@@ -1573,7 +1538,7 @@ public class ComputerUtilCombat {
|
||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
||||
}
|
||||
toughness += CardFactoryUtil.xCount(source, bonus);
|
||||
toughness += AbilityUtils.calculateAmount(source, bonus, sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1638,8 +1603,8 @@ public class ComputerUtilCombat {
|
||||
|
||||
//Check triggers that deal damage or shrink the attacker
|
||||
if (ComputerUtilCombat.getDamageToKill(attacker)
|
||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities) <= 0) {
|
||||
return true;
|
||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities) <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check Destroy triggers (Cockatrice and friends)
|
||||
@@ -2256,11 +2221,12 @@ public class ComputerUtilCombat {
|
||||
* @return a int.
|
||||
*/
|
||||
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")) {
|
||||
killDamage = 1 + c.getPreventNextDamageTotalShields();
|
||||
killDamage = 1 + damageShield;
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
final Game game = target.getGame();
|
||||
int restDamage = damage;
|
||||
|
||||
@@ -2291,39 +2256,38 @@ public class ComputerUtilCombat {
|
||||
// Predict replacement effects
|
||||
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||
for (final ReplacementEffect re : ca.getReplacementEffects()) {
|
||||
Map<String, String> params = re.getMapParams();
|
||||
if (!re.getMode().equals(ReplacementType.DamageDone) || !params.containsKey("PreventionEffect")) {
|
||||
if (!re.getMode().equals(ReplacementType.DamageDone) ||
|
||||
(!re.hasParam("PreventionEffect") && !re.hasParam("Prevent"))) {
|
||||
continue;
|
||||
}
|
||||
// Immortal Coil prevents the damage but has a similar negative effect
|
||||
if ("Immortal Coil".equals(ca.getName())) {
|
||||
continue;
|
||||
}
|
||||
if (params.containsKey("ValidSource")
|
||||
&& !source.isValid(params.get("ValidSource"), ca.getController(), ca, null)) {
|
||||
if (!re.matchesValidParam("ValidSource", source)) {
|
||||
continue;
|
||||
}
|
||||
if (params.containsKey("ValidTarget")
|
||||
&& !target.isValid(params.get("ValidTarget"), ca.getController(), ca, null)) {
|
||||
if (!re.matchesValidParam("ValidTarget", source)) {
|
||||
continue;
|
||||
}
|
||||
if (params.containsKey("IsCombat")) {
|
||||
if (params.get("IsCombat").equals("True")) {
|
||||
if (!isCombat) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (isCombat) {
|
||||
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, restDamage - AbilityUtils.calculateAmount(ca, repSA.getParam("Amount"), repSA));
|
||||
}
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
restDamage = target.staticDamagePrevention(restDamage, source, isCombat, true);
|
||||
restDamage = target.staticDamagePrevention(restDamage, 0, source, isCombat);
|
||||
|
||||
return restDamage;
|
||||
}
|
||||
@@ -2344,13 +2308,7 @@ public class ComputerUtilCombat {
|
||||
// This function helps the AI calculate the actual amount of damage an
|
||||
// effect would deal
|
||||
public final static int predictDamageTo(final Card target, final int damage, final Card source, final boolean isCombat) {
|
||||
|
||||
int restDamage = damage;
|
||||
|
||||
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
|
||||
restDamage = target.staticDamagePrevention(restDamage, source, isCombat, true);
|
||||
|
||||
return restDamage;
|
||||
return predictDamageTo(target, damage, 0, source, isCombat);
|
||||
}
|
||||
|
||||
|
||||
@@ -2372,7 +2330,6 @@ public class ComputerUtilCombat {
|
||||
* @return a int.
|
||||
*/
|
||||
public final static int predictDamageTo(final Card target, final int damage, final int possiblePrevention, final Card source, final boolean isCombat) {
|
||||
|
||||
int restDamage = damage;
|
||||
|
||||
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) {
|
||||
|
||||
if (combatant.hasKeyword(Keyword.DOUBLE_STRIKE) || combatant.hasKeyword(Keyword.FIRST_STRIKE)) {
|
||||
return true;
|
||||
}
|
||||
@@ -2490,7 +2446,14 @@ public class ComputerUtilCombat {
|
||||
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(
|
||||
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) {
|
||||
@@ -2499,20 +2462,6 @@ public class ComputerUtilCombat {
|
||||
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) {
|
||||
List<Card> categorizedAttackers = Lists.newArrayList();
|
||||
|
||||
@@ -2602,5 +2551,3 @@ public class ComputerUtilCombat {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.ai;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import forge.game.ability.ApiType;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -156,7 +157,7 @@ public class ComputerUtilCost {
|
||||
if (typeList.size() > ai.getMaxHandSize()) {
|
||||
continue;
|
||||
}
|
||||
int num = AbilityUtils.calculateAmount(source, disc.getAmount(), null);
|
||||
int num = AbilityUtils.calculateAmount(source, disc.getAmount(), sa);
|
||||
|
||||
for (int i = 0; i < num; i++) {
|
||||
Card pref = ComputerUtil.getCardPreference(ai, source, "DiscardCost", typeList);
|
||||
@@ -557,7 +558,6 @@ public class ComputerUtilCost {
|
||||
boolean payForOwnOnly = "OnlyOwn".equals(aiLogic);
|
||||
boolean payOwner = sa.hasParam("UnlessAI") && aiLogic.startsWith("Defined");
|
||||
boolean payNever = "Never".equals(aiLogic);
|
||||
boolean shockland = "Shockland".equals(aiLogic);
|
||||
boolean isMine = sa.getActivatingPlayer().equals(payer);
|
||||
|
||||
if (payNever) { return false; }
|
||||
@@ -572,26 +572,6 @@ public class ComputerUtilCost {
|
||||
if (sa.getHostCard() == null || payer.equals(sa.getHostCard().getController())) {
|
||||
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)) {
|
||||
final Card c = source.getEnchantingCard();
|
||||
if (c == null || c.isUntapped()) {
|
||||
@@ -629,6 +609,29 @@ public class ComputerUtilCost {
|
||||
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
|
||||
if (alreadyPaid || (payers.size() > 1 && (isMine && !payForOwnOnly))) {
|
||||
return false;
|
||||
|
||||
@@ -609,7 +609,6 @@ public class ComputerUtilMana {
|
||||
|
||||
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
||||
|
||||
//System.out.println(manaProduced);
|
||||
payMultipleMana(cost, manaProduced, ai);
|
||||
|
||||
// remove from available lists
|
||||
|
||||
@@ -162,9 +162,9 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
value -= subValue(10, "must-attack");
|
||||
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
||||
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");
|
||||
}
|
||||
}//*/
|
||||
|
||||
if (c.hasSVar("DestroyWhenDamaged")) {
|
||||
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
|
||||
|
||||
@@ -1215,8 +1215,7 @@ public abstract class GameState {
|
||||
boolean tapped = c.isTapped();
|
||||
boolean sickness = c.hasSickness();
|
||||
Map<CounterType, Integer> counters = c.getCounters();
|
||||
// Note: Not clearCounters() since we want to keep the counters
|
||||
// var as-is.
|
||||
// Note: Not clearCounters() since we want to keep the counters var as-is.
|
||||
c.setCounters(Maps.newHashMap());
|
||||
if (c.isAura()) {
|
||||
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
|
||||
|
||||
@@ -81,9 +81,9 @@ import forge.util.collect.FCollection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* A prototype for player controller class
|
||||
*
|
||||
*
|
||||
* Handles phase skips for now.
|
||||
*/
|
||||
public class PlayerControllerAi extends PlayerController {
|
||||
@@ -94,11 +94,11 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
brains = new AiController(p, game);
|
||||
}
|
||||
|
||||
|
||||
public void allowCheatShuffle(boolean value){
|
||||
brains.allowCheatShuffle(value);
|
||||
}
|
||||
|
||||
|
||||
public void setUseSimulation(boolean value) {
|
||||
brains.setUseSimulation(value);
|
||||
}
|
||||
@@ -131,6 +131,12 @@ public class PlayerControllerAi extends PlayerController {
|
||||
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
|
||||
public Integer announceRequirements(SpellAbility ability, String announce) {
|
||||
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
|
||||
@@ -157,7 +163,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
case BidLife:
|
||||
return 0;
|
||||
default:
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null; // return incorrect value to indicate that
|
||||
@@ -240,7 +246,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return getAi().confirmAction(sa, mode, message);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean confirmBidAction(SpellAbility sa, PlayerActionConfirmMode mode, String string,
|
||||
int bid, Player winner) {
|
||||
@@ -568,8 +574,8 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
|
||||
return brains.aiShouldRun(replacementEffect, effectSA);
|
||||
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question) {
|
||||
return brains.aiShouldRun(replacementEffect, effectSA, affected);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -646,10 +652,9 @@ public class PlayerControllerAi extends PlayerController {
|
||||
public List<SpellAbility> chooseSpellAbilityToPlay() {
|
||||
return brains.chooseSpellAbilityToPlay();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean playChosenSpellAbility(SpellAbility sa) {
|
||||
// System.out.println("Playing sa: " + sa);
|
||||
if (sa instanceof LandAbility) {
|
||||
if (sa.canPlay()) {
|
||||
sa.resolve();
|
||||
@@ -659,7 +664,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
ComputerUtil.handlePlayingSpellAbility(player, sa, getGame());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard) {
|
||||
@@ -712,7 +717,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
public int chooseNumber(SpellAbility sa, String title, int min, int max) {
|
||||
return brains.chooseNumber(sa, title, min, max);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int chooseNumber(SpellAbility sa, String string, int min, int max, Map<String, Object> params) {
|
||||
ApiType api = sa.getApi();
|
||||
@@ -788,7 +793,8 @@ public class PlayerControllerAi extends PlayerController {
|
||||
case "BetterTgtThanRemembered":
|
||||
if (source.getRememberedCount() > 0) {
|
||||
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;
|
||||
}
|
||||
for (Card c : source.getController().getCreaturesInPlay()) {
|
||||
@@ -814,7 +820,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
*
|
||||
* @see
|
||||
* forge.game.player.PlayerController#chooseBinary(forge.game.spellability.
|
||||
* SpellAbility, java.lang.String,
|
||||
@@ -855,7 +861,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
return sa.getChosenList();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) {
|
||||
final String c = ComputerUtilCard.getMostProminentColor(player.getCardsIn(ZoneType.Hand));
|
||||
@@ -901,7 +907,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
*
|
||||
* @see forge.game.player.PlayerController#chooseCounterType(java.util.List,
|
||||
* forge.game.spellability.SpellAbility, java.lang.String, java.util.Map)
|
||||
*/
|
||||
@@ -917,7 +923,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
@Override
|
||||
public boolean confirmPayment(CostPart costPart, String prompt, SpellAbility sa) {
|
||||
return brains.confirmPayment(costPart); // AI is expected to know what it is paying for at the moment (otherwise add another parameter to this method)
|
||||
return brains.confirmPayment(costPart); // AI is expected to know what it is paying for at the moment (otherwise add another parameter to this method)
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -998,6 +1004,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
emptyAbility.setActivatingPlayer(player);
|
||||
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
||||
emptyAbility.setSVars(sa.getSVars());
|
||||
emptyAbility.setXManaCostPaid(sa.getRootAbility().getXManaCostPaid());
|
||||
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
|
||||
return true;
|
||||
@@ -1104,7 +1111,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
public void revealAnte(String message, Multimap<Player, PaperCard> removedAnteCards) {
|
||||
// Ai won't understand that anyway
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<? extends PaperCard> complainCardsCantPlayWell(Deck myDeck) {
|
||||
return brains.complainCardsCantPlayWell(myDeck);
|
||||
@@ -1152,7 +1159,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Do not convoke potential blockers until after opponent's attack
|
||||
final CardCollectionView blockers = ComputerUtilCard.getLikelyBlockers(ai, null);
|
||||
if ((ph.isPlayerTurn(ai) && ph.getPhase().isAfter(PhaseType.COMBAT_BEGIN)) ||
|
||||
|
||||
@@ -39,7 +39,6 @@ import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardUtil;
|
||||
@@ -379,7 +378,7 @@ public class SpecialCardAi {
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -1085,7 +1084,7 @@ public class SpecialCardAi {
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
|
||||
// do not use this SA if devotion to most prominent color is less than its own activation cost + 1 (to actually get advantage)
|
||||
|
||||
@@ -162,7 +162,7 @@ public abstract class SpellAbilityAi {
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false; // prevent infinite loop
|
||||
}
|
||||
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
||||
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
||||
}
|
||||
|
||||
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
@@ -193,7 +193,7 @@ public abstract class SpellAbilityAi {
|
||||
* Handles the AI decision to play a triggered SpellAbility
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.Learn, LearnAi.class)
|
||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||
.put(ApiType.LosesGame, GameLossAi.class)
|
||||
.put(ApiType.MakeCard, AlwaysPlayAi.class)
|
||||
.put(ApiType.Mana, ManaEffectAi.class)
|
||||
.put(ApiType.ManaReflected, CannotPlayAi.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
|
||||
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;
|
||||
}
|
||||
// Don't use instant speed animate abilities outside human's
|
||||
// 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)
|
||||
|| game.getCombat() != null && game.getCombat().getAttackersOf(ai).isEmpty())) {
|
||||
return false;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// Don't animate if the AI won't attack anyway or use as a potential blocker
|
||||
Player opponent = ai.getWeakestOpponent();
|
||||
// Activating as a potential blocker is only viable if it's an ability activated from a permanent, otherwise
|
||||
// the AI will waste resources
|
||||
boolean activateAsPotentialBlocker = sa.hasParam("UntilYourNextTurn")
|
||||
boolean activateAsPotentialBlocker = "UntilYourNextTurn".equals(sa.getParam("Duration"))
|
||||
&& ai.getGame().getPhaseHandler().getNextTurn() != ai
|
||||
&& source.isPermanent();
|
||||
if (ph.isPlayerTurn(ai) && ai.getLife() < 6 && opponent.getLife() > 6
|
||||
&& 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 true;
|
||||
@@ -156,7 +156,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
&& !c.isEquipping();
|
||||
|
||||
// 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;
|
||||
if (sa.hasParam("Power")) {
|
||||
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()) {
|
||||
// Do not try to crew a vehicle which is already a creature
|
||||
return false;
|
||||
@@ -278,7 +278,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
Map<Card, Integer> data = Maps.newHashMap();
|
||||
for (final Card c : list) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -312,9 +312,9 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
// if its player turn,
|
||||
// check if its Permanent or that creature would attack
|
||||
if (ph.isPlayerTurn(ai)) {
|
||||
if (!sa.hasParam("Permanent")
|
||||
if (!"Permanent".equals(sa.getParam("Duration"))
|
||||
&& !ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animatedCopy)
|
||||
&& !sa.hasParam("UntilHostLeavesPlay")) {
|
||||
&& !"UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -360,8 +360,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
|
||||
// two are the only things
|
||||
// that animate a target. Those can just use AI:RemoveDeck:All until
|
||||
// this can do a reasonably
|
||||
// good job of picking a good target
|
||||
// this can do a reasonably good job of picking a good target
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -555,12 +555,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if (!evenBetterList.isEmpty()) {
|
||||
betterList = evenBetterList;
|
||||
}
|
||||
evenBetterList = CardLists.filter(betterList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.isUntapped();
|
||||
}
|
||||
});
|
||||
evenBetterList = CardLists.filter(betterList, CardPredicates.Presets.UNTAPPED);
|
||||
if (!evenBetterList.isEmpty()) {
|
||||
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,
|
||||
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 String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
Card chosen = null;
|
||||
|
||||
if ("Guilty Conscience".equals(sourceName)) {
|
||||
chosen = SpecialCardAi.GuiltyConscience.getBestAttachTarget(ai, sa, list);
|
||||
} else if ("Bonds of Faith".equals(sourceName)) {
|
||||
chosen = doPumpOrCurseAILogic(ai, sa, list, "Human");
|
||||
} else if ("Clutch of Undeath".equals(sourceName)) {
|
||||
chosen = doPumpOrCurseAILogic(ai, sa, list, "Zombie");
|
||||
} else if (sa.hasParam("AIValid")) {
|
||||
// TODO: Make the AI recognize which cards to pump based on the card's abilities alone
|
||||
chosen = doPumpOrCurseAILogic(ai, sa, list, sa.getParam("AIValid"));
|
||||
}
|
||||
|
||||
// If Mandatory (brought directly into play without casting) gotta
|
||||
@@ -768,7 +761,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
int powerBuff = 0;
|
||||
for (StaticAbility stAb : sa.getHostCard().getStaticAbilities()) {
|
||||
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())) {
|
||||
@@ -1646,7 +1639,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
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.")) {
|
||||
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);
|
||||
} else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) {
|
||||
for (SpellAbility ability : card.getSpellAbilities()) {
|
||||
@@ -1699,7 +1692,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if (!c.getController().equals(ai)) {
|
||||
return false;
|
||||
}
|
||||
return c.getType().hasCreatureType(type);
|
||||
return c.isValid(type, ai, sa.getHostCard(), sa);
|
||||
}
|
||||
});
|
||||
List<Card> oppNonType = CardLists.filter(list, new Predicate<Card>() {
|
||||
@@ -1709,7 +1702,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if (c.getController().equals(ai)) {
|
||||
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
|
||||
}
|
||||
}
|
||||
if (isHidden(sa)) {
|
||||
if (sa.isHidden()) {
|
||||
return hiddenOriginCanPlayAI(aiPlayer, sa);
|
||||
}
|
||||
return knownOriginCanPlayAI(aiPlayer, sa);
|
||||
@@ -182,21 +182,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
if (isHidden(sa)) {
|
||||
if (sa.isHidden()) {
|
||||
return hiddenOriginPlayDrawbackAI(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>
|
||||
* changeZoneTriggerAINoCost.
|
||||
@@ -232,7 +223,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return delta <= 0;
|
||||
}
|
||||
|
||||
if (isHidden(sa)) {
|
||||
if (sa.isHidden()) {
|
||||
return hiddenTriggerAI(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);
|
||||
}
|
||||
|
||||
if (isHidden(sa)) {
|
||||
if (sa.isHidden()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1316,8 +1307,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
private static Card canBouncePermanent(final Player ai, SpellAbility sa, CardCollectionView list) {
|
||||
Game game = ai.getGame();
|
||||
// filter out untargetables
|
||||
CardCollectionView aiPermanents = CardLists
|
||||
.filterControlledBy(list, ai);
|
||||
CardCollectionView aiPermanents = CardLists.filterControlledBy(list, ai);
|
||||
CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS);
|
||||
|
||||
// Felidar Guardian + Saheeli Rai combo support
|
||||
@@ -1870,23 +1860,25 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}));
|
||||
}
|
||||
|
||||
// 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>>>() {
|
||||
@Override
|
||||
public int compare(Map.Entry<Player, Map.Entry<String, Integer>> o1, Map.Entry<Player, Map.Entry<String, Integer>> o2) {
|
||||
return o1.getValue().getValue() - o2.getValue().getValue();
|
||||
if (!data.isEmpty()) {
|
||||
// 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>>>() {
|
||||
@Override
|
||||
public int compare(Map.Entry<Player, Map.Entry<String, Integer>> o1, Map.Entry<Player, Map.Entry<String, Integer>> o2) {
|
||||
return o1.getValue().getValue() - o2.getValue().getValue();
|
||||
}
|
||||
});
|
||||
|
||||
// filter list again by the opponent and a creature of the wanted name that can be targeted
|
||||
list = CardLists.filter(CardLists.filterControlledBy(list, max.getKey()),
|
||||
CardPredicates.nameEquals(max.getValue().getKey()), CardPredicates.isTargetableBy(sa));
|
||||
|
||||
// list should contain only one element or zero
|
||||
sa.resetTargets();
|
||||
for (Card c : list) {
|
||||
sa.getTargets().add(c);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// filter list again by the opponent and a creature of the wanted name that can be targeted
|
||||
list = CardLists.filter(CardLists.filterControlledBy(list, max.getKey()),
|
||||
CardPredicates.nameEquals(max.getValue().getKey()), CardPredicates.isTargetableBy(sa));
|
||||
|
||||
// list should contain only one element or zero
|
||||
sa.resetTargets();
|
||||
for (Card c : list) {
|
||||
sa.getTargets().add(c);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1987,8 +1979,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
setPayX = true;
|
||||
// TODO use ComputerUtilCost.getMaxXValue if able
|
||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
toPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
} else {
|
||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ public class CharmAi extends SpellAbilityAi {
|
||||
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
||||
chosenList.add(sub);
|
||||
if (chosenList.size() == min) {
|
||||
break; // enough choices
|
||||
break; // enough choices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
}
|
||||
} else if ("GideonBlackblade".equals(aiLogic)) {
|
||||
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
|
||||
} else if ("SoulEcho".equals(aiLogic)) {
|
||||
return doTriggerAINoCost(ai, sa, true);
|
||||
} else if ("Always".equals(aiLogic)) {
|
||||
return true;
|
||||
}
|
||||
@@ -70,7 +68,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return checkApiLogic(aiPlayer, sa);
|
||||
return sa.isTrigger() ? doTriggerAINoCost(aiPlayer, sa, sa.isMandatory()) : checkApiLogic(aiPlayer, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -56,7 +56,7 @@ public class CloneAi extends SpellAbilityAi {
|
||||
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
|
||||
|
||||
// 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;
|
||||
if (sa.hasParam("Power")) {
|
||||
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
|
||||
// useful
|
||||
if (!bFlag) { // All of the defined stuff is cloned, not very useful
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -246,7 +245,7 @@ public class CloneAi extends SpellAbilityAi {
|
||||
// Combat_Begin step
|
||||
if (!ph.is(PhaseType.COMBAT_BEGIN)
|
||||
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
||||
&& !sa.hasParam("ActivationPhases") && sa.hasParam("Duration")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -257,6 +256,6 @@ public class CloneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// 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 =
|
||||
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
|
||||
// purpose
|
||||
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
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
|
||||
// blocking at least
|
||||
// Don't steal something if I can't Attack without, or prevent it from blocking at least
|
||||
if (lose.contains("EOT")
|
||||
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& !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) {
|
||||
// filter by MustTarget requirement
|
||||
CardCollection originalList = new CardCollection(list);
|
||||
@@ -264,7 +264,6 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -113,8 +113,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
setPayX = true;
|
||||
// TODO use ComputerUtilCost.getMaxXValue
|
||||
toPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), usableManaSources + 1);
|
||||
toPay = Math.min(ComputerUtilCost.getMaxXValue(sa, ai), usableManaSources + 1);
|
||||
} else {
|
||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||
}
|
||||
@@ -124,8 +123,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (toPay <= usableManaSources) {
|
||||
// If this is a reusable Resource, feel free to play it most of
|
||||
// the time
|
||||
// If this is a reusable Resource, feel free to play it most of the time
|
||||
if (!SpellAbilityAi.playReusable(ai,sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -120,8 +120,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
||||
|
||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
// pre filter targetable cards with counters and can receive one of
|
||||
// them
|
||||
// pre filter targetable cards with counters and can receive one of them
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -866,17 +866,16 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
}
|
||||
if (choice != null && divided) {
|
||||
int alloc = Math.max(amount / totalTargets, 1);
|
||||
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
||||
sa.addDividedAllocation(choice, left);
|
||||
} else {
|
||||
sa.addDividedAllocation(choice, alloc);
|
||||
left -= alloc;
|
||||
}
|
||||
}
|
||||
if (choice != null && divided) {
|
||||
int alloc = Math.max(amount / totalTargets, 1);
|
||||
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
||||
sa.addDividedAllocation(choice, left);
|
||||
} else {
|
||||
sa.addDividedAllocation(choice, alloc);
|
||||
left -= alloc;
|
||||
}
|
||||
}
|
||||
|
||||
if (choice != null) {
|
||||
sa.getTargets().add(choice);
|
||||
list.remove(choice);
|
||||
|
||||
@@ -111,8 +111,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
|
||||
|
||||
if ((enemy.getLife() - restDamage) < 5) {
|
||||
// drop the human to less than 5
|
||||
// life
|
||||
// drop the human to less than 5 life
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -147,12 +146,12 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value > 0) { //more likely to burn with larger hand
|
||||
if (value > 0) { //more likely to burn with larger hand
|
||||
for (int i = 3; i < hand.size(); i++) {
|
||||
value *= 1.1f;
|
||||
}
|
||||
}
|
||||
if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time
|
||||
if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time
|
||||
return false;
|
||||
} else {
|
||||
final float chance = MyRandom.getRandom().nextFloat();
|
||||
|
||||
@@ -30,7 +30,6 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
@@ -41,7 +40,6 @@ import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
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.
|
||||
sa.setXManaCostPaid(dmg);
|
||||
} 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")) {
|
||||
// cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact
|
||||
if (sa.getTargetRestrictions().canTgtPlayer()) {
|
||||
@@ -734,12 +732,10 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
|
||||
// When giving priority to targeting Creatures for mandatory
|
||||
// triggers
|
||||
// feel free to add the Human after we run out of good targets
|
||||
// triggers 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
|
||||
// on the stack
|
||||
// or from taking combat damage
|
||||
// on the stack or from taking combat damage
|
||||
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
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
|
||||
// because we can
|
||||
// TODO: Improve Damage, we shouldn't just target the player just because we can
|
||||
if (sa.canTarget(enemy) && tcs.size() < tgt.getMaxTargets(source, sa)) {
|
||||
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
||||
@@ -985,7 +980,6 @@ public class DamageDealAi extends DamageAiBase {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
||||
@@ -1042,7 +1036,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
saTgt = saTgt.getParent();
|
||||
}
|
||||
|
||||
Player opponent = ai.getOpponents().min(PlayerPredicates.compareByLife());
|
||||
Player opponent = ai.getWeakestOpponent();
|
||||
|
||||
// TODO: somehow account for the possible cost reduction?
|
||||
int dmg = ComputerUtilMana.determineLeftoverMana(sa, ai, saTgt.getParam("XColor"));
|
||||
@@ -1085,7 +1079,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
saTgt.resetTargets();
|
||||
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
|
||||
|
||||
sa.setXManaCostPaid(dmg);
|
||||
saTgt.setXManaCostPaid(dmg);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,9 +45,8 @@ public class DigAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
} else {
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
sa.getTargets().add(opp);
|
||||
libraryOwner = opp;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,9 +28,8 @@ public class DigMultipleAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
} else {
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
sa.getTargets().add(opp);
|
||||
libraryOwner = opp;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,9 +60,8 @@ public class DigUntilAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
if (!sa.canTarget(opp)) {
|
||||
return false;
|
||||
} else {
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
sa.getTargets().add(opp);
|
||||
libraryOwner = opp;
|
||||
} else {
|
||||
if (sa.hasParam("Valid")) {
|
||||
|
||||
@@ -257,7 +257,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
} else {
|
||||
numCards = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
// 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
|
||||
numCards = Math.min(numCards, safeDraw);
|
||||
|
||||
@@ -377,7 +377,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
// checks what the ai prevent from casting it on itself
|
||||
// if spell is not mandatory
|
||||
if (aiTarget && !ai.cantLose()) {
|
||||
if (numCards >= computerLibrarySize) {
|
||||
if (numCards >= computerLibrarySize - 3) {
|
||||
if (xPaid) {
|
||||
numCards = computerLibrarySize - 1;
|
||||
if (numCards <= 0 && !mandatory) {
|
||||
@@ -422,8 +422,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
root.setXManaCostPaid(numCards);
|
||||
} else {
|
||||
// Don't draw too many cards and then risk discarding
|
||||
// cards at EOT
|
||||
// Don't draw too many cards and then risk discarding cards at EOT
|
||||
if (!drawback && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
@@ -441,7 +440,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
continue;
|
||||
}
|
||||
|
||||
// use xPaid abilties only for itself
|
||||
// use xPaid abilities only for itself
|
||||
if (xPaid) {
|
||||
continue;
|
||||
}
|
||||
@@ -493,7 +492,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
// TODO: consider if human is the defined player
|
||||
|
||||
// ability is not targeted
|
||||
if (numCards >= computerLibrarySize) {
|
||||
if (numCards >= computerLibrarySize - 3) {
|
||||
if (ai.isCardInPlay("Laboratory Maniac")) {
|
||||
return true;
|
||||
}
|
||||
@@ -509,8 +508,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
&& game.getPhaseHandler().isPlayerTurn(ai)
|
||||
&& !sa.isTrigger()
|
||||
&& !assumeSafeX) {
|
||||
// Don't draw too many cards and then risk discarding cards at
|
||||
// EOT
|
||||
// Don't draw too many cards and then risk discarding cards at EOT
|
||||
if (!drawback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class EffectAi extends SpellAbilityAi {
|
||||
if (!game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
||||
|
||||
@@ -29,7 +29,7 @@ public class FogAi extends SpellAbilityAi {
|
||||
final Card hostCard = sa.getHostCard();
|
||||
|
||||
// Don't cast it, if the effect is already in place
|
||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,7 @@ public class GameLossAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only one SA Lose the Game card right now, which is Door to
|
||||
// Nothingness
|
||||
// Only one SA Lose the Game card right now, which is Door to Nothingness
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
@@ -29,19 +28,23 @@ public class GameLossAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
Player loser = ai;
|
||||
|
||||
// Phage the Untouchable
|
||||
// (Final Fortune would need to attach it's delayed trigger to a
|
||||
// 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;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
sa.getTargets().add(loser);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -266,8 +266,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
||||
}
|
||||
if (!hasTgt && mandatory) {
|
||||
// need to target something but its neither negative against
|
||||
// opponents,
|
||||
// nor posive against allies
|
||||
// opponents, nor positive against allies
|
||||
|
||||
// hurting ally is probably better than healing opponent
|
||||
// look for Lifegain not Negative (case of lifegain negated)
|
||||
@@ -295,8 +294,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
||||
sa.getTargets().add(ally);
|
||||
hasTgt = true;
|
||||
}
|
||||
// better heal opponent which most life then the one with the
|
||||
// lowest
|
||||
// better heal opponent which most life then the one with the lowest
|
||||
if (!hasTgt) {
|
||||
Player opp = opps.max(PlayerPredicates.compareByLife());
|
||||
sa.getTargets().add(opp);
|
||||
|
||||
@@ -28,7 +28,6 @@ public class LifeLoseAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
|
||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
@@ -60,7 +59,6 @@ public class LifeLoseAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
@@ -123,12 +121,12 @@ public class LifeLoseAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||
// TODO: check against the amount we could obtain when multiple activations are possible
|
||||
PlayerCollection filteredPlayer = tgtPlayers
|
||||
.filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount)));
|
||||
// killing opponents asap
|
||||
|
||||
@@ -21,6 +21,7 @@ import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -214,13 +215,7 @@ public class MillAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// get targeted or defined Player with largest library
|
||||
// TODO in Java 8 find better way
|
||||
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();
|
||||
}
|
||||
});
|
||||
final Player m = Collections.max(list, PlayerPredicates.compareByZoneSize(ZoneType.Library));
|
||||
|
||||
int cardsToDiscard = m.getCardsIn(ZoneType.Library).size();
|
||||
|
||||
|
||||
@@ -297,7 +297,7 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
||||
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.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
/**
|
||||
@@ -40,10 +39,8 @@ public class PermanentNoncreatureAi extends PermanentAi {
|
||||
if (host.hasSVar("OblivionRing")) {
|
||||
SpellAbility effectExile = AbilityFactory.getAbility(host.getSVar("TrigExile"), host);
|
||||
final ZoneType origin = ZoneType.listValueOf(effectExile.getParam("Origin")).get(0);
|
||||
final TargetRestrictions tgt = effectExile.getTargetRestrictions();
|
||||
final CardCollection list = CardLists.getValidCards(game.getCardsIn(origin), tgt.getValidTgts(), ai, host,
|
||||
effectExile);
|
||||
CardCollection targets = CardLists.getTargetableCards(list, sa);
|
||||
effectExile.setActivatingPlayer(ai);
|
||||
CardCollection targets = CardLists.getTargetableCards(game.getCardsIn(origin), effectExile);
|
||||
if (sourceName.equals("Suspension Field")
|
||||
|| sourceName.equals("Detention Sphere")) {
|
||||
// existing "exile until leaves" enchantments only target
|
||||
|
||||
@@ -28,7 +28,6 @@ import forge.game.card.CardPredicates;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.Spell;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityPredicates;
|
||||
@@ -158,16 +157,7 @@ public class PlayAi extends SpellAbilityAi {
|
||||
|
||||
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)
|
||||
* @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)) {
|
||||
return true;
|
||||
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
||||
|
||||
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, 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)
|
||||
&& !(sa.isCurse() && defense < 0)
|
||||
&& !containsNonCombatKeyword(keywords)
|
||||
&& !sa.hasParam("UntilYourNextTurn")
|
||||
&& !"UntilYourNextTurn".equals(sa.getParam("Duration"))
|
||||
&& !"Snapcaster".equals(sa.getParam("AILogic"))
|
||||
&& !isFight) {
|
||||
return false;
|
||||
|
||||
@@ -44,7 +44,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean grantsUsefulExtraBlockOpts(final Player ai, final SpellAbility sa, final Card card, List<String> keywords) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
||||
@@ -110,7 +109,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
&& (card.getNetCombatDamage() > 0)
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
|
||||
} 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);
|
||||
}
|
||||
if (!ph.isPlayerTurn(ai)) {
|
||||
@@ -154,14 +153,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
}
|
||||
});
|
||||
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.")) {
|
||||
return !ph.getPhase().isBefore(PhaseType.MAIN2) && !card.isUntapped() && ph.isPlayerTurn(ai)
|
||||
&& Untap.canUntap(card);
|
||||
@@ -480,7 +471,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
}
|
||||
}); // leaves all creatures that will be destroyed
|
||||
} // -X/-X end
|
||||
else if (attack < 0 && !game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||
else if (attack < 0 && !game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||
// spells that give -X/0
|
||||
boolean isMyTurn = game.getPhaseHandler().isPlayerTurn(ai);
|
||||
if (isMyTurn) {
|
||||
@@ -514,7 +505,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
else {
|
||||
final boolean addsKeywords = !keywords.isEmpty();
|
||||
if (addsKeywords) {
|
||||
|
||||
// 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.")
|
||||
|| 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) {
|
||||
for (final String keyword : keywords) {
|
||||
// since most keywords are combat relevant check for those that are
|
||||
// not
|
||||
// since most keywords are combat relevant check for those that are not
|
||||
if (keyword.endsWith("This card doesn't untap during your next untap step.")
|
||||
|| keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) {
|
||||
return true;
|
||||
|
||||
@@ -113,7 +113,7 @@ public class PumpAllAi extends PumpAiBase {
|
||||
if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
|| phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
|| game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())
|
||||
|| game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||
|| game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
int totalPower = 0;
|
||||
@@ -151,6 +151,12 @@ public class PumpAllAi extends PumpAiBase {
|
||||
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) {
|
||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||
for (final Card c : comp) {
|
||||
|
||||
@@ -49,14 +49,6 @@ import forge.game.zone.ZoneType;
|
||||
*/
|
||||
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
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
@@ -65,8 +57,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
||||
boolean chance = false;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt == null) {
|
||||
// As far as I can tell these Defined Cards will only have one of
|
||||
// them
|
||||
// As far as I can tell these Defined Cards will only have one of them
|
||||
final List<Card> list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!game.getStack().isEmpty()) {
|
||||
@@ -105,8 +96,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (!game.getStack().isEmpty()) {
|
||||
// check stack for something on the stack will kill anything i
|
||||
// control
|
||||
// check stack for something on the stack will kill anything i control
|
||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||
|
||||
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
|
||||
// can target
|
||||
// TODO see if something on the stack is about to kill something i can target
|
||||
|
||||
// choose my best X without regen
|
||||
if (CardLists.getNotType(compTargetables, "Creature").isEmpty()) {
|
||||
|
||||
@@ -18,7 +18,7 @@ public class RepeatAi extends SpellAbilityAi {
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
if (!sa.canTarget(opp)) {
|
||||
return false;
|
||||
}
|
||||
sa.resetTargets();
|
||||
|
||||
@@ -119,11 +119,9 @@ public class ScryAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
double chance = .4; // 40 percent chance of milling with instant speed
|
||||
// stuff
|
||||
double chance = .4; // 40 percent chance of milling with instant speed stuff
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
chance = .667; // 66.7% chance for sorcery speed (since it will
|
||||
// never activate EOT)
|
||||
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
|
||||
}
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
|
||||
@@ -52,8 +52,7 @@ public class SetStateAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
// Gross generalization, but this always considers alternate
|
||||
// states more powerful
|
||||
// Gross generalization, but this always considers alternate states more powerful
|
||||
return !sa.getHostCard().isInAlternateState();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ public class ShuffleAi extends SpellAbilityAi {
|
||||
return aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN2, aiPlayer);
|
||||
}
|
||||
|
||||
// not really sure when the compy would use this; maybe only after a
|
||||
// human
|
||||
// not really sure when the compy would use this; maybe only after a human
|
||||
// deliberately put a card on top of their library
|
||||
return false;
|
||||
/*
|
||||
|
||||
@@ -11,6 +11,7 @@ import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
@@ -309,9 +310,8 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: use Defined to determine, if this is an unfavorable result
|
||||
|
||||
return true;
|
||||
final List<Card> pDefined = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
return pDefined.isEmpty() || (pDefined.get(0).isUntapped() && pDefined.get(0).getController() != ai);
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
if (tapPrefTargeting(ai, source, sa, mandatory)) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.phase.PhaseType;
|
||||
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.filter(validTappables, Presets.UNTAPPED);
|
||||
validTappables = CardLists.filter(validTappables, CardPredicates.Presets.UNTAPPED);
|
||||
|
||||
if (sa.hasParam("AILogic")) {
|
||||
String logic = sa.getParam("AILogic");
|
||||
@@ -69,18 +69,8 @@ public class TapAllAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<Card> human = CardLists.filter(validTappables, new Predicate<Card>() {
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
});
|
||||
final List<Card> human = CardLists.filterControlledBy(validTappables, opp);
|
||||
final List<Card> compy = CardLists.filterControlledBy(validTappables, ai);
|
||||
if (human.size() <= compy.size()) {
|
||||
return false;
|
||||
}
|
||||
@@ -102,7 +92,7 @@ public class TapAllAi extends SpellAbilityAi {
|
||||
final Game game = source.getGame();
|
||||
CardCollectionView tmpList = game.getCardsIn(ZoneType.Battlefield);
|
||||
tmpList = CardLists.getValidCards(tmpList, valid, source.getController(), source, sa);
|
||||
tmpList = CardLists.filter(tmpList, Presets.UNTAPPED);
|
||||
tmpList = CardLists.filter(tmpList, CardPredicates.Presets.UNTAPPED);
|
||||
return tmpList;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@ public class TapOrUntapAi extends TapAiBase {
|
||||
|
||||
if (!sa.usesTargeting()) {
|
||||
// assume we are looking to tap human's stuff
|
||||
// TODO - check for things with untap abilities, and don't tap
|
||||
// those.
|
||||
// TODO - check for things with untap abilities, and don't tap those.
|
||||
|
||||
boolean bFlag = false;
|
||||
for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) {
|
||||
@@ -40,6 +39,4 @@ public class TapOrUntapAi extends TapAiBase {
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
|
||||
if (!sa.usesTargeting()) {
|
||||
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 {
|
||||
return untapPrefTargeting(ai, sa, false);
|
||||
}
|
||||
@@ -82,9 +82,8 @@ public class UntapAi extends SpellAbilityAi {
|
||||
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);
|
||||
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 {
|
||||
if (untapPrefTargeting(ai, sa, mandatory)) {
|
||||
return true;
|
||||
|
||||
@@ -8,6 +8,7 @@ import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -29,6 +30,10 @@ public class UntapAllAi extends SpellAbilityAi {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
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 false;
|
||||
|
||||
@@ -88,7 +88,6 @@ public class GameCopier {
|
||||
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
|
||||
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
|
||||
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
|
||||
newPlayer.setPreventNextDamage(origPlayer.getPreventNextDamage());
|
||||
newPlayer.getManaPool().add(origPlayer.getManaPool());
|
||||
newPlayer.setCommanders(origPlayer.getCommanders()); // will be fixed up below
|
||||
playerMap.put(origPlayer, newPlayer);
|
||||
|
||||
@@ -151,6 +151,11 @@ public class StaticData {
|
||||
return this.editions;
|
||||
}
|
||||
|
||||
public final CardEdition.Collection getCustomEditions(){
|
||||
return this.customEditions;
|
||||
}
|
||||
|
||||
|
||||
private List<CardEdition> sortedEditions;
|
||||
public final List<CardEdition> getSortedEditions() {
|
||||
if (sortedEditions == null) {
|
||||
@@ -158,12 +163,24 @@ public class StaticData {
|
||||
for (CardEdition set : editions) {
|
||||
sortedEditions.add(set);
|
||||
}
|
||||
if (customEditions.size() > 0){
|
||||
for (CardEdition set : customEditions) {
|
||||
sortedEditions.add(set);
|
||||
}
|
||||
}
|
||||
Collections.sort(sortedEditions);
|
||||
Collections.reverse(sortedEditions); //put newer sets at the top
|
||||
}
|
||||
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) {
|
||||
PaperCard card = commonCards.getCard(cardName, setCode, artIndex);
|
||||
boolean isCustom = false;
|
||||
|
||||
@@ -6,7 +6,6 @@ package forge.card;
|
||||
*/
|
||||
public class CardAiHints {
|
||||
|
||||
|
||||
private final boolean isRemovedFromAIDecks;
|
||||
private final boolean isRemovedFromRandomDecks;
|
||||
private final boolean isRemovedFromNonCommanderDecks;
|
||||
@@ -15,7 +14,6 @@ public class CardAiHints {
|
||||
private final DeckHints deckNeeds;
|
||||
private final DeckHints deckHas;
|
||||
|
||||
|
||||
public CardAiHints(boolean remAi, boolean remRandom, boolean remUnlessCommander, DeckHints dh, DeckHints dn, DeckHints has) {
|
||||
isRemovedFromAIDecks = remAi;
|
||||
isRemovedFromRandomDecks = remRandom;
|
||||
@@ -90,5 +88,4 @@ public class CardAiHints {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -533,6 +533,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
@Override
|
||||
public int getPrintCount(String cardName, String edition) {
|
||||
int cnt = 0;
|
||||
if (edition == null || cardName == null)
|
||||
return cnt;
|
||||
for (PaperCard pc : getAllCards(cardName)) {
|
||||
if (pc.getEdition().equals(edition)) {
|
||||
cnt++;
|
||||
@@ -544,6 +546,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
@Override
|
||||
public int getMaxPrintCount(String cardName) {
|
||||
int max = -1;
|
||||
if (cardName == null)
|
||||
return max;
|
||||
for (PaperCard pc : getAllCards(cardName)) {
|
||||
if (max < pc.getArtIndex()) {
|
||||
max = pc.getArtIndex();
|
||||
@@ -555,6 +559,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
@Override
|
||||
public int getArtCount(String cardName, String setName) {
|
||||
int cnt = 0;
|
||||
if (cardName == null || setName == null)
|
||||
return cnt;
|
||||
|
||||
Collection<PaperCard> cards = getAllCards(cardName);
|
||||
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) {
|
||||
if (alternateName.containsKey(cardName)) {
|
||||
return alternateName.get(cardName);
|
||||
@@ -749,7 +772,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
|
||||
public PaperCard createUnsupportedCard(String cardName) {
|
||||
|
||||
CardRequest request = CardRequest.fromString(cardName);
|
||||
CardEdition cardEdition = CardEdition.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);
|
||||
|
||||
}
|
||||
|
||||
private final Editor editor = new Editor();
|
||||
|
||||
@@ -19,7 +19,6 @@ package forge.card;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -121,13 +120,16 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
// commonly used printsheets with collector number
|
||||
public enum EditionSectionWithCollectorNumbers {
|
||||
CARDS("cards"),
|
||||
SPECIAL_SLOT("special slot"), //to help with convoluted boosters
|
||||
PRECON_PRODUCT("precon product"),
|
||||
BORDERLESS("borderless"),
|
||||
SHOWCASE("showcase"),
|
||||
EXTENDED_ART("extended art"),
|
||||
ALTERNATE_ART("alternate art"),
|
||||
ALTERNATE_FRAME("alternate frame"),
|
||||
BUY_A_BOX("buy a box"),
|
||||
PROMO("promo"),
|
||||
BUNDLE("bundle"),
|
||||
BOX_TOPPER("box topper");
|
||||
|
||||
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 String collectorNumber;
|
||||
public final String name;
|
||||
@@ -172,6 +174,56 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
sb.append(name);
|
||||
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");
|
||||
@@ -206,6 +258,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
private String[] chaosDraftThemes = new String[0];
|
||||
|
||||
private final ListMultimap<String, CardInSet> cardMap;
|
||||
private final List<CardInSet> cardsInSet;
|
||||
private final Map<String, Integer> tokenNormalized;
|
||||
// custom print sheets that will be loaded lazily
|
||||
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) {
|
||||
this.cardMap = cardMap;
|
||||
this.cardsInSet = new ArrayList<>(cardMap.values());
|
||||
Collections.sort(cardsInSet);
|
||||
this.tokenNormalized = tokens;
|
||||
this.customPrintSheetsToParse = customPrintSheetsToParse;
|
||||
}
|
||||
|
||||
private CardEdition(CardInSet[] cards, Map<String, Integer> tokens) {
|
||||
List<CardInSet> cardsList = Arrays.asList(cards);
|
||||
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.customPrintSheetsToParse = new HashMap<>();
|
||||
}
|
||||
@@ -256,7 +314,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
date = date + "-01";
|
||||
try {
|
||||
return formatter.parse(date);
|
||||
} catch (ParseException e) {
|
||||
} catch (Exception e) {
|
||||
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> 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
|
||||
@@ -306,7 +364,10 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
if (o == null) {
|
||||
return 1;
|
||||
}
|
||||
return date.compareTo(o.date);
|
||||
int dateComp = date.compareTo(o.date);
|
||||
if (0 != dateComp)
|
||||
return dateComp;
|
||||
return name.compareTo(o.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -340,7 +401,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
}
|
||||
|
||||
public boolean isLargeSet() {
|
||||
return getAllCardsInSet().size() > 200 && !smallSetOverride;
|
||||
return this.cardsInSet.size() > 200 && !smallSetOverride;
|
||||
}
|
||||
|
||||
public int getCntBoosterPictures() {
|
||||
@@ -414,7 +475,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
* rarity - grouping #4
|
||||
* name - grouping #5
|
||||
*/
|
||||
"(^([0-9]+.?) )?(([SCURML]) )?(.*)$"
|
||||
"(^([0-9A-Z]+.?) )?(([SCURML]) )?(.*)$"
|
||||
);
|
||||
|
||||
ListMultimap<String, CardInSet> cardMap = ArrayListMultimap.create();
|
||||
|
||||
@@ -84,6 +84,12 @@ public final class CardRules implements ICardCharacteristics {
|
||||
boolean isReminder = false;
|
||||
boolean isSymbol = false;
|
||||
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();
|
||||
for(int i = 0; i < len; i++) {
|
||||
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);
|
||||
}
|
||||
} else if ("AlternateMode".equals(key)) {
|
||||
//System.out.println(faces[curFace].getName());
|
||||
this.altMode = CardSplitType.smartValueOf(value);
|
||||
} else if ("ALTERNATE".equals(key)) {
|
||||
this.curFace = 1;
|
||||
@@ -539,7 +544,6 @@ public final class CardRules implements ICardCharacteristics {
|
||||
@Override
|
||||
public final ManaCostShard next() {
|
||||
final String unparsed = st.nextToken();
|
||||
// System.out.println(unparsed);
|
||||
if (StringUtils.isNumeric(unparsed)) {
|
||||
this.genericCost += Integer.parseInt(unparsed);
|
||||
return null;
|
||||
@@ -555,7 +559,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
*/
|
||||
@Override
|
||||
public void remove() {
|
||||
} // unsuported
|
||||
} // unsupported
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ public class ManaCostParser implements IParserManaCost {
|
||||
*/
|
||||
public ManaCostParser(final String cost) {
|
||||
this.cost = cost.split(" ");
|
||||
// System.out.println(cost);
|
||||
this.nextToken = 0;
|
||||
this.genericCost = 0;
|
||||
}
|
||||
@@ -66,7 +65,6 @@ public class ManaCostParser implements IParserManaCost {
|
||||
@Override
|
||||
public final ManaCostShard next() {
|
||||
final String unparsed = this.cost[this.nextToken++];
|
||||
// System.out.println(unparsed);
|
||||
if (StringUtils.isNumeric(unparsed)) {
|
||||
this.genericCost += Integer.parseInt(unparsed);
|
||||
return null;
|
||||
|
||||
@@ -17,10 +17,11 @@ import forge.card.MagicColor;
|
||||
import forge.util.PredicateCard;
|
||||
import forge.util.PredicateString;
|
||||
|
||||
//import forge.Card;
|
||||
|
||||
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.
|
||||
*/
|
||||
@@ -228,6 +229,7 @@ public interface IPaperCard extends InventoryItem, Serializable {
|
||||
|
||||
String getName();
|
||||
String getEdition();
|
||||
String getCollectorNumber();
|
||||
int getArtIndex();
|
||||
boolean isFoil();
|
||||
boolean isToken();
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -26,6 +26,7 @@ import com.google.common.base.Function;
|
||||
import forge.ImageKeys;
|
||||
import forge.StaticData;
|
||||
import forge.card.CardDb;
|
||||
import forge.card.CardEdition;
|
||||
import forge.card.CardRarity;
|
||||
import forge.card.CardRules;
|
||||
import forge.util.CardTranslation;
|
||||
@@ -36,7 +37,7 @@ import forge.util.TextUtil;
|
||||
* A lightweight version of a card that matches real-world cards, to use outside of games (eg. inventory, decks, trade).
|
||||
* <br><br>
|
||||
* The full set of rules is in the CardRules class.
|
||||
*
|
||||
*
|
||||
* @author Forge
|
||||
*/
|
||||
public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet, IPaperCard, Serializable {
|
||||
@@ -48,12 +49,19 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
||||
// These fields are kinda PK for PrintedCard
|
||||
private final String name;
|
||||
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 boolean foil;
|
||||
private Boolean hasImage;
|
||||
|
||||
// Calculated fields are below:
|
||||
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
|
||||
public String getName() {
|
||||
@@ -65,6 +73,23 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
||||
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
|
||||
public int getArtIndex() {
|
||||
return artIndex;
|
||||
@@ -90,6 +115,20 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
||||
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
|
||||
// public String getImageKey() {
|
||||
// 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) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
@@ -160,32 +215,27 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
||||
if (!edition.equals(other.edition)) {
|
||||
return false;
|
||||
}
|
||||
if ((other.foil != foil) || (other.artIndex != artIndex)) {
|
||||
if (!getCollectorNumber().equals(other.getCollectorNumber()))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return (other.foil == foil) && (other.artIndex == artIndex);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
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) {
|
||||
return code + 1;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
// FIXME: Check
|
||||
@Override
|
||||
public String toString() {
|
||||
return CardTranslation.getTranslatedName(name);
|
||||
@@ -194,21 +244,49 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Comparable#compareTo(java.lang.Object)
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
public int compareTo(final IPaperCard o) {
|
||||
final int nameCmp = getName().compareToIgnoreCase(o.getName());
|
||||
final int nameCmp = name.compareToIgnoreCase(o.getName());
|
||||
if (0 != nameCmp) {
|
||||
return nameCmp;
|
||||
}
|
||||
// TODO compare sets properly
|
||||
//FIXME: compare sets properly
|
||||
int setDiff = edition.compareTo(o.getEdition());
|
||||
if ( 0 != setDiff )
|
||||
if (0 != 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());
|
||||
}
|
||||
|
||||
@@ -216,8 +294,7 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
||||
// default deserialization
|
||||
ois.defaultReadObject();
|
||||
|
||||
IPaperCard pc = null;
|
||||
pc = StaticData.instance().getCommonCards().getCard(name, edition, artIndex);
|
||||
IPaperCard pc = StaticData.instance().getCommonCards().getCard(name, edition, artIndex);
|
||||
if (pc == null) {
|
||||
pc = StaticData.instance().getVariantCards().getCard(name, edition, artIndex);
|
||||
if (pc == null) {
|
||||
@@ -228,9 +305,35 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|
||||
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
|
||||
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) {
|
||||
imageKey += ImageKeys.BACKFACE_POSTFIX;
|
||||
}
|
||||
|
||||
@@ -132,6 +132,12 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||
|
||||
@Override public String toString() { return name; }
|
||||
@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 boolean isFoil() { return false; }
|
||||
@Override public CardRules getRules() { return card; }
|
||||
|
||||
@@ -70,12 +70,14 @@ public class BoosterGenerator {
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
@@ -461,7 +463,7 @@ public class BoosterGenerator {
|
||||
if (toReplace != null) {
|
||||
// Keep the foil state
|
||||
if (toReplace.isFoil()) {
|
||||
toAdd = StaticData.instance().getCommonCards().getFoiled(toAdd);
|
||||
toAdd = toAdd.getFoiled();
|
||||
}
|
||||
booster.remove(toReplace);
|
||||
booster.add(toAdd);
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -208,7 +207,7 @@ public final class FileUtil {
|
||||
if ((file == null) || !file.exists()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return FileUtil.readAllLines(new FileReader(file), false);
|
||||
return FileUtil.readAllLines(file, false);
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException("FileUtil : readFile() error, " + ex);
|
||||
}
|
||||
@@ -248,6 +247,31 @@ public final class FileUtil {
|
||||
}
|
||||
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
|
||||
public static List<Pair<String, String>> readNameUrlFile(String nameUrlFile) {
|
||||
|
||||
@@ -99,7 +99,7 @@ public class ImageUtil {
|
||||
final CardDb db = StaticData.instance().getCommonCards();
|
||||
return db.getRules(card.getMeldWith()).getOtherPart().getName();
|
||||
} else {
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
@@ -118,6 +118,27 @@ public class ImageUtil {
|
||||
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) {
|
||||
final StringBuilder out = new StringBuilder();
|
||||
char c;
|
||||
@@ -130,4 +151,4 @@ public class ImageUtil {
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,147 +15,155 @@ import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class Localizer {
|
||||
|
||||
private static Localizer instance;
|
||||
|
||||
private List<LocalizationChangeObserver> observers = new ArrayList<>();
|
||||
|
||||
private Locale locale;
|
||||
private ResourceBundle resourceBundle;
|
||||
private static Localizer instance;
|
||||
|
||||
public static Localizer getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (Localizer.class) {
|
||||
instance = new Localizer();
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private Localizer() {
|
||||
}
|
||||
|
||||
public void initialize(String localeID, String languagesDirectory) {
|
||||
setLanguage(localeID, languagesDirectory);
|
||||
}
|
||||
private List<LocalizationChangeObserver> observers = new ArrayList<>();
|
||||
|
||||
public String convert(String value, String fromEncoding, String toEncoding) throws UnsupportedEncodingException {
|
||||
return new String(value.getBytes(fromEncoding), toEncoding);
|
||||
}
|
||||
private Locale locale;
|
||||
private ResourceBundle resourceBundle;
|
||||
|
||||
public String charset(String value, String charsets[]) {
|
||||
String probe = StandardCharsets.UTF_8.name();
|
||||
for(String c : charsets) {
|
||||
Charset charset = Charset.forName(c);
|
||||
if(charset != null) {
|
||||
try {
|
||||
if (value.equals(convert(convert(value, charset.name(), probe), probe, charset.name()))) {
|
||||
return c;
|
||||
}
|
||||
} catch(UnsupportedEncodingException ignored) {}
|
||||
}
|
||||
}
|
||||
return StandardCharsets.UTF_8.name();
|
||||
}
|
||||
public static Localizer getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (Localizer.class) {
|
||||
instance = new Localizer();
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public String getMessage(final String key, final Object... messageArguments) {
|
||||
MessageFormat formatter = null;
|
||||
private Localizer() {
|
||||
}
|
||||
|
||||
try {
|
||||
//formatter = new MessageFormat(resourceBundle.getString(key.toLowerCase()), locale);
|
||||
formatter = new MessageFormat(resourceBundle.getString(key), locale);
|
||||
} catch (final IllegalArgumentException | MissingResourceException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (formatter == null) {
|
||||
System.err.println("INVALID PROPERTY: '" + key + "' -- Translation Needed?");
|
||||
return "INVALID PROPERTY: '" + key + "' -- Translation Needed?";
|
||||
}
|
||||
|
||||
formatter.setLocale(locale);
|
||||
|
||||
String formattedMessage = "CHAR ENCODING ERROR";
|
||||
final String[] charsets = { "ISO-8859-1", "UTF-8" };
|
||||
//Support non-English-standard characters
|
||||
String detectedCharset = charset(resourceBundle.getString(key), charsets);
|
||||
public void initialize(String localeID, String languagesDirectory) {
|
||||
setLanguage(localeID, languagesDirectory);
|
||||
}
|
||||
|
||||
final int argLength = messageArguments.length;
|
||||
Object[] syncEncodingMessageArguments = new Object[argLength];
|
||||
//when messageArguments encoding not equal resourceBundle.getString(key),convert to equal
|
||||
//avoid convert to a have two encoding content formattedMessage string.
|
||||
for (int i = 0; i < argLength; i++) {
|
||||
String objCharset = charset(messageArguments[i].toString(), charsets);
|
||||
try {
|
||||
syncEncodingMessageArguments[i] = convert(messageArguments[i].toString(), objCharset, detectedCharset);
|
||||
} catch (UnsupportedEncodingException ignored) {
|
||||
System.err.println("Cannot Convert '" + messageArguments[i].toString() + "' from '" + objCharset + "' To '" + detectedCharset + "'");
|
||||
return "encoding '" + key + "' translate string failure";
|
||||
}
|
||||
}
|
||||
public String convert(String value, String fromEncoding, String toEncoding) throws UnsupportedEncodingException {
|
||||
return new String(value.getBytes(fromEncoding), toEncoding);
|
||||
}
|
||||
|
||||
try {
|
||||
formattedMessage = new String(formatter.format(syncEncodingMessageArguments).getBytes(detectedCharset), StandardCharsets.UTF_8);
|
||||
} catch(UnsupportedEncodingException ignored) {}
|
||||
public String charset(String value, String charsets[]) {
|
||||
String probe = StandardCharsets.UTF_8.name();
|
||||
for(String c : charsets) {
|
||||
Charset charset = Charset.forName(c);
|
||||
if(charset != null) {
|
||||
try {
|
||||
if (value.equals(convert(convert(value, charset.name(), probe), probe, charset.name()))) {
|
||||
return c;
|
||||
}
|
||||
} catch(UnsupportedEncodingException ignored) {}
|
||||
}
|
||||
}
|
||||
return StandardCharsets.UTF_8.name();
|
||||
}
|
||||
|
||||
return formattedMessage;
|
||||
}
|
||||
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) {
|
||||
MessageFormat formatter = null;
|
||||
|
||||
public void setLanguage(final String languageRegionID, final String languagesDirectory) {
|
||||
|
||||
String[] splitLocale = languageRegionID.split("-");
|
||||
|
||||
Locale oldLocale = locale;
|
||||
locale = new Locale(splitLocale[0], splitLocale[1]);
|
||||
|
||||
//Don't reload the language if nothing changed
|
||||
if (oldLocale == null || !oldLocale.equals(locale)) {
|
||||
try {
|
||||
//formatter = new MessageFormat(resourceBundle.getString(key.toLowerCase()), locale);
|
||||
formatter = new MessageFormat(resourceBundle.getString(key), locale);
|
||||
} catch (final IllegalArgumentException | MissingResourceException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
File file = new File(languagesDirectory);
|
||||
URL[] urls = null;
|
||||
|
||||
try {
|
||||
urls = new URL[] { file.toURI().toURL() };
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (formatter == null) {
|
||||
System.err.println("INVALID PROPERTY: '" + key + "' -- Translation Needed?");
|
||||
return "INVALID PROPERTY: '" + key + "' -- Translation Needed?";
|
||||
}
|
||||
|
||||
ClassLoader loader = new URLClassLoader(urls);
|
||||
formatter.setLocale(locale);
|
||||
|
||||
try {
|
||||
resourceBundle = ResourceBundle.getBundle(languageRegionID, new Locale(splitLocale[0], splitLocale[1]), loader);
|
||||
} catch (NullPointerException | MissingResourceException e) {
|
||||
//If the language can't be loaded, default to US English
|
||||
resourceBundle = ResourceBundle.getBundle("en-US", new Locale("en", "US"), loader);
|
||||
e.printStackTrace();
|
||||
}
|
||||
String formattedMessage = "CHAR ENCODING ERROR";
|
||||
final String[] charsets = { "ISO-8859-1", "UTF-8" };
|
||||
//Support non-English-standard characters
|
||||
String detectedCharset = charset(resourceBundle.getString(key), charsets);
|
||||
|
||||
System.out.println("Language '" + resourceBundle.toString() + "' loaded successfully.");
|
||||
|
||||
notifyObservers();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<Language> getLanguages() {
|
||||
//TODO List all languages by getting their files
|
||||
return null;
|
||||
}
|
||||
|
||||
public void registerObserver(LocalizationChangeObserver observer) {
|
||||
observers.add(observer);
|
||||
}
|
||||
|
||||
private void notifyObservers() {
|
||||
for (LocalizationChangeObserver observer : observers) {
|
||||
observer.localizationChanged();
|
||||
}
|
||||
}
|
||||
final int argLength = messageArguments.length;
|
||||
Object[] syncEncodingMessageArguments = new Object[argLength];
|
||||
//when messageArguments encoding not equal resourceBundle.getString(key),convert to equal
|
||||
//avoid convert to a have two encoding content formattedMessage string.
|
||||
for (int i = 0; i < argLength; i++) {
|
||||
String objCharset = charset(messageArguments[i].toString(), charsets);
|
||||
try {
|
||||
syncEncodingMessageArguments[i] = convert(messageArguments[i].toString(), objCharset, detectedCharset);
|
||||
} catch (UnsupportedEncodingException ignored) {
|
||||
System.err.println("Cannot Convert '" + messageArguments[i].toString() + "' from '" + objCharset + "' To '" + detectedCharset + "'");
|
||||
return "encoding '" + key + "' translate string failure";
|
||||
}
|
||||
}
|
||||
|
||||
public static class Language {
|
||||
public String languageName;
|
||||
public String languageID;
|
||||
}
|
||||
try {
|
||||
formattedMessage = new String(formatter.format(syncEncodingMessageArguments).getBytes(detectedCharset), StandardCharsets.UTF_8);
|
||||
} catch(UnsupportedEncodingException ignored) {}
|
||||
|
||||
return formattedMessage;
|
||||
}
|
||||
|
||||
public void setLanguage(final String languageRegionID, final String languagesDirectory) {
|
||||
|
||||
String[] splitLocale = languageRegionID.split("-");
|
||||
|
||||
Locale oldLocale = locale;
|
||||
locale = new Locale(splitLocale[0], splitLocale[1]);
|
||||
|
||||
//Don't reload the language if nothing changed
|
||||
if (oldLocale == null || !oldLocale.equals(locale)) {
|
||||
|
||||
File file = new File(languagesDirectory);
|
||||
URL[] urls = null;
|
||||
|
||||
try {
|
||||
urls = new URL[] { file.toURI().toURL() };
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
ClassLoader loader = new URLClassLoader(urls);
|
||||
|
||||
try {
|
||||
resourceBundle = ResourceBundle.getBundle(languageRegionID, new Locale(splitLocale[0], splitLocale[1]), loader);
|
||||
} catch (NullPointerException | MissingResourceException e) {
|
||||
//If the language can't be loaded, default to US English
|
||||
resourceBundle = ResourceBundle.getBundle("en-US", new Locale("en", "US"), loader);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
System.out.println("Language '" + resourceBundle.toString() + "' loaded successfully.");
|
||||
|
||||
notifyObservers();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<Language> getLanguages() {
|
||||
//TODO List all languages by getting their files
|
||||
return null;
|
||||
}
|
||||
|
||||
public void registerObserver(LocalizationChangeObserver observer) {
|
||||
observers.add(observer);
|
||||
}
|
||||
|
||||
private void notifyObservers() {
|
||||
for (LocalizationChangeObserver observer : observers) {
|
||||
observer.localizationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Language {
|
||||
public String languageName;
|
||||
public String languageID;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -162,6 +162,9 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean matchesValid(final Object o, final String[] valids, final Card srcCard) {
|
||||
if (srcCard == null) {
|
||||
return false;
|
||||
}
|
||||
return matchesValid(o, valids, srcCard, srcCard.getController());
|
||||
}
|
||||
|
||||
@@ -188,13 +191,17 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
return matchesValid(o, valids, getHostCard());
|
||||
}
|
||||
|
||||
public boolean matchesValidParam(String param, final Object o) {
|
||||
if (hasParam(param) && !matchesValid(o, getParam(param).split(","))) {
|
||||
public boolean matchesValidParam(String param, final Object o, final Card srcCard) {
|
||||
if (hasParam(param) && !matchesValid(o, getParam(param).split(","), srcCard)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean matchesValidParam(String param, final Object o) {
|
||||
return matchesValidParam(param, o, getHostCard());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
|
||||
final String rightString = presentCompare.substring(2);
|
||||
int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this);
|
||||
final int left = list.size();
|
||||
@@ -434,7 +440,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
}
|
||||
|
||||
if (params.containsKey("WerewolfTransformCondition")) {
|
||||
if (!CardUtil.getLastTurnCast("Card", this.getHostCard()).isEmpty()) {
|
||||
if (!CardUtil.getLastTurnCast("Card", this.getHostCard(), this).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,7 +756,7 @@ public class Game {
|
||||
// Rule 800.4 Losing a Multiplayer game
|
||||
CardCollectionView cards = this.getCardsInGame();
|
||||
boolean planarControllerLost = false;
|
||||
boolean isMultiplayer = this.getPlayers().size() > 2;
|
||||
boolean isMultiplayer = getPlayers().size() > 2;
|
||||
|
||||
// 702.142f & 707.9
|
||||
// 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) {
|
||||
// CR 800.4d if card is controlled by opponent, LTB should trigger
|
||||
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);
|
||||
// CR 603.2f owner of trigger source lost game
|
||||
triggerHandler.clearDelayedTrigger(c);
|
||||
getTriggerHandler().clearDelayedTrigger(c);
|
||||
} else {
|
||||
// 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);
|
||||
getAction().controllerChangeZoneCorrection(c);
|
||||
}
|
||||
c.removeTempController(p);
|
||||
if (c.getController().equals(p)) {
|
||||
this.getAction().exile(c, null);
|
||||
getAction().exile(c, null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -830,7 +830,7 @@ public class Game {
|
||||
}
|
||||
|
||||
// Remove leftover items from
|
||||
this.getStack().removeInstancesControlledBy(p);
|
||||
getStack().removeInstancesControlledBy(p);
|
||||
|
||||
getTriggerHandler().onPlayerLost(p);
|
||||
|
||||
@@ -1054,7 +1054,8 @@ public class Game {
|
||||
|
||||
public void onCleanupPhase() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import forge.game.ability.effects.AttachEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardDamageMap;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
@@ -61,6 +62,7 @@ import forge.game.event.GameEventCardTapped;
|
||||
import forge.game.event.GameEventFlipCoin;
|
||||
import forge.game.event.GameEventGameStarted;
|
||||
import forge.game.event.GameEventScry;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.mulligan.MulliganService;
|
||||
import forge.game.player.GameLossReason;
|
||||
@@ -144,8 +146,8 @@ public class GameAction {
|
||||
boolean fromBattlefield = zoneFrom != null && zoneFrom.is(ZoneType.Battlefield);
|
||||
boolean wasFacedown = c.isFaceDown();
|
||||
|
||||
//Rule 110.5g: A token that has left the battlefield can't move to another zone
|
||||
if (c.isToken() && zoneFrom != null && !fromBattlefield && !zoneFrom.is(ZoneType.Stack)) {
|
||||
// Rule 111.8: A token that has left the battlefield can't move to another zone
|
||||
if (!c.isSpell() && c.isToken() && zoneFrom != null && !fromBattlefield && !zoneFrom.is(ZoneType.Stack)) {
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -241,7 +243,7 @@ public class GameAction {
|
||||
copied = CardFactory.copyCard(c, false);
|
||||
|
||||
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.setChangedCardKeywords(c.getChangedCardKeywords());
|
||||
copied.setChangedCardTypes(c.getChangedCardTypesMap());
|
||||
@@ -342,6 +344,7 @@ public class GameAction {
|
||||
c.updateStateForView();
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -371,7 +374,7 @@ public class GameAction {
|
||||
if (saTargeting != null) {
|
||||
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.
|
||||
Card hostCard = cause.getHostCard();
|
||||
if (!cause.hasParam("RememberLKI") && hostCard.isRemembered(c)) {
|
||||
@@ -438,7 +441,7 @@ public class GameAction {
|
||||
}
|
||||
|
||||
if (mergedCards != null) {
|
||||
// Move components of merged permanet here
|
||||
// Move components of merged permanent here
|
||||
// Also handle 721.3e and 903.9a
|
||||
boolean wasToken = c.isToken();
|
||||
if (commanderEffect != null) {
|
||||
@@ -938,7 +941,10 @@ public class GameAction {
|
||||
game.getCombat().removeFromCombat(c);
|
||||
game.getCombat().saveLKI(lki);
|
||||
}
|
||||
game.getTriggerHandler().registerActiveLTBTrigger(lki);
|
||||
// again, make sure no triggers run from cards leaving controlled by loser
|
||||
if (!lki.getController().equals(lki.getOwner())) {
|
||||
game.getTriggerHandler().registerActiveLTBTrigger(lki);
|
||||
}
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
|
||||
runParams.put(AbilityKey.CardLKI, lki);
|
||||
runParams.put(AbilityKey.Origin, c.getZone().getZoneType().name());
|
||||
@@ -1352,8 +1358,7 @@ public class GameAction {
|
||||
if (c.getCounters(CounterEnumType.LORE) < c.getFinalChapterNr()) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getStack().hasSimultaneousStackEntries() &&
|
||||
!game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isChapter())) {
|
||||
if (!game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isChapter())) {
|
||||
sacrifice(c, null, table);
|
||||
checkAgain = true;
|
||||
}
|
||||
@@ -1680,7 +1685,9 @@ public class GameAction {
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
|
||||
runParams.put(AbilityKey.Causer, activator);
|
||||
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);
|
||||
return sacrificed != null;
|
||||
}
|
||||
@@ -1695,7 +1702,7 @@ public class GameAction {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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.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.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardDamageMap;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
@@ -35,19 +35,17 @@ import forge.game.card.CounterType;
|
||||
import forge.game.event.GameEventCardAttachment;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
protected final int id;
|
||||
private String name = "";
|
||||
private int preventNextDamage = 0;
|
||||
protected CardCollection attachedCards = new CardCollection();
|
||||
private Map<Card, Map<String, String>> preventionShieldsWithEffects = Maps.newTreeMap();
|
||||
protected Map<CounterType, Integer> counters = Maps.newHashMap();
|
||||
|
||||
protected GameEntity(int id0) {
|
||||
@@ -67,216 +65,61 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
getView().updateName(this);
|
||||
}
|
||||
|
||||
public final int addDamage(final int damage, final Card source, boolean isCombat, boolean noPrevention,
|
||||
final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
|
||||
if (noPrevention) {
|
||||
return addDamageWithoutPrevention(damage, source, damageMap, preventMap, counterTable, cause);
|
||||
} else if (isCombat) {
|
||||
return addCombatDamage(damage, source, damageMap, preventMap, counterTable);
|
||||
} else {
|
||||
return addDamage(damage, source, damageMap, preventMap, counterTable, cause);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// This function handles damage after replacement and prevention effects are applied
|
||||
public abstract int addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat, CardDamageMap damageMap, GameEntityCounterTable counterTable);
|
||||
public abstract int addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat, 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);
|
||||
public int staticDamagePrevention(final int damage, final int possiblePrevention, final Card source, final boolean isCombat) {
|
||||
if (damage <= 0) {
|
||||
return 0;
|
||||
}
|
||||
if (!source.canDamagePrevented(isCombat)) {
|
||||
return damage;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return Math.max(0, damage - possiblePrevention);
|
||||
}
|
||||
|
||||
// This should be also usable by the AI to forecast an effect (so it must
|
||||
// not change the game state)
|
||||
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() {
|
||||
int shields = preventNextDamage;
|
||||
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();
|
||||
return getGame().getReplacementHandler().getTotalPreventionShieldAmount(this);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
public GameEntityCounterTable() {
|
||||
}
|
||||
|
||||
public GameEntityCounterTable(Table<Optional<Player>, GameEntity, Map<CounterType, Integer>> counterTable) {
|
||||
putAll(counterTable);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.google.common.collect.ForwardingTable#delegate()
|
||||
|
||||
@@ -101,9 +101,10 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
this.effectiveDate = effectiveDate;
|
||||
|
||||
if(sets != null) {
|
||||
StaticData data = StaticData.instance();
|
||||
Set<String> parsedSets = new HashSet<>();
|
||||
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!");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ public class StaticEffect {
|
||||
// remove keywords
|
||||
// (Although nothing uses it at this time)
|
||||
if (hasParam("AddKeyword") || hasParam("RemoveKeyword")
|
||||
|| hasParam("RemoveAllAbilities")) {
|
||||
|| hasParam("RemoveAllAbilities") || hasParam("RemoveLandTypes")) {
|
||||
affectedCard.removeChangedCardKeywords(getTimestamp());
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ public enum AbilityKey {
|
||||
DefendingPlayer("DefendingPlayer"),
|
||||
Destination("Destination"),
|
||||
Devoured("Devoured"),
|
||||
DividedShieldAmount("DividedShieldAmount"),
|
||||
EchoPaid("EchoPaid"),
|
||||
EffectOnly("EffectOnly"),
|
||||
Exploited("Exploited"),
|
||||
@@ -96,12 +97,14 @@ public enum AbilityKey {
|
||||
PayingMana("PayingMana"),
|
||||
Phase("Phase"),
|
||||
Player("Player"),
|
||||
PreventedAmount("PreventedAmount"),
|
||||
PreventMap("PreventMap"),
|
||||
Prevention("Prevention"),
|
||||
Produced("Produced"),
|
||||
Regeneration("Regeneration"),
|
||||
ReplacementEffect("ReplacementEffect"),
|
||||
ReplacementResult("ReplacementResult"),
|
||||
ReplacementResultMap("ReplacementResultMap"),
|
||||
Result("Result"),
|
||||
Scheme("Scheme"),
|
||||
Source("Source"),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -97,6 +97,7 @@ public enum ApiType {
|
||||
LookAt (LookAtEffect.class),
|
||||
LoseLife (LifeLoseEffect.class),
|
||||
LosesGame (GameLossEffect.class),
|
||||
MakeCard (MakeCardEffect.class),
|
||||
Mana (ManaEffect.class),
|
||||
ManaReflected (ManaReflectedEffect.class),
|
||||
Manifest (ManifestEffect.class),
|
||||
|
||||
@@ -21,9 +21,10 @@ import forge.game.GameObject;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
@@ -55,7 +56,7 @@ public abstract class SpellAbilityEffect {
|
||||
public abstract void resolve(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();
|
||||
}
|
||||
|
||||
@@ -123,7 +124,7 @@ public abstract class SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("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(TextUtil.enclosedParen(TextUtil.concatNoSpace(svar,"=",String.valueOf(amount))));
|
||||
} else{
|
||||
@@ -198,7 +199,7 @@ public abstract class SpellAbilityEffect {
|
||||
// Players
|
||||
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 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); }
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
// 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, final String definedParam) { return getEntities(false, definedParam, sa); }
|
||||
@@ -297,7 +297,7 @@ public abstract class SpellAbilityEffect {
|
||||
}
|
||||
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) {
|
||||
trig.addRemembered(c);
|
||||
|
||||
@@ -576,6 +576,8 @@ public abstract class SpellAbilityEffect {
|
||||
|
||||
GameEntity defender = null;
|
||||
FCollection<GameEntity> defs = null;
|
||||
// important to update defenders here, maybe some PW got removed
|
||||
combat.initConstraints();
|
||||
if ("True".equalsIgnoreCase(attacking)) {
|
||||
defs = (FCollection<GameEntity>) combat.getDefenders();
|
||||
} 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
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final int numTurns = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumTurns"), sa);
|
||||
|
||||
List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
|
||||
|
||||
for (final Player player : tgtPlayers) {
|
||||
sb.append(player).append(" ");
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
// Every Animate event needs a unique time stamp
|
||||
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);
|
||||
if (sa.hasParam("Types")) {
|
||||
@@ -158,13 +158,7 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
};
|
||||
|
||||
if (!permanent) {
|
||||
if (sa.hasParam("UntilEndOfCombat")) {
|
||||
game.getEndOfCombat().addUntil(unanimate);
|
||||
} else if (sa.hasParam("UntilYourNextTurn")) {
|
||||
game.getCleanup().addUntil(host.getController(), unanimate);
|
||||
} else {
|
||||
game.getEndOfTurn().addUntil(unanimate);
|
||||
}
|
||||
addUntilCommand(sa, unanimate);
|
||||
}
|
||||
}
|
||||
} // animateAllResolve
|
||||
|
||||
@@ -29,8 +29,8 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
String animateImprinted = null;
|
||||
|
||||
//if host is not on the battlefield don't apply
|
||||
if ((sa.hasParam("UntilHostLeavesPlay") || sa.hasParam("UntilLoseControlOfHost"))
|
||||
&& !sa.getHostCard().isInPlay()) {
|
||||
if (("UntilHostLeavesPlay".equals(sa.getParam("Duration")) || "UntilLoseControlOfHost".equals(sa.getParam("Duration")))
|
||||
&& !source.isInPlay()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
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();
|
||||
if (sa.hasParam("Types")) {
|
||||
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
|
||||
@@ -296,15 +296,16 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
// sb.append(abilities)
|
||||
// sb.append(triggers)
|
||||
if (!permanent) {
|
||||
if (sa.hasParam("UntilEndOfCombat")) {
|
||||
final String duration = sa.getParam("Duration");
|
||||
if ("UntilEndOfCombat".equals(duration)) {
|
||||
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.");
|
||||
} else if (sa.hasParam("UntilYourNextUpkeep")) {
|
||||
} else if ("UntilYourNextUpkeep".equals(duration)) {
|
||||
sb.append(" until your next upkeep.");
|
||||
} else if (sa.hasParam("UntilYourNextTurn")) {
|
||||
} else if ("UntilYourNextTurn".equals(duration)) {
|
||||
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.");
|
||||
} else {
|
||||
sb.append(" until end of turn.");
|
||||
@@ -313,7 +314,6 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
sb.append(".");
|
||||
}
|
||||
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.event.GameEventCardStatsChanged;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementHandler;
|
||||
import forge.game.spellability.AbilityStatic;
|
||||
@@ -111,6 +110,10 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
removeEnchantmentTypes = true;
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberAnimated")) {
|
||||
source.addRemembered(c);
|
||||
}
|
||||
|
||||
if ((power != null) || (toughness != null)) {
|
||||
c.addNewPT(power, toughness, timestamp);
|
||||
}
|
||||
@@ -214,30 +217,11 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
addedStaticAbilities, removeAll, false, removeLandTypes, timestamp);
|
||||
}
|
||||
|
||||
if (!sa.hasParam("Permanent")) {
|
||||
if (sa.hasParam("UntilEndOfCombat")) {
|
||||
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")) {
|
||||
if (!"Permanent".equals(sa.getParam("Duration"))) {
|
||||
if ("UntilControllerNextUntap".equals(sa.getParam("Duration"))) {
|
||||
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 {
|
||||
game.getEndOfTurn().addUntil(unanimate);
|
||||
addUntilCommand(sa, unanimate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class AssignGroupEffect extends SpellAbilityEffect {
|
||||
Player chooser = sa.getActivatingPlayer();
|
||||
if (sa.hasParam("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();
|
||||
|
||||
@@ -29,7 +29,7 @@ public class BlockEffect extends SpellAbilityEffect {
|
||||
|
||||
List<Card> attackers = new ArrayList<>();
|
||||
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))
|
||||
attackers.add(attacker);
|
||||
}
|
||||
@@ -37,7 +37,7 @@ public class BlockEffect extends SpellAbilityEffect {
|
||||
|
||||
List<Card> blockers = new ArrayList<>();
|
||||
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))
|
||||
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.") &&
|
||||
blockers.size() < defender.getCreaturesInPlay().size() ||
|
||||
blockers.size() < CombatUtil.needsBlockers(attacker)) {
|
||||
if (blockers.size() < CombatUtil.getMinNumBlockersForAttacker(attacker, defender)) {
|
||||
// If not enough remaining creatures to block, don't add them as blocker
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -78,21 +78,17 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
||||
TargetChoices newTargetBlock = oldTargetBlock.clone();
|
||||
// gets the divied value from old target
|
||||
Integer div = oldTargetBlock.getDividedValue(oldTarget);
|
||||
newTargetBlock.remove(oldTarget);
|
||||
replaceIn.updateTarget(newTargetBlock, sa.getHostCard());
|
||||
// 3. test if updated choices would be correct.
|
||||
GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa, "DefinedMagnet"), null);
|
||||
|
||||
if (replaceIn.getSpellAbility(true).canTarget(newTarget)) {
|
||||
newTargetBlock.remove(oldTarget);
|
||||
newTargetBlock.add(newTarget);
|
||||
if (div != null) {
|
||||
newTargetBlock.addDividedAllocation(newTarget, div);
|
||||
}
|
||||
replaceIn.updateTarget(newTargetBlock, sa.getHostCard());
|
||||
}
|
||||
else {
|
||||
replaceIn.updateTarget(oldTargetBlock, sa.getHostCard());
|
||||
}
|
||||
}
|
||||
else {
|
||||
while(changingTgtSI != null) {
|
||||
@@ -103,6 +99,14 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
||||
int div = changingTgtSA.getTotalDividedValue();
|
||||
changingTgtSA.resetTargets();
|
||||
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);
|
||||
changingTgtSA.getTargets().add(choice);
|
||||
if (changingTgtSA.isDividedAsYouChoose()) {
|
||||
|
||||
@@ -26,7 +26,7 @@ public class ChangeTextEffect extends SpellAbilityEffect {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = source.getGame();
|
||||
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;
|
||||
if (sa.hasParam("ChangeColorWord")) {
|
||||
@@ -151,7 +151,7 @@ public class ChangeTextEffect extends SpellAbilityEffect {
|
||||
changedTypeWordNew = null;
|
||||
}
|
||||
|
||||
final boolean permanent = sa.hasParam("Permanent");
|
||||
final boolean permanent = "Permanent".equals(sa.getParam("Duration"));
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Change the text of ");
|
||||
|
||||
@@ -42,8 +42,10 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
@@ -53,7 +55,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
CardCollection cards;
|
||||
List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
final Game game = sa.getActivatingPlayer().getGame();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if ((!sa.usesTargeting() && !sa.hasParam("Defined")) || sa.hasParam("UseAllOriginZones")) {
|
||||
cards = new CardCollection(game.getCardsIn(origin));
|
||||
@@ -122,7 +123,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (sa.hasParam("ForgetOtherRemembered")) {
|
||||
sa.getHostCard().clearRemembered();
|
||||
source.clearRemembered();
|
||||
}
|
||||
|
||||
final String remember = sa.getParam("RememberChanged");
|
||||
@@ -203,9 +204,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("ExileFaceDown")) {
|
||||
movedCard.turnFaceDown(true);
|
||||
}
|
||||
if (sa.hasParam("Tapped")) {
|
||||
movedCard.setTapped(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (remember != null) {
|
||||
@@ -270,8 +268,8 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
|
||||
if (sa.hasParam("UntilHostLeavesPlay")) {
|
||||
source.addLeavesPlayCommand(untilHostLeavesPlayCommand(triggerList, source));
|
||||
if (sa.hasParam("Duration")) {
|
||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, source));
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
private boolean isHidden(SpellAbility sa) {
|
||||
boolean hidden = sa.hasParam("Hidden");
|
||||
if (!hidden && sa.hasParam("Origin")) {
|
||||
hidden = ZoneType.isHidden(sa.getParam("Origin"));
|
||||
}
|
||||
return hidden;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
if (isHidden(sa)) {
|
||||
if (sa.isHidden()) {
|
||||
return changeHiddenOriginStackDescription(sa);
|
||||
}
|
||||
return changeKnownOriginStackDescription(sa);
|
||||
@@ -97,13 +89,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
// Player whose cards will change zones
|
||||
List<Player> fetchers = null;
|
||||
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()) {
|
||||
fetchers = Lists.newArrayList(sa.getTargets().getTargetPlayers());
|
||||
}
|
||||
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);
|
||||
@@ -111,7 +103,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
// Player who chooses the cards to move
|
||||
List<Player> choosers = Lists.newArrayList();
|
||||
if (sa.hasParam("Chooser")) {
|
||||
choosers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Chooser"), sa);
|
||||
choosers = AbilityUtils.getDefinedPlayers(host, sa.getParam("Chooser"), sa);
|
||||
}
|
||||
if (choosers.isEmpty()) {
|
||||
choosers.add(sa.getActivatingPlayer());
|
||||
@@ -420,11 +412,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
//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;
|
||||
}
|
||||
|
||||
if (isHidden(sa) && !sa.hasParam("Ninjutsu")) {
|
||||
if (sa.isHidden() && !sa.hasParam("Ninjutsu")) {
|
||||
changeHiddenOriginResolve(sa);
|
||||
} else {
|
||||
//else if (isKnown(origin) || sa.containsKey("Ninjutsu")) {
|
||||
@@ -502,7 +494,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
Player chooser = player;
|
||||
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) {
|
||||
@@ -510,7 +502,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
// gameCard is LKI in that case, the card is not in game anymore
|
||||
// or the timestamp did change
|
||||
// this should check Self too
|
||||
if (gameCard == null || !tgtC.equalsWithTimestamp(gameCard)) {
|
||||
if (gameCard == null || !tgtC.equalsWithTimestamp(gameCard) || gameCard.isPhasedOut()) {
|
||||
continue;
|
||||
}
|
||||
if (sa.usesTargeting() && !gameCard.canBeTargetedBy(sa)) {
|
||||
@@ -636,8 +628,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
hostCard.removeRemembered(gameCard);
|
||||
}
|
||||
|
||||
// Auras without Candidates stay in their current
|
||||
// location
|
||||
// Auras without Candidates stay in their current location
|
||||
if (gameCard.isAura()) {
|
||||
final SpellAbility saAura = gameCard.getFirstAttachSpell();
|
||||
if (saAura != null) {
|
||||
@@ -679,12 +670,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
game.getCombat().getBandOfAttacker(movedCard).setBlocked(false);
|
||||
combatChanged = true;
|
||||
}
|
||||
if (sa.hasParam("Tapped") || sa.hasParam("Ninjutsu")) {
|
||||
movedCard.setTapped(true);
|
||||
}
|
||||
if (sa.hasParam("Untapped")) {
|
||||
movedCard.setTapped(false);
|
||||
}
|
||||
movedCard.setTimestamp(ts);
|
||||
} else {
|
||||
// might set before card is moved only for nontoken
|
||||
@@ -812,12 +797,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
counterTable.triggerCountersPutAll(game);
|
||||
|
||||
|
||||
if (sa.hasParam("AtEOT") && !triggerList.isEmpty()) {
|
||||
registerDelayedTrigger(sa, sa.getParam("AtEOT"), triggerList.allCards());
|
||||
}
|
||||
if (sa.hasParam("UntilHostLeavesPlay")) {
|
||||
hostCard.addLeavesPlayCommand(untilHostLeavesPlayCommand(triggerList, hostCard));
|
||||
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, hostCard));
|
||||
}
|
||||
|
||||
// for things like Gaea's Blessing
|
||||
@@ -1292,11 +1276,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
setFaceDownState(c, sa);
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -1402,8 +1381,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
|
||||
if (sa.hasParam("UntilHostLeavesPlay")) {
|
||||
source.addLeavesPlayCommand(untilHostLeavesPlayCommand(triggerList, source));
|
||||
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, source));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public class ChoosePlayerEffect extends SpellAbilityEffect {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
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 boolean random = sa.hasParam("Random");
|
||||
|
||||
@@ -5,11 +5,11 @@ import java.util.List;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -84,7 +84,6 @@ public class ChooseSourceEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("Choices")) {
|
||||
permanentSources = CardLists.getValidCards(permanentSources, 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);
|
||||
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 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) {
|
||||
final CardCollection chosen = new CardCollection();
|
||||
@@ -137,7 +136,7 @@ public class ChooseSourceEffect extends SpellAbilityEffect {
|
||||
Card o = null;
|
||||
do {
|
||||
o = p.getController().chooseSingleEntityForEffect(sourcesToChooseFrom, sa, choiceTitle, null);
|
||||
} while (o == null);
|
||||
} while (o == null || o.getName().startsWith("--"));
|
||||
chosen.add(o);
|
||||
sourcesToChooseFrom.remove(o);
|
||||
}
|
||||
|
||||
@@ -51,10 +51,11 @@ public class ClashEffect extends SpellAbilityEffect {
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, player);
|
||||
runParams.put(AbilityKey.Won, player.equals(winner) ? "True" : "False");
|
||||
sa.getHostCard().getGame().getTriggerHandler().runTrigger(TriggerType.Clashed, runParams, false);
|
||||
runParams.put(AbilityKey.Player, opponent);
|
||||
runParams.put(AbilityKey.Won, opponent.equals(winner) ? "True" : "False");
|
||||
sa.getHostCard().getGame().getTriggerHandler().runTrigger(TriggerType.Clashed, runParams, false);
|
||||
source.getGame().getTriggerHandler().runTrigger(TriggerType.Clashed, runParams, false);
|
||||
final Map<AbilityKey, Object> runParams2 = AbilityKey.newMap();
|
||||
runParams2.put(AbilityKey.Player, opponent);
|
||||
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.List;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
@@ -143,6 +142,12 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
|
||||
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
|
||||
if (tgtCard.isAura() && !tgtCard.isInZone(ZoneType.Battlefield)) {
|
||||
AttachEffect.attachAuraOnIndirectEnterBattlefield(tgtCard);
|
||||
@@ -150,7 +155,7 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("Duration")) {
|
||||
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<Object> clonedRemembered = new FCollection<>(tgtCard.getRemembered());
|
||||
|
||||
@@ -164,31 +169,19 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
cloneCard.clearImprintedCards();
|
||||
cloneCard.clearRemembered();
|
||||
// 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(Iterables.filter(clonedRemembered, Card.class), CardPredicates.ownerLives()));
|
||||
cloneCard.addRemembered(Iterables.filter(Iterables.filter(clonedRemembered, Card.class), CardPredicates.ownerLives()));
|
||||
cloneCard.updateStateForView();
|
||||
game.fireEvent(new GameEventCardStatsChanged(cloneCard));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final String duration = sa.getParam("Duration");
|
||||
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);
|
||||
}
|
||||
addUntilCommand(sa, unclone);
|
||||
}
|
||||
|
||||
//Clear Remembered and Imprint lists
|
||||
// now we can also cleanup in case target was another card
|
||||
tgtCard.clearRemembered();
|
||||
tgtCard.clearImprintedCards();
|
||||
|
||||
|
||||
@@ -99,8 +99,8 @@ public class ControlExchangeEffect extends SpellAbilityEffect {
|
||||
object2.setController(player1, tStamp);
|
||||
object1.setController(player2, tStamp);
|
||||
if (sa.hasParam("RememberExchanged")) {
|
||||
sa.getHostCard().addRemembered(object1);
|
||||
sa.getHostCard().addRemembered(object2);
|
||||
host.addRemembered(object1);
|
||||
host.addRemembered(object2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
final List<Player> newController = getTargetPlayers(sa, "NewController");
|
||||
final List<Player> newController = getDefinedPlayersOrTargeted(sa, "NewController");
|
||||
if (newController.isEmpty()) {
|
||||
newController.add(sa.getActivatingPlayer());
|
||||
}
|
||||
@@ -108,8 +108,8 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!tgtC.equals(sa.getHostCard()) && !sa.getHostCard().getGainControlTargets().contains(tgtC)) {
|
||||
sa.getHostCard().addGainControlTarget(tgtC);
|
||||
if (!tgtC.equals(source) && !source.getGainControlTargets().contains(tgtC)) {
|
||||
source.addGainControlTarget(tgtC);
|
||||
}
|
||||
|
||||
long tStamp = game.getNextTimestamp();
|
||||
@@ -139,24 +139,24 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
game.fireEvent(new GameEventCardStatsChanged(tgtC));
|
||||
}
|
||||
|
||||
if (remember && !sa.getHostCard().isRemembered(tgtC)) {
|
||||
sa.getHostCard().addRemembered(tgtC);
|
||||
if (remember && !source.isRemembered(tgtC)) {
|
||||
source.addRemembered(tgtC);
|
||||
}
|
||||
|
||||
if (forget && sa.getHostCard().isRemembered(tgtC)) {
|
||||
sa.getHostCard().removeRemembered(tgtC);
|
||||
if (forget && source.isRemembered(tgtC)) {
|
||||
source.removeRemembered(tgtC);
|
||||
}
|
||||
|
||||
if (lose != null) {
|
||||
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
|
||||
sa.getHostCard().addLeavesPlayCommand(loseControl);
|
||||
if (lose.contains("LeavesPlay") && source != tgtC) { // Only return control if host and target are different cards
|
||||
source.addLeavesPlayCommand(loseControl);
|
||||
}
|
||||
if (lose.contains("Untap")) {
|
||||
sa.getHostCard().addUntapCommand(loseControl);
|
||||
source.addUntapCommand(loseControl);
|
||||
}
|
||||
if (lose.contains("LoseControl")) {
|
||||
sa.getHostCard().addChangeControllerCommand(loseControl);
|
||||
source.addChangeControllerCommand(loseControl);
|
||||
}
|
||||
if (lose.contains("EOT")) {
|
||||
game.getEndOfTurn().addUntil(loseControl);
|
||||
@@ -169,7 +169,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
if (lose.contains("StaticCommandCheck")) {
|
||||
String leftVar = sa.getSVar(sa.getParam("StaticCommandCheckSVar"));
|
||||
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 (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.game.ability.effects;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -21,6 +22,7 @@ import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.CardTranslation;
|
||||
import forge.util.Localizer;
|
||||
|
||||
@@ -162,6 +164,19 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("MayChooseTarget")) {
|
||||
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
|
||||
if (sa.hasParam("Epic")) {
|
||||
|
||||
@@ -41,7 +41,7 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
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 ZoneType zone = sa.hasParam("ValidZone") ? ZoneType.smartValueOf(sa.getParam("ValidZone")) : ZoneType.Battlefield;
|
||||
final boolean etbcounter = sa.hasParam("ETB");
|
||||
@@ -52,7 +52,7 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
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()) {
|
||||
final Player pl = sa.getTargets().getFirstTargetedPlayer();
|
||||
|
||||
@@ -155,16 +155,16 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
}
|
||||
Player chooser = activator;
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
chooser = choosers.get(0);
|
||||
}
|
||||
|
||||
int n = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParamOrDefault("ChoiceAmount",
|
||||
int n = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("ChoiceAmount",
|
||||
"1"), sa);
|
||||
int m = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParamOrDefault("MinChoiceAmount",
|
||||
int m = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("MinChoiceAmount",
|
||||
sa.getParamOrDefault("ChoiceAmount", "1")), sa);
|
||||
|
||||
// no choices allowed
|
||||
@@ -405,10 +405,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
Player placer = activator;
|
||||
if (sa.hasParam("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();
|
||||
|
||||
@@ -422,7 +422,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
List<String> keywords = Arrays.asList(sa.getParam("SharedKeywords").split(" & "));
|
||||
List<ZoneType> zones = ZoneType.listValueOf(sa.getParam("SharedKeywordsZone"));
|
||||
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) {
|
||||
resolvePerType(sa, placer, CounterType.getType(k), counterAmount, table);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user