mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 10:48:00 +00:00
Merge branch 'master' of https://git.cardforge.org/core-developers/forge
This commit is contained in:
291
.gitignore
vendored
291
.gitignore
vendored
@@ -1,238 +1,77 @@
|
|||||||
/*.idea
|
# Ignore IDEA config files
|
||||||
/*.iml
|
|
||||||
/*.tmp
|
*.idea
|
||||||
/.metadata
|
*.iml
|
||||||
/.recommenders
|
*.tmp
|
||||||
forge-ai/forge-ai.iml
|
.metadata
|
||||||
forge-ai/target
|
.recommenders
|
||||||
forge-core/forge-core.iml
|
|
||||||
forge-core/target
|
|
||||||
forge-game/*.iml
|
# Ignore Eclipse config files
|
||||||
forge-game/target
|
|
||||||
forge-gui-android/*.iml
|
.settings
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
|
||||||
|
# Ignore VS Code config files
|
||||||
|
|
||||||
|
.vscode/settings.json
|
||||||
|
.vscode/launch.json
|
||||||
|
|
||||||
|
|
||||||
|
# Ignore NetBeans config files
|
||||||
|
|
||||||
|
nbactions.xml
|
||||||
|
|
||||||
|
|
||||||
|
# Ignore binaries, temp files and test output, everywhere
|
||||||
|
|
||||||
|
target
|
||||||
|
test-output
|
||||||
|
bin
|
||||||
|
gen
|
||||||
|
*.log
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: specify what these ignores are for (releasing?)
|
||||||
|
|
||||||
|
forge.profile.properties
|
||||||
|
/jgv.txt
|
||||||
|
pom.xml.next
|
||||||
|
pom.xml.releaseBackup
|
||||||
|
pom.xml.tag
|
||||||
|
release.properties
|
||||||
|
|
||||||
|
|
||||||
|
# Ignore mobile-related resources
|
||||||
|
|
||||||
|
forge-gui-android/res/*/*
|
||||||
|
!forge-gui-android/res/*/ic_launcher.png
|
||||||
|
!forge-gui-android/res/*/ic_launcher*.png
|
||||||
|
!forge-gui-android/res/layout/main.xml
|
||||||
forge-gui-android/*.keystore
|
forge-gui-android/*.keystore
|
||||||
forge-gui-android/assets/fallback_skin/Thumbs.db
|
forge-gui-android/**/Thumbs.db
|
||||||
forge-gui-android/bin
|
forge-gui-mobile-dev/**/Thumbs.db
|
||||||
forge-gui-android/gen
|
|
||||||
forge-gui-android/res/Thumbs.db
|
|
||||||
forge-gui-android/res/bin
|
|
||||||
forge-gui-android/res/drawable-hdpi/Thumbs.db
|
|
||||||
forge-gui-android/res/drawable-hdpi/bin
|
|
||||||
forge-gui-android/res/drawable-hdpi/gen
|
|
||||||
forge-gui-android/res/drawable-hdpi/target
|
|
||||||
forge-gui-android/res/drawable-ldpi/Thumbs.db
|
|
||||||
forge-gui-android/res/drawable-ldpi/bin
|
|
||||||
forge-gui-android/res/drawable-ldpi/gen
|
|
||||||
forge-gui-android/res/drawable-ldpi/target
|
|
||||||
forge-gui-android/res/drawable-mdpi/Thumbs.db
|
|
||||||
forge-gui-android/res/drawable-mdpi/bin
|
|
||||||
forge-gui-android/res/drawable-mdpi/gen
|
|
||||||
forge-gui-android/res/drawable-mdpi/target
|
|
||||||
forge-gui-android/res/drawable-xhdpi/Thumbs.db
|
|
||||||
forge-gui-android/res/drawable-xhdpi/bin
|
|
||||||
forge-gui-android/res/drawable-xhdpi/gen
|
|
||||||
forge-gui-android/res/drawable-xhdpi/target
|
|
||||||
forge-gui-android/res/drawable-xxhdpi/Thumbs.db
|
|
||||||
forge-gui-android/res/drawable-xxhdpi/bin
|
|
||||||
forge-gui-android/res/drawable-xxhdpi/gen
|
|
||||||
forge-gui-android/res/drawable-xxhdpi/target
|
|
||||||
forge-gui-android/res/gen
|
|
||||||
forge-gui-android/res/layout/Thumbs.db
|
|
||||||
forge-gui-android/res/layout/bin
|
|
||||||
forge-gui-android/res/layout/gen
|
|
||||||
forge-gui-android/res/layout/target
|
|
||||||
forge-gui-android/res/target
|
|
||||||
forge-gui-android/res/values/Thumbs.db
|
|
||||||
forge-gui-android/res/values/bin
|
|
||||||
forge-gui-android/res/values/gen
|
|
||||||
forge-gui-android/res/values/target
|
|
||||||
forge-gui-android/target
|
|
||||||
forge-gui-desktop/*.iml
|
|
||||||
forge-gui-desktop/target
|
|
||||||
forge-gui-ios/*.iml
|
|
||||||
forge-gui-ios/target
|
|
||||||
forge-gui-mobile-dev/*.iml
|
|
||||||
forge-gui-mobile-dev/bin
|
|
||||||
forge-gui-mobile-dev/fallback_skin/Thumbs.db
|
|
||||||
forge-gui-mobile-dev/res
|
forge-gui-mobile-dev/res
|
||||||
forge-gui-mobile-dev/target
|
|
||||||
forge-gui-mobile-dev/testAssets
|
forge-gui-mobile-dev/testAssets
|
||||||
forge-gui-mobile/*.iml
|
|
||||||
forge-gui-mobile/bin
|
|
||||||
forge-gui-mobile/target
|
|
||||||
forge-gui/forge-gui.iml
|
|
||||||
forge-gui/forge.profile.properties
|
|
||||||
forge-gui/res/*.log
|
|
||||||
forge-gui/res/PerSetTrackingResults
|
|
||||||
forge-gui/res/cardsfolder/*.bat
|
forge-gui/res/cardsfolder/*.bat
|
||||||
|
|
||||||
|
forge-gui/res/PerSetTrackingResults
|
||||||
forge-gui/res/decks
|
forge-gui/res/decks
|
||||||
forge-gui/res/layouts
|
forge-gui/res/layouts
|
||||||
forge-gui/res/pics*
|
forge-gui/res/pics*
|
||||||
forge-gui/res/pics_product
|
forge-gui/res/pics_product
|
||||||
|
|
||||||
|
forge-gui/res/skins/**/PerSetTrackingResults
|
||||||
|
forge-gui/res/skins/**/decks
|
||||||
|
forge-gui/res/skins/**/layouts
|
||||||
|
forge-gui/res/skins/**/pics*
|
||||||
|
forge-gui/res/skins/**/pics_product
|
||||||
|
|
||||||
forge-gui/res/quest/world/1996-05[!!-~]Ice[!!-~]Age/duels/.directory
|
forge-gui/res/quest/world/1996-05[!!-~]Ice[!!-~]Age/duels/.directory
|
||||||
forge-gui/res/skins/*.log
|
|
||||||
forge-gui/res/skins/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/Thumbs.db
|
|
||||||
forge-gui/res/skins/arabian_nights/*.log
|
|
||||||
forge-gui/res/skins/arabian_nights/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/arabian_nights/Thumbs.db
|
|
||||||
forge-gui/res/skins/arabian_nights/decks
|
|
||||||
forge-gui/res/skins/arabian_nights/layouts
|
|
||||||
forge-gui/res/skins/arabian_nights/pics*
|
|
||||||
forge-gui/res/skins/arabian_nights/pics_product
|
|
||||||
forge-gui/res/skins/comic/*.log
|
|
||||||
forge-gui/res/skins/comic/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/comic/Thumbs.db
|
|
||||||
forge-gui/res/skins/comic/decks
|
|
||||||
forge-gui/res/skins/comic/layouts
|
|
||||||
forge-gui/res/skins/comic/pics*
|
|
||||||
forge-gui/res/skins/comic/pics_product
|
|
||||||
forge-gui/res/skins/dark_ascension/*.log
|
|
||||||
forge-gui/res/skins/dark_ascension/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/dark_ascension/Thumbs.db
|
|
||||||
forge-gui/res/skins/dark_ascension/decks
|
|
||||||
forge-gui/res/skins/dark_ascension/layouts
|
|
||||||
forge-gui/res/skins/dark_ascension/pics*
|
|
||||||
forge-gui/res/skins/dark_ascension/pics_product
|
|
||||||
forge-gui/res/skins/decks
|
|
||||||
forge-gui/res/skins/default/*.log
|
|
||||||
forge-gui/res/skins/default/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/default/Thumbs.db
|
|
||||||
forge-gui/res/skins/default/decks
|
|
||||||
forge-gui/res/skins/default/layouts
|
|
||||||
forge-gui/res/skins/default/pics*
|
|
||||||
forge-gui/res/skins/default/pics_product
|
|
||||||
forge-gui/res/skins/firebloom/*.log
|
|
||||||
forge-gui/res/skins/firebloom/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/firebloom/Thumbs.db
|
|
||||||
forge-gui/res/skins/firebloom/decks
|
|
||||||
forge-gui/res/skins/firebloom/layouts
|
|
||||||
forge-gui/res/skins/firebloom/pics*
|
|
||||||
forge-gui/res/skins/firebloom/pics_product
|
|
||||||
forge-gui/res/skins/inferno/*.log
|
|
||||||
forge-gui/res/skins/inferno/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/inferno/Thumbs.db
|
|
||||||
forge-gui/res/skins/inferno/decks
|
|
||||||
forge-gui/res/skins/inferno/layouts
|
|
||||||
forge-gui/res/skins/inferno/pics*
|
|
||||||
forge-gui/res/skins/inferno/pics_product
|
|
||||||
forge-gui/res/skins/innistrad/*.log
|
|
||||||
forge-gui/res/skins/innistrad/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/innistrad/Thumbs.db
|
|
||||||
forge-gui/res/skins/innistrad/decks
|
|
||||||
forge-gui/res/skins/innistrad/layouts
|
|
||||||
forge-gui/res/skins/innistrad/pics*
|
|
||||||
forge-gui/res/skins/innistrad/pics_product
|
|
||||||
forge-gui/res/skins/journeyman/*.log
|
|
||||||
forge-gui/res/skins/journeyman/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/journeyman/Thumbs.db
|
|
||||||
forge-gui/res/skins/journeyman/decks
|
|
||||||
forge-gui/res/skins/journeyman/layouts
|
|
||||||
forge-gui/res/skins/journeyman/pics*
|
|
||||||
forge-gui/res/skins/journeyman/pics_product
|
|
||||||
forge-gui/res/skins/kamigawa/*.log
|
|
||||||
forge-gui/res/skins/kamigawa/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/kamigawa/Thumbs.db
|
|
||||||
forge-gui/res/skins/kamigawa/decks
|
|
||||||
forge-gui/res/skins/kamigawa/layouts
|
|
||||||
forge-gui/res/skins/kamigawa/pics*
|
|
||||||
forge-gui/res/skins/kamigawa/pics_product
|
|
||||||
forge-gui/res/skins/layouts
|
|
||||||
forge-gui/res/skins/marble_blue/*.log
|
|
||||||
forge-gui/res/skins/marble_blue/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/marble_blue/Thumbs.db
|
|
||||||
forge-gui/res/skins/marble_blue/decks
|
|
||||||
forge-gui/res/skins/marble_blue/layouts
|
|
||||||
forge-gui/res/skins/marble_blue/pics*
|
|
||||||
forge-gui/res/skins/marble_blue/pics_product
|
|
||||||
forge-gui/res/skins/metalcraft/*.log
|
|
||||||
forge-gui/res/skins/metalcraft/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/metalcraft/Thumbs.db
|
|
||||||
forge-gui/res/skins/metalcraft/decks
|
|
||||||
forge-gui/res/skins/metalcraft/layouts
|
|
||||||
forge-gui/res/skins/metalcraft/pics*
|
|
||||||
forge-gui/res/skins/metalcraft/pics_product
|
|
||||||
forge-gui/res/skins/mythic_rare/*.log
|
|
||||||
forge-gui/res/skins/mythic_rare/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/mythic_rare/Thumbs.db
|
|
||||||
forge-gui/res/skins/mythic_rare/decks
|
|
||||||
forge-gui/res/skins/mythic_rare/layouts
|
|
||||||
forge-gui/res/skins/mythic_rare/pics*
|
|
||||||
forge-gui/res/skins/mythic_rare/pics_product
|
|
||||||
forge-gui/res/skins/phyrexia/*.log
|
|
||||||
forge-gui/res/skins/phyrexia/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/phyrexia/Thumbs.db
|
|
||||||
forge-gui/res/skins/phyrexia/decks
|
|
||||||
forge-gui/res/skins/phyrexia/layouts
|
|
||||||
forge-gui/res/skins/phyrexia/pics*
|
|
||||||
forge-gui/res/skins/phyrexia/pics_product
|
|
||||||
forge-gui/res/skins/pics*
|
|
||||||
forge-gui/res/skins/pics_product
|
|
||||||
forge-gui/res/skins/ravnica/*.log
|
|
||||||
forge-gui/res/skins/ravnica/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/ravnica/Thumbs.db
|
|
||||||
forge-gui/res/skins/ravnica/decks
|
|
||||||
forge-gui/res/skins/ravnica/layouts
|
|
||||||
forge-gui/res/skins/ravnica/pics*
|
|
||||||
forge-gui/res/skins/ravnica/pics_product
|
|
||||||
forge-gui/res/skins/rebel/*.log
|
|
||||||
forge-gui/res/skins/rebel/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/rebel/Thumbs.db
|
|
||||||
forge-gui/res/skins/rebel/decks
|
|
||||||
forge-gui/res/skins/rebel/layouts
|
|
||||||
forge-gui/res/skins/rebel/pics*
|
|
||||||
forge-gui/res/skins/rebel/pics_product
|
|
||||||
forge-gui/res/skins/sleeping_forest/*.log
|
|
||||||
forge-gui/res/skins/sleeping_forest/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/sleeping_forest/Thumbs.db
|
|
||||||
forge-gui/res/skins/sleeping_forest/decks
|
|
||||||
forge-gui/res/skins/sleeping_forest/layouts
|
|
||||||
forge-gui/res/skins/sleeping_forest/pics*
|
|
||||||
forge-gui/res/skins/sleeping_forest/pics_product
|
|
||||||
forge-gui/res/skins/smith/*.log
|
|
||||||
forge-gui/res/skins/smith/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/smith/Thumbs.db
|
|
||||||
forge-gui/res/skins/smith/decks
|
|
||||||
forge-gui/res/skins/smith/layouts
|
|
||||||
forge-gui/res/skins/smith/pics*
|
|
||||||
forge-gui/res/skins/smith/pics_product
|
|
||||||
forge-gui/res/skins/the_dale/*.log
|
|
||||||
forge-gui/res/skins/the_dale/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/the_dale/Thumbs.db
|
|
||||||
forge-gui/res/skins/the_dale/decks
|
|
||||||
forge-gui/res/skins/the_dale/layouts
|
|
||||||
forge-gui/res/skins/the_dale/pics*
|
|
||||||
forge-gui/res/skins/the_dale/pics_product
|
|
||||||
forge-gui/res/skins/the_simpsons/*.log
|
|
||||||
forge-gui/res/skins/the_simpsons/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/the_simpsons/Thumbs.db
|
|
||||||
forge-gui/res/skins/the_simpsons/decks
|
|
||||||
forge-gui/res/skins/the_simpsons/layouts
|
|
||||||
forge-gui/res/skins/the_simpsons/pics*
|
|
||||||
forge-gui/res/skins/the_simpsons/pics_product
|
|
||||||
forge-gui/res/skins/zendikar/*.log
|
|
||||||
forge-gui/res/skins/zendikar/PerSetTrackingResults
|
|
||||||
forge-gui/res/skins/zendikar/Thumbs.db
|
|
||||||
forge-gui/res/skins/zendikar/decks
|
|
||||||
forge-gui/res/skins/zendikar/layouts
|
|
||||||
forge-gui/res/skins/zendikar/pics*
|
|
||||||
forge-gui/res/skins/zendikar/pics_product
|
|
||||||
forge-gui/target
|
|
||||||
forge-gui/tools/AllCards.json
|
forge-gui/tools/AllCards.json
|
||||||
forge-gui/tools/EditionTrackingResults
|
forge-gui/tools/EditionTrackingResults
|
||||||
forge-gui/tools/PerSetTrackingResults
|
forge-gui/tools/PerSetTrackingResults
|
||||||
forge-gui/tools/oracleScript.log
|
|
||||||
/forge.profile.properties
|
|
||||||
/jgv.txt
|
|
||||||
/nbactions.xml
|
|
||||||
/pom.xml.next
|
|
||||||
/pom.xml.releaseBackup
|
|
||||||
/pom.xml.tag
|
|
||||||
/release.properties
|
|
||||||
/target
|
|
||||||
/test-output
|
|
||||||
.settings
|
|
||||||
.classpath
|
|
||||||
.project
|
|
||||||
.vscode/settings.json
|
|
||||||
.vscode/launch.json
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import forge.deck.CardPool;
|
|||||||
import forge.deck.Deck;
|
import forge.deck.Deck;
|
||||||
import forge.deck.DeckSection;
|
import forge.deck.DeckSection;
|
||||||
import forge.game.*;
|
import forge.game.*;
|
||||||
import forge.game.ability.AbilityFactory;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.ability.SpellApiBased;
|
import forge.game.ability.SpellApiBased;
|
||||||
@@ -64,7 +63,6 @@ import forge.util.collect.FCollectionView;
|
|||||||
import io.sentry.Sentry;
|
import io.sentry.Sentry;
|
||||||
import io.sentry.event.BreadcrumbBuilder;
|
import io.sentry.event.BreadcrumbBuilder;
|
||||||
|
|
||||||
import java.security.InvalidParameterException;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
@@ -204,24 +202,22 @@ public class AiController {
|
|||||||
return api == null;
|
return api == null;
|
||||||
}
|
}
|
||||||
boolean rightapi = false;
|
boolean rightapi = false;
|
||||||
String battlefield = ZoneType.Battlefield.toString();
|
|
||||||
Player activatingPlayer = sa.getActivatingPlayer();
|
Player activatingPlayer = sa.getActivatingPlayer();
|
||||||
|
|
||||||
// Trigger play improvements
|
// Trigger play improvements
|
||||||
for (final Trigger tr : card.getTriggers()) {
|
for (final Trigger tr : card.getTriggers()) {
|
||||||
// These triggers all care for ETB effects
|
// These triggers all care for ETB effects
|
||||||
|
|
||||||
final Map<String, String> params = tr.getMapParams();
|
|
||||||
if (tr.getMode() != TriggerType.ChangesZone) {
|
if (tr.getMode() != TriggerType.ChangesZone) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params.get("Destination").equals(battlefield)) {
|
if (!ZoneType.Battlefield.toString().equals(tr.getParam("Destination"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.containsKey("ValidCard")) {
|
if (tr.hasParam("ValidCard")) {
|
||||||
String validCard = params.get("ValidCard");
|
String validCard = tr.getParam("ValidCard");
|
||||||
if (!validCard.contains("Self")) {
|
if (!validCard.contains("Self")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -245,21 +241,11 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if trigger is not mandatory - no problem
|
// if trigger is not mandatory - no problem
|
||||||
if (params.get("OptionalDecider") != null && api == null) {
|
if (tr.hasParam("OptionalDecider") && api == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
SpellAbility exSA = tr.getOverridingAbility();
|
SpellAbility exSA = tr.ensureAbility().copy(activatingPlayer);
|
||||||
|
|
||||||
if (exSA == null) {
|
|
||||||
// Maybe better considerations
|
|
||||||
if (!params.containsKey("Execute")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
exSA = AbilityFactory.getAbility(card, params.get("Execute"));
|
|
||||||
} else {
|
|
||||||
exSA = exSA.copy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (api != null) {
|
if (api != null) {
|
||||||
if (exSA.getApi() != api) {
|
if (exSA.getApi() != api) {
|
||||||
@@ -273,13 +259,7 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa != null) {
|
exSA.setTrigger(tr);
|
||||||
exSA.setActivatingPlayer(activatingPlayer);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
exSA.setActivatingPlayer(player);
|
|
||||||
}
|
|
||||||
exSA.setTrigger(true);
|
|
||||||
|
|
||||||
// for trigger test, need to ignore the conditions
|
// for trigger test, need to ignore the conditions
|
||||||
SpellAbilityCondition cons = exSA.getConditions();
|
SpellAbilityCondition cons = exSA.getConditions();
|
||||||
@@ -304,18 +284,16 @@ public class AiController {
|
|||||||
// Replacement effects
|
// Replacement effects
|
||||||
for (final ReplacementEffect re : card.getReplacementEffects()) {
|
for (final ReplacementEffect re : card.getReplacementEffects()) {
|
||||||
// These Replacements all care for ETB effects
|
// These Replacements all care for ETB effects
|
||||||
|
|
||||||
final Map<String, String> params = re.getMapParams();
|
|
||||||
if (!(re instanceof ReplaceMoved)) {
|
if (!(re instanceof ReplaceMoved)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params.get("Destination").equals(battlefield)) {
|
if (!ZoneType.Battlefield.toString().equals(re.getParam("Destination"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.containsKey("ValidCard")) {
|
if (re.hasParam("ValidCard")) {
|
||||||
String validCard = params.get("ValidCard");
|
String validCard = re.getParam("ValidCard");
|
||||||
if (!validCard.contains("Self")) {
|
if (!validCard.contains("Self")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -337,27 +315,18 @@ public class AiController {
|
|||||||
if (!re.requirementsCheck(game)) {
|
if (!re.requirementsCheck(game)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final SpellAbility exSA = re.getOverridingAbility();
|
SpellAbility exSA = re.getOverridingAbility();
|
||||||
|
|
||||||
if (exSA != null) {
|
if (exSA != null) {
|
||||||
if (sa != null) {
|
exSA = exSA.copy(activatingPlayer);
|
||||||
exSA.setActivatingPlayer(activatingPlayer);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
exSA.setActivatingPlayer(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exSA.getActivatingPlayer() == null) {
|
|
||||||
throw new InvalidParameterException("Executing SpellAbility for Replacement Effect has no activating player");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ETBReplacement uses overriding abilities.
|
// ETBReplacement uses overriding abilities.
|
||||||
// These checks only work if the Executing SpellAbility is an Ability_Sub.
|
// These checks only work if the Executing SpellAbility is an Ability_Sub.
|
||||||
if (exSA != null && (exSA instanceof AbilitySub) && !doTrigger(exSA, false)) {
|
if ((exSA instanceof AbilitySub) && !doTrigger(exSA, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,6 +697,8 @@ public class AiController {
|
|||||||
|
|
||||||
public AiPlayDecision canPlaySa(SpellAbility sa) {
|
public AiPlayDecision canPlaySa(SpellAbility sa) {
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
|
final boolean isRightTiming = sa.canCastTiming(player);
|
||||||
|
|
||||||
if (!checkAiSpecificRestrictions(sa)) {
|
if (!checkAiSpecificRestrictions(sa)) {
|
||||||
return AiPlayDecision.CantPlayAi;
|
return AiPlayDecision.CantPlayAi;
|
||||||
}
|
}
|
||||||
@@ -771,6 +742,7 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Cost payCosts = sa.getPayCosts();
|
Cost payCosts = sa.getPayCosts();
|
||||||
|
if(payCosts != null) {
|
||||||
ManaCost mana = payCosts.getTotalMana();
|
ManaCost mana = payCosts.getTotalMana();
|
||||||
if (mana != null) {
|
if (mana != null) {
|
||||||
if(mana.countX() > 0) {
|
if(mana.countX() > 0) {
|
||||||
@@ -779,7 +751,7 @@ public class AiController {
|
|||||||
if (xPay <= 0) {
|
if (xPay <= 0) {
|
||||||
return AiPlayDecision.CantAffordX;
|
return AiPlayDecision.CantAffordX;
|
||||||
}
|
}
|
||||||
card.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
} else if (mana.isZero()) {
|
} else if (mana.isZero()) {
|
||||||
// if mana is zero, but card mana cost does have X, then something is wrong
|
// if mana is zero, but card mana cost does have X, then something is wrong
|
||||||
ManaCost cardCost = card.getManaCost();
|
ManaCost cardCost = card.getManaCost();
|
||||||
@@ -789,10 +761,14 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (checkCurseEffects(sa)) {
|
if (checkCurseEffects(sa)) {
|
||||||
return AiPlayDecision.CurseEffects;
|
return AiPlayDecision.CurseEffects;
|
||||||
}
|
}
|
||||||
if (sa instanceof SpellPermanent) {
|
if (sa instanceof SpellPermanent) {
|
||||||
|
if (!isRightTiming) {
|
||||||
|
return AiPlayDecision.AnotherTime;
|
||||||
|
}
|
||||||
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
|
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
|
||||||
}
|
}
|
||||||
if (sa.usesTargeting() && !sa.isTargetNumberValid()) {
|
if (sa.usesTargeting() && !sa.isTargetNumberValid()) {
|
||||||
@@ -805,8 +781,14 @@ public class AiController {
|
|||||||
&& !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) {
|
&& !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) {
|
||||||
return AiPlayDecision.CurseEffects;
|
return AiPlayDecision.CurseEffects;
|
||||||
}
|
}
|
||||||
|
if (!isRightTiming) {
|
||||||
|
return AiPlayDecision.AnotherTime;
|
||||||
|
}
|
||||||
return canPlaySpellBasic(card, sa);
|
return canPlaySpellBasic(card, sa);
|
||||||
}
|
}
|
||||||
|
if (!isRightTiming) {
|
||||||
|
return AiPlayDecision.AnotherTime;
|
||||||
|
}
|
||||||
return AiPlayDecision.WillPlay;
|
return AiPlayDecision.WillPlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -997,6 +979,10 @@ public class AiController {
|
|||||||
if (source.isEquipment() && noCreatures) {
|
if (source.isEquipment() && noCreatures) {
|
||||||
p -= 9;
|
p -= 9;
|
||||||
}
|
}
|
||||||
|
// don't equip stuff in main 2 if there's more stuff to cast at the moment
|
||||||
|
if (sa.getApi() == ApiType.Attach && !sa.isCurse() && source.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
|
p -= 1;
|
||||||
|
}
|
||||||
// 1. increase chance of using Surge effects
|
// 1. increase chance of using Surge effects
|
||||||
// 2. non-surged versions are usually inefficient
|
// 2. non-surged versions are usually inefficient
|
||||||
if (source.getOracleText().contains("surge cost") && !sa.isSurged()) {
|
if (source.getOracleText().contains("surge cost") && !sa.isSurged()) {
|
||||||
@@ -1075,8 +1061,8 @@ public class AiController {
|
|||||||
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) {
|
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) {
|
||||||
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
||||||
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
||||||
final int CMC = Integer.parseInt(sourceCard.getSVar("PayX"));
|
final int cmc = Integer.parseInt(sa.getSVar("PayX"));
|
||||||
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(CMC));
|
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
|
||||||
if (discards.isEmpty()) {
|
if (discards.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
@@ -1605,10 +1591,18 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sa.setActivatingPlayer(player);
|
sa.setActivatingPlayer(player);
|
||||||
|
SpellAbility root = sa.getRootAbility();
|
||||||
|
|
||||||
|
if (root.isSpell() || root.isTrigger() || root.isReplacementAbility()) {
|
||||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||||
|
}
|
||||||
|
|
||||||
AiPlayDecision opinion = canPlayAndPayFor(sa);
|
AiPlayDecision opinion = canPlayAndPayFor(sa);
|
||||||
|
|
||||||
|
// reset LastStateBattlefield
|
||||||
|
sa.setLastStateBattlefield(CardCollection.EMPTY);
|
||||||
|
sa.setLastStateGraveyard(CardCollection.EMPTY);
|
||||||
// PhaseHandler ph = game.getPhaseHandler();
|
// PhaseHandler ph = game.getPhaseHandler();
|
||||||
// System.out.printf("Ai thinks '%s' of %s -> %s @ %s %s >>> \n", opinion, sa.getHostCard(), sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
|
// System.out.printf("Ai thinks '%s' of %s -> %s @ %s %s >>> \n", opinion, sa.getHostCard(), sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
|
||||||
|
|
||||||
@@ -1789,6 +1783,8 @@ public class AiController {
|
|||||||
return Math.max(remaining, min) / 2;
|
return Math.max(remaining, min) / 2;
|
||||||
} else if ("LowestLoseLife".equals(logic)) {
|
} else if ("LowestLoseLife".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
||||||
|
} else if ("HighestLoseLife".equals(logic)) {
|
||||||
|
return MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
||||||
} else if ("HighestGetCounter".equals(logic)) {
|
} else if ("HighestGetCounter".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(3);
|
return MyRandom.getRandom().nextInt(3);
|
||||||
} else if (source.hasSVar("EnergyToPay")) {
|
} else if (source.hasSVar("EnergyToPay")) {
|
||||||
@@ -1806,6 +1802,9 @@ public class AiController {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
CardCollection result = new CardCollection();
|
CardCollection result = new CardCollection();
|
||||||
|
if (sa.hasParam("AIMaxAmount")) {
|
||||||
|
max = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("AIMaxAmount"), sa);
|
||||||
|
}
|
||||||
switch(sa.getApi()) {
|
switch(sa.getApi()) {
|
||||||
case TwoPiles:
|
case TwoPiles:
|
||||||
// TODO: improve AI
|
// TODO: improve AI
|
||||||
@@ -2099,9 +2098,9 @@ public class AiController {
|
|||||||
return filterList(list, CardTraitPredicates.hasParam("AiLogic", logic));
|
return filterList(list, CardTraitPredicates.hasParam("AiLogic", logic));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat) {
|
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> possible, int min, int num, boolean allowRepeat) {
|
||||||
if (simPicker != null) {
|
if (simPicker != null) {
|
||||||
return simPicker.chooseModeForAbility(sa, min, num, allowRepeat);
|
return simPicker.chooseModeForAbility(sa, possible, min, num, allowRepeat);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
|
import forge.game.GameEntityCounterTable;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
@@ -20,6 +23,7 @@ import forge.game.card.CardPredicates.Presets;
|
|||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.cost.*;
|
import forge.game.cost.*;
|
||||||
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
@@ -554,17 +558,14 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final String amount = cost.getAmount();
|
||||||
Integer c = cost.convertAmount();
|
Integer c = cost.convertAmount();
|
||||||
|
|
||||||
if (c == null) {
|
if (c == null) {
|
||||||
if (ability.getSVar(cost.getAmount()).equals("XChoice")) {
|
final String sVar = ability.getSVar(amount);
|
||||||
|
if (sVar.equals("XChoice")) {
|
||||||
String logic = ability.getParamOrDefault("AILogic", "");
|
String logic = ability.getParamOrDefault("AILogic", "");
|
||||||
if ("SacToReduceCost".equals(logic)) {
|
if (!logic.isEmpty() && !logic.equals("Never")) {
|
||||||
// e.g. Torgaar, Famine Incarnate
|
|
||||||
// TODO: currently returns an empty list, so the AI doesn't sacrifice anything. Trying to make
|
|
||||||
// the AI decide on creatures to sac makes the AI sacrifice them, but the cost is not reduced and the
|
|
||||||
// AI pays the full mana cost anyway (despite sacrificing creatures).
|
|
||||||
return PaymentDecision.card(new CardCollection());
|
|
||||||
} else if (!logic.isEmpty() && !logic.equals("Never")) {
|
|
||||||
// If at least some other AI logic is specified, assume that the AI for that API knows how
|
// If at least some other AI logic is specified, assume that the AI for that API knows how
|
||||||
// to define ChosenX and thus honor that value.
|
// to define ChosenX and thus honor that value.
|
||||||
// Cards which have no special logic for this yet but which do work in a simple/suboptimal way
|
// Cards which have no special logic for this yet but which do work in a simple/suboptimal way
|
||||||
@@ -574,8 +575,10 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
// Other cards are assumed to be flagged AI:RemoveDeck:All for now
|
// Other cards are assumed to be flagged AI:RemoveDeck:All for now
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
} else if (sVar.equals("Count$xPaid")) {
|
||||||
|
c = AbilityUtils.calculateAmount(source, "PayX", ability);
|
||||||
} else {
|
} else {
|
||||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||||
@@ -632,101 +635,98 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
|
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected int removeCounter(GameEntityCounterTable table, List<Card> prefs, CounterEnumType cType, int stillToRemove) {
|
||||||
|
int removed = 0;
|
||||||
|
if (!prefs.isEmpty() && stillToRemove > 0) {
|
||||||
|
Collections.sort(prefs, CardPredicates.compareByCounterType(cType));
|
||||||
|
|
||||||
|
for (Card prefCard : prefs) {
|
||||||
|
// already enough removed
|
||||||
|
if (stillToRemove <= removed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
||||||
|
if (thisRemove > 0) {
|
||||||
|
removed += thisRemove;
|
||||||
|
table.put(prefCard, CounterType.get(cType), thisRemove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
||||||
final String amount = cost.getAmount();
|
final String amount = cost.getAmount();
|
||||||
final int c = AbilityUtils.calculateAmount(source, amount, ability);
|
final int c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||||
final String type = cost.getType();
|
final Card originalHost = ObjectUtils.defaultIfNull(ability.getOriginalHost(), source);
|
||||||
|
|
||||||
CardCollectionView typeList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source, ability);
|
if (c <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
CardCollectionView typeList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source, ability);
|
||||||
|
// only cards with counters are of interest
|
||||||
|
typeList = CardLists.filter(typeList, CardPredicates.hasCounters());
|
||||||
|
|
||||||
// no target
|
// no target
|
||||||
if (typeList.isEmpty()) {
|
if (typeList.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO fill up a GameEntityCounterTable
|
||||||
|
// cost now has counter type or null
|
||||||
|
// the amount might be different from 1, could be X
|
||||||
|
// currently if amount is bigger than one,
|
||||||
|
// it tries to remove all counters from one source and type at once
|
||||||
|
|
||||||
|
|
||||||
|
int toRemove = 0;
|
||||||
|
final GameEntityCounterTable table = new GameEntityCounterTable();
|
||||||
|
|
||||||
|
// currently the only one using remove any counter using a type uses p1p1
|
||||||
|
|
||||||
// the first things are benefit from removing counters
|
// the first things are benefit from removing counters
|
||||||
|
|
||||||
// try to remove +1/+1 counter from undying creature
|
|
||||||
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.P1P1, c),
|
|
||||||
CardPredicates.hasKeyword("Undying"));
|
|
||||||
|
|
||||||
if (!prefs.isEmpty()) {
|
|
||||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.P1P1));
|
|
||||||
PaymentDecision result = PaymentDecision.card(prefs);
|
|
||||||
result.ct = CounterType.get(CounterEnumType.P1P1);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to remove -1/-1 counter from persist creature
|
// try to remove -1/-1 counter from persist creature
|
||||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.M1M1, c),
|
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.M1M1))) {
|
||||||
CardPredicates.hasKeyword("Persist"));
|
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.M1M1), CardPredicates.hasKeyword(Keyword.PERSIST));
|
||||||
|
|
||||||
if (!prefs.isEmpty()) {
|
toRemove += removeCounter(table, prefs, CounterEnumType.M1M1, c - toRemove);
|
||||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.M1M1));
|
|
||||||
PaymentDecision result = PaymentDecision.card(prefs);
|
|
||||||
result.ct = CounterType.get(CounterEnumType.M1M1);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to remove Time counter from Chronozoa, it will generate more
|
// try to remove +1/+1 counter from undying creature
|
||||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.TIME, c),
|
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.P1P1))) {
|
||||||
CardPredicates.nameEquals("Chronozoa"));
|
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.P1P1), CardPredicates.hasKeyword(Keyword.UNDYING));
|
||||||
|
|
||||||
if (!prefs.isEmpty()) {
|
toRemove += removeCounter(table, prefs, CounterEnumType.P1P1, c - toRemove);
|
||||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.TIME));
|
|
||||||
PaymentDecision result = PaymentDecision.card(prefs);
|
|
||||||
result.ct = CounterType.get(CounterEnumType.TIME);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to remove Quest counter on something with enough counters for the
|
if (c > toRemove && cost.counter == null && originalHost.hasSVar("AIRemoveCounterCostPriority") && !"ANY".equalsIgnoreCase(originalHost.getSVar("AIRemoveCounterCostPriority"))) {
|
||||||
// effect to continue
|
String[] counters = TextUtil.split(originalHost.getSVar("AIRemoveCounterCostPriority"), ',');
|
||||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.QUEST, c));
|
|
||||||
|
|
||||||
if (!prefs.isEmpty()) {
|
for (final String ctr : counters) {
|
||||||
prefs = CardLists.filter(prefs, new Predicate<Card>() {
|
CounterType ctype = CounterType.getType(ctr);
|
||||||
@Override
|
// ctype == null means any type
|
||||||
public boolean apply(final Card crd) {
|
// any type is just used to return null for this
|
||||||
// a Card without MaxQuestEffect doesn't need any Quest
|
|
||||||
// counters
|
|
||||||
int e = 0;
|
|
||||||
if (crd.hasSVar("MaxQuestEffect")) {
|
|
||||||
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
|
|
||||||
}
|
|
||||||
return crd.getCounters(CounterEnumType.QUEST) >= e + c;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterEnumType.QUEST)));
|
|
||||||
PaymentDecision result = PaymentDecision.card(prefs);
|
|
||||||
result.ct = CounterType.get(CounterEnumType.QUEST);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter for only cards with enough counters
|
for (Card card : CardLists.filter(typeList, CardPredicates.hasCounter(ctype))) {
|
||||||
typeList = CardLists.filter(typeList, new Predicate<Card>() {
|
int thisRemove = Math.min(card.getCounters(ctype), c - toRemove);
|
||||||
@Override
|
if (thisRemove > 0) {
|
||||||
public boolean apply(final Card crd) {
|
toRemove += thisRemove;
|
||||||
for (Integer i : crd.getCounters().values()) {
|
table.put(card, ctype, thisRemove);
|
||||||
if (i >= c) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// nothing with enough counters of any type
|
|
||||||
if (typeList.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter for negative counters
|
// filter for negative counters
|
||||||
|
if (c > toRemove && cost.counter == null) {
|
||||||
List<Card> negatives = CardLists.filter(typeList, new Predicate<Card>() {
|
List<Card> negatives = CardLists.filter(typeList, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card crd) {
|
public boolean apply(final Card crd) {
|
||||||
for (Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
for (CounterType cType : table.filterToRemove(crd).keySet()) {
|
||||||
if (e.getValue() >= c && ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
if (ComputerUtil.isNegativeCounter(cType, crd)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -735,25 +735,29 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!negatives.isEmpty()) {
|
if (!negatives.isEmpty()) {
|
||||||
final Card card = ComputerUtilCard.getBestAI(negatives);
|
// TODO sort negatives to remove from best Cards first?
|
||||||
PaymentDecision result = PaymentDecision.card(card);
|
for (final Card crd : negatives) {
|
||||||
|
for (Map.Entry<CounterType, Integer> e : table.filterToRemove(crd).entrySet()) {
|
||||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
if (ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||||
if (e.getValue() >= c && ComputerUtil.isNegativeCounter(e.getKey(), card)) {
|
int over = Math.min(e.getValue(), c - toRemove);
|
||||||
result.ct = e.getKey();
|
if (over > 0) {
|
||||||
break;
|
toRemove += over;
|
||||||
|
table.put(crd, e.getKey(), over);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter for useless counters
|
// filter for useless counters
|
||||||
// they have no effect on the card, if they are there or removed
|
// they have no effect on the card, if they are there or removed
|
||||||
|
if (c > toRemove && cost.counter == null) {
|
||||||
List<Card> useless = CardLists.filter(typeList, new Predicate<Card>() {
|
List<Card> useless = CardLists.filter(typeList, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card crd) {
|
public boolean apply(final Card crd) {
|
||||||
for (Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
for (CounterType ctype : table.filterToRemove(crd).keySet()) {
|
||||||
if (e.getValue() >= c && ComputerUtil.isUselessCounter(e.getKey())) {
|
if (ComputerUtil.isUselessCounter(ctype, crd)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -762,62 +766,98 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!useless.isEmpty()) {
|
if (!useless.isEmpty()) {
|
||||||
final Card card = useless.get(0);
|
for (final Card crd : useless) {
|
||||||
PaymentDecision result = PaymentDecision.card(card);
|
for (Map.Entry<CounterType, Integer> e : table.filterToRemove(crd).entrySet()) {
|
||||||
|
if (ComputerUtil.isUselessCounter(e.getKey(), crd)) {
|
||||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
int over = Math.min(e.getValue(), c - toRemove);
|
||||||
if (e.getValue() >= c && ComputerUtil.isUselessCounter(e.getKey())) {
|
if (over > 0) {
|
||||||
result.ct = e.getKey();
|
toRemove += over;
|
||||||
break;
|
table.put(crd, e.getKey(), over);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// try a way to pay unless cost
|
// try to remove Time counter from Chronozoa, it will generate more token
|
||||||
if ("Chisei, Heart of Oceans".equals(ComputerUtilAbility.getAbilitySourceName(ability))) {
|
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.TIME))) {
|
||||||
final Card card = ComputerUtilCard.getWorstAI(typeList);
|
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.TIME), CardPredicates.nameEquals("Chronozoa"));
|
||||||
PaymentDecision result = PaymentDecision.card(card);
|
|
||||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
toRemove += removeCounter(table, prefs, CounterEnumType.TIME, c - toRemove);
|
||||||
if (e.getValue() >= c) {
|
|
||||||
result.ct = e.getKey();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the card defines its own priorities for counter removal as cost
|
// try to remove Quest counter on something with enough counters for the
|
||||||
if (source.hasSVar("AIRemoveCounterCostPriority")) {
|
// effect to continue
|
||||||
String[] counters = TextUtil.split(source.getSVar("AIRemoveCounterCostPriority"), ',');
|
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.QUEST))) {
|
||||||
|
List<Card> prefs = CardLists.filter(typeList, new Predicate<Card>() {
|
||||||
for (final String ctr : counters) {
|
|
||||||
List<Card> withCtr = CardLists.filter(typeList, new Predicate<Card>() {
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card crd) {
|
public boolean apply(final Card crd) {
|
||||||
for (Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
// a Card without MaxQuestEffect doesn't need any Quest
|
||||||
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) {
|
// counters
|
||||||
return true;
|
int e = 0;
|
||||||
|
if (crd.hasSVar("MaxQuestEffect")) {
|
||||||
|
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
|
||||||
}
|
}
|
||||||
}
|
return crd.getCounters(CounterEnumType.QUEST) > e;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!withCtr.isEmpty()) {
|
Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterEnumType.QUEST)));
|
||||||
final Card card = withCtr.get(0);
|
|
||||||
PaymentDecision result = PaymentDecision.card(card);
|
|
||||||
|
|
||||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
for (final Card crd : prefs) {
|
||||||
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) {
|
int e = 0;
|
||||||
result.ct = e.getKey();
|
if (crd.hasSVar("MaxQuestEffect")) {
|
||||||
break;
|
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
|
||||||
}
|
}
|
||||||
}
|
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
|
||||||
return result;
|
if (over > 0) {
|
||||||
|
toRemove += over;
|
||||||
|
table.put(crd, CounterType.get(CounterEnumType.QUEST), over);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
// remove Lore counters from Sagas to keep them longer
|
||||||
|
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.LORE))) {
|
||||||
|
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.LORE), CardPredicates.isType("Saga"));
|
||||||
|
// TODO add Svars and other stuff to keep the Sagas on specific levels
|
||||||
|
// also add a way for the AI to respond to the last Chapter ability to keep the Saga on the field if wanted
|
||||||
|
toRemove += removeCounter(table, prefs, CounterEnumType.LORE, c - toRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO add logic to remove positive counters?
|
||||||
|
if (c > toRemove && cost.counter != null) {
|
||||||
|
// TODO add logic for Ooze Flux, should probably try to make a token as big as possible
|
||||||
|
// without killing own non undying creatures in the process
|
||||||
|
// the amount of X should probably be tweaked for this
|
||||||
|
List<Card> withCtr = CardLists.filter(typeList, CardPredicates.hasCounter(cost.counter));
|
||||||
|
for (Card card : withCtr) {
|
||||||
|
int thisRemove = Math.min(card.getCounters(cost.counter), c - toRemove);
|
||||||
|
if (thisRemove > 0) {
|
||||||
|
toRemove += thisRemove;
|
||||||
|
table.put(card, cost.counter, thisRemove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to not return null
|
||||||
|
// Special part for CostPriority Any
|
||||||
|
if (c > toRemove && cost.counter == null && originalHost.hasSVar("AIRemoveCounterCostPriority") && "ANY".equalsIgnoreCase(originalHost.getSVar("AIRemoveCounterCostPriority"))) {
|
||||||
|
for (Card card : typeList) {
|
||||||
|
// TODO try not to remove to much positive counters from the same card
|
||||||
|
for (Map.Entry<CounterType, Integer> e : table.filterToRemove(card).entrySet()) {
|
||||||
|
int thisRemove = Math.min(e.getValue(), c - toRemove);
|
||||||
|
if (thisRemove > 0) {
|
||||||
|
toRemove += thisRemove;
|
||||||
|
table.put(card, e.getKey(), thisRemove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if table is empty, than no counter was removed
|
||||||
|
return table.isEmpty() ? null : PaymentDecision.counters(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -835,7 +875,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
c = source.getCounters(cost.counter);
|
c = source.getCounters(cost.counter);
|
||||||
} else if (sVar.equals("Targeted$CardManaCost")) {
|
} else if (sVar.equals("Targeted$CardManaCost")) {
|
||||||
c = 0;
|
c = 0;
|
||||||
if (ability.getTargets().getNumTargeted() > 0) {
|
if (ability.getTargets().size() > 0) {
|
||||||
for (Card tgt : ability.getTargets().getTargetCards()) {
|
for (Card tgt : ability.getTargets().getTargetCards()) {
|
||||||
if (tgt.getManaCost() != null) {
|
if (tgt.getManaCost() != null) {
|
||||||
c += tgt.getManaCost().getCMC();
|
c += tgt.getManaCost().getCMC();
|
||||||
@@ -843,7 +883,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (sVar.equals("Count$xPaid")) {
|
} else if (sVar.equals("Count$xPaid")) {
|
||||||
c = AbilityUtils.calculateAmount(source, "PayX", null);
|
c = AbilityUtils.calculateAmount(source, "PayX", ability);
|
||||||
} else {
|
} else {
|
||||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public enum AiProps { /** */
|
|||||||
ALWAYS_COPY_SPELL_IF_CMC_DIFF ("2"), /** */
|
ALWAYS_COPY_SPELL_IF_CMC_DIFF ("2"), /** */
|
||||||
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */
|
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */
|
||||||
ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */
|
ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */
|
||||||
|
ACTIVELY_PROTECT_VS_CURSE_AURAS("false"), /** */
|
||||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */
|
DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */
|
||||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
|
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
|
||||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */
|
DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */
|
||||||
|
|||||||
@@ -101,7 +101,9 @@ public class ComputerUtil {
|
|||||||
sa = GameActionUtil.addExtraKeywordCost(sa);
|
sa = GameActionUtil.addExtraKeywordCost(sa);
|
||||||
|
|
||||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||||
CharmEffect.makeChoices(sa);
|
if (!CharmEffect.makeChoices(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (chooseTargets != null) {
|
if (chooseTargets != null) {
|
||||||
chooseTargets.run();
|
chooseTargets.run();
|
||||||
@@ -159,8 +161,6 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
// Play higher costing spells first?
|
// Play higher costing spells first?
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
// Convert cost to CMC
|
|
||||||
// String totalMana = source.getSVar("PayX"); // + cost.getCMC()
|
|
||||||
|
|
||||||
// Consider the costs here for relative "scoring"
|
// Consider the costs here for relative "scoring"
|
||||||
if (hasDiscardHandCost(cost)) {
|
if (hasDiscardHandCost(cost)) {
|
||||||
@@ -261,7 +261,9 @@ public class ComputerUtil {
|
|||||||
newSA.setHostCard(game.getAction().moveToStack(source, sa));
|
newSA.setHostCard(game.getAction().moveToStack(source, sa));
|
||||||
|
|
||||||
if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) {
|
if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) {
|
||||||
CharmEffect.makeChoices(newSA);
|
if (!CharmEffect.makeChoices(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,7 +716,6 @@ public class ComputerUtil {
|
|||||||
CardCollection remaining = new CardCollection(cardlist);
|
CardCollection remaining = new CardCollection(cardlist);
|
||||||
final CardCollection sacrificed = new CardCollection();
|
final CardCollection sacrificed = new CardCollection();
|
||||||
final Card host = source.getHostCard();
|
final Card host = source.getHostCard();
|
||||||
final boolean considerSacLogic = "ConsiderSac".equals(source.getParam("AILogic"));
|
|
||||||
final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold");
|
final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold");
|
||||||
|
|
||||||
if ("OpponentOnly".equals(source.getParam("AILogic"))) {
|
if ("OpponentOnly".equals(source.getParam("AILogic"))) {
|
||||||
@@ -730,25 +731,20 @@ public class ComputerUtil {
|
|||||||
if (!ai.canLoseLife() || ai.cantLose()) {
|
if (!ai.canLoseLife() || ai.cantLose()) {
|
||||||
return sacrificed; // sacrifice none
|
return sacrificed; // sacrifice none
|
||||||
}
|
}
|
||||||
} else if (!considerSacLogic) {
|
} else {
|
||||||
return sacrificed; // sacrifice none
|
return sacrificed; // sacrifice none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean exceptSelf = "ExceptSelf".equals(source.getParam("AILogic"));
|
boolean exceptSelf = "ExceptSelf".equals(source.getParam("AILogic"));
|
||||||
boolean removedSelf = false;
|
boolean removedSelf = false;
|
||||||
|
|
||||||
if (isOptional && source.hasParam("Devour") || source.hasParam("Exploit") || considerSacLogic) {
|
if (isOptional && source.hasParam("Devour") || source.hasParam("Exploit")) {
|
||||||
if (source.hasParam("Exploit")) {
|
if (source.hasParam("Exploit")) {
|
||||||
for (Trigger t : host.getTriggers()) {
|
for (Trigger t : host.getTriggers()) {
|
||||||
if (t.getMode() == TriggerType.Exploited) {
|
if (t.getMode() == TriggerType.Exploited) {
|
||||||
final String execute = t.getParam("Execute");
|
final SpellAbility exSA = t.ensureAbility().copy(ai);
|
||||||
if (execute == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final SpellAbility exSA = AbilityFactory.getAbility(host.getSVar(execute), host);
|
|
||||||
|
|
||||||
exSA.setActivatingPlayer(ai);
|
exSA.setTrigger(t);
|
||||||
exSA.setTrigger(true);
|
|
||||||
|
|
||||||
// Run non-mandatory trigger.
|
// Run non-mandatory trigger.
|
||||||
// These checks only work if the Executing SpellAbility is an Ability_Sub.
|
// These checks only work if the Executing SpellAbility is an Ability_Sub.
|
||||||
@@ -764,17 +760,21 @@ public class ComputerUtil {
|
|||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
int sacThreshold = 190;
|
int sacThreshold = 190;
|
||||||
|
|
||||||
if ("HeartPiercer".equals(source.getParam("SacrificeParam"))) {
|
String logic = source.getParamOrDefault("AILogic", "");
|
||||||
if (c.getNetPower() == 0) {
|
if (logic.startsWith("SacForDamage")) {
|
||||||
|
if (c.getNetPower() <= 0) {
|
||||||
return false;
|
return false;
|
||||||
} else if (c.getNetPower() >= ai.getOpponentsSmallestLifeTotal()) {
|
} else if (c.getNetPower() >= ai.getOpponentsSmallestLifeTotal()) {
|
||||||
return true;
|
return true;
|
||||||
|
} else if (logic.endsWith(".GiantX2") && c.getType().hasCreatureType("Giant")
|
||||||
|
&& c.getNetPower() * 2 >= ai.getOpponentsSmallestLifeTotal()) {
|
||||||
|
return true; // TODO: generalize this for any type and actually make the AI prefer giants?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("DesecrationDemon".equals(source.getParam("AILogic"))) {
|
if ("DesecrationDemon".equals(source.getParam("AILogic"))) {
|
||||||
sacThreshold = SpecialCardAi.DesecrationDemon.getSacThreshold();
|
sacThreshold = SpecialCardAi.DesecrationDemon.getSacThreshold();
|
||||||
} else if (considerSacLogic && considerSacThreshold != -1) {
|
} else if (considerSacThreshold != -1) {
|
||||||
sacThreshold = considerSacThreshold;
|
sacThreshold = considerSacThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1569,7 +1569,7 @@ public class ComputerUtil {
|
|||||||
return threatened;
|
return threatened;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
objects = topStack.getTargets().getTargets();
|
objects = topStack.getTargets();
|
||||||
final List<GameObject> canBeTargeted = new ArrayList<>();
|
final List<GameObject> canBeTargeted = new ArrayList<>();
|
||||||
for (Object o : objects) {
|
for (Object o : objects) {
|
||||||
if (o instanceof Card) {
|
if (o instanceof Card) {
|
||||||
@@ -1841,6 +1841,28 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//Generic curse auras
|
||||||
|
else if ((threatApi == ApiType.Attach && (topStack.isCurse() || "Curse".equals(topStack.getParam("AILogic"))))) {
|
||||||
|
AiController aic = aiPlayer.isAI() ? ((PlayerControllerAi)aiPlayer.getController()).getAi() : null;
|
||||||
|
boolean enableCurseAuraRemoval = aic != null ? aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE) : false;
|
||||||
|
if (enableCurseAuraRemoval) {
|
||||||
|
for (final Object o : objects) {
|
||||||
|
if (o instanceof Card) {
|
||||||
|
final Card c = (Card) o;
|
||||||
|
// give Shroud to targeted creatures
|
||||||
|
if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll && tgt == null) && !grantShroud) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (saviourApi == ApiType.Protection) {
|
||||||
|
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
threatened.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Iterables.addAll(threatened, ComputerUtil.predictThreatenedObjects(aiPlayer, saviour, topStack.getSubAbility()));
|
Iterables.addAll(threatened, ComputerUtil.predictThreatenedObjects(aiPlayer, saviour, topStack.getSubAbility()));
|
||||||
return threatened;
|
return threatened;
|
||||||
@@ -2788,7 +2810,17 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this countertypes has no effect
|
// this countertypes has no effect
|
||||||
public static boolean isUselessCounter(CounterType type) {
|
public static boolean isUselessCounter(CounterType type, Card c) {
|
||||||
|
|
||||||
|
// Quest counter on a card without MaxQuestEffect are useless
|
||||||
|
if (type.is(CounterEnumType.QUEST)) {
|
||||||
|
int e = 0;
|
||||||
|
if ( c.hasSVar("MaxQuestEffect")) {
|
||||||
|
e = Integer.parseInt(c.getSVar("MaxQuestEffect"));
|
||||||
|
}
|
||||||
|
return c.getCounters(type) > e;
|
||||||
|
}
|
||||||
|
|
||||||
return type.is(CounterEnumType.AWAKENING) || type.is(CounterEnumType.MANIFESTATION) || type.is(CounterEnumType.PETRIFICATION)
|
return type.is(CounterEnumType.AWAKENING) || type.is(CounterEnumType.MANIFESTATION) || type.is(CounterEnumType.PETRIFICATION)
|
||||||
|| type.is(CounterEnumType.TRAINING);
|
|| type.is(CounterEnumType.TRAINING);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1856,7 +1856,7 @@ public class ComputerUtilCard {
|
|||||||
|
|
||||||
public static AiPlayDecision checkNeedsToPlayReqs(final Card card, final SpellAbility sa) {
|
public static AiPlayDecision checkNeedsToPlayReqs(final Card card, final SpellAbility sa) {
|
||||||
Game game = card.getGame();
|
Game game = card.getGame();
|
||||||
boolean isRightSplit = sa != null && sa.isRightSplit();
|
boolean isRightSplit = sa != null && sa.getCardState() != null;
|
||||||
String needsToPlayName = isRightSplit ? "SplitNeedsToPlay" : "NeedsToPlay";
|
String needsToPlayName = isRightSplit ? "SplitNeedsToPlay" : "NeedsToPlay";
|
||||||
String needsToPlayVarName = isRightSplit ? "SplitNeedsToPlayVar" : "NeedsToPlayVar";
|
String needsToPlayVarName = isRightSplit ? "SplitNeedsToPlayVar" : "NeedsToPlayVar";
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import forge.game.player.Player;
|
|||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementLayer;
|
import forge.game.replacement.ReplacementLayer;
|
||||||
import forge.game.replacement.ReplacementType;
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.AbilityActivated;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
@@ -1011,7 +1010,7 @@ public class ComputerUtilCombat {
|
|||||||
return power;
|
return power;
|
||||||
}
|
}
|
||||||
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated)) {
|
if (!ability.isActivatedAbility()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
|
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
|
||||||
@@ -1145,7 +1144,7 @@ public class ComputerUtilCombat {
|
|||||||
return toughness;
|
return toughness;
|
||||||
}
|
}
|
||||||
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated)) {
|
if (!ability.isActivatedAbility()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1368,7 +1367,7 @@ public class ComputerUtilCombat {
|
|||||||
return power;
|
return power;
|
||||||
}
|
}
|
||||||
for (SpellAbility ability : attacker.getAllSpellAbilities()) {
|
for (SpellAbility ability : attacker.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated)) {
|
if (!ability.isActivatedAbility()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
|
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
|
||||||
@@ -1591,7 +1590,7 @@ public class ComputerUtilCombat {
|
|||||||
return toughness;
|
return toughness;
|
||||||
}
|
}
|
||||||
for (SpellAbility ability : attacker.getAllSpellAbilities()) {
|
for (SpellAbility ability : attacker.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated)) {
|
if (!ability.isActivatedAbility()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2445,7 +2444,7 @@ public class ComputerUtilCombat {
|
|||||||
final Player controller = combatant.getController();
|
final Player controller = combatant.getController();
|
||||||
for (Card c : controller.getCardsIn(ZoneType.Battlefield)) {
|
for (Card c : controller.getCardsIn(ZoneType.Battlefield)) {
|
||||||
for (SpellAbility ability : c.getAllSpellAbilities()) {
|
for (SpellAbility ability : c.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated)) {
|
if (!ability.isActivatedAbility()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ability.getApi() != ApiType.Pump) {
|
if (ability.getApi() != ApiType.Pump) {
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import forge.game.zone.ZoneType;
|
|||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -45,7 +47,7 @@ public class ComputerUtilCost {
|
|||||||
final CostPutCounter addCounter = (CostPutCounter) part;
|
final CostPutCounter addCounter = (CostPutCounter) part;
|
||||||
final CounterType type = addCounter.getCounter();
|
final CounterType type = addCounter.getCounter();
|
||||||
|
|
||||||
if (type.equals(CounterEnumType.M1M1)) {
|
if (type.is(CounterEnumType.M1M1)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,9 +55,6 @@ public class ComputerUtilCost {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkRemoveCounterCost(final Cost cost, final Card source) {
|
|
||||||
return checkRemoveCounterCost(cost, source, null);
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Check remove counter cost.
|
* Check remove counter cost.
|
||||||
*
|
*
|
||||||
@@ -69,13 +68,14 @@ public class ComputerUtilCost {
|
|||||||
if (cost == null) {
|
if (cost == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa);
|
||||||
for (final CostPart part : cost.getCostParts()) {
|
for (final CostPart part : cost.getCostParts()) {
|
||||||
if (part instanceof CostRemoveCounter) {
|
if (part instanceof CostRemoveCounter) {
|
||||||
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
|
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
|
||||||
|
|
||||||
final CounterType type = remCounter.counter;
|
final CounterType type = remCounter.counter;
|
||||||
if (!part.payCostFromSource()) {
|
if (!part.payCostFromSource()) {
|
||||||
if (CounterEnumType.P1P1.equals(type)) {
|
if (type.is(CounterEnumType.P1P1)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -88,17 +88,15 @@ public class ComputerUtilCost {
|
|||||||
|
|
||||||
// Remove X counters - set ChosenX to max possible value here, the SAs should correct that
|
// Remove X counters - set ChosenX to max possible value here, the SAs should correct that
|
||||||
// value later as the AI decides what to do (in checkApiLogic / checkAiLogic)
|
// value later as the AI decides what to do (in checkApiLogic / checkAiLogic)
|
||||||
if (sa != null && sa.hasSVar(remCounter.getAmount())) {
|
if (sa.hasSVar(remCounter.getAmount())) {
|
||||||
final String sVar = sa.getSVar(remCounter.getAmount());
|
final String sVar = sa.getSVar(remCounter.getAmount());
|
||||||
if (sVar.equals("XChoice") && !sa.hasSVar("ChosenX")) {
|
if (sVar.equals("Count$xPaid") && sa.hasSVar("PayX")) {
|
||||||
sa.setSVar("ChosenX", String.valueOf(source.getCounters(type)));
|
sa.setSVar("PayX", Integer.toString(Math.min(Integer.valueOf(sa.getSVar("PayX")), source.getCounters(type))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the sa what the PaymentDecision is.
|
|
||||||
// ignore Loyality abilities with Zero as Cost
|
// ignore Loyality abilities with Zero as Cost
|
||||||
if (sa != null && !CounterEnumType.LOYALTY.equals(type)) {
|
if (!type.is(CounterEnumType.LOYALTY)) {
|
||||||
final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa);
|
|
||||||
PaymentDecision pay = decision.visit(remCounter);
|
PaymentDecision pay = decision.visit(remCounter);
|
||||||
if (pay == null || pay.c <= 0) {
|
if (pay == null || pay.c <= 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -106,19 +104,15 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//don't kill the creature
|
//don't kill the creature
|
||||||
if (CounterEnumType.P1P1.equals(type) && source.getLethalDamage() <= 1
|
if (type.is(CounterEnumType.P1P1) && source.getLethalDamage() <= 1
|
||||||
&& !source.hasKeyword(Keyword.UNDYING)) {
|
&& !source.hasKeyword(Keyword.UNDYING)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (part instanceof CostRemoveAnyCounter) {
|
} else if (part instanceof CostRemoveAnyCounter) {
|
||||||
if (sa != null) {
|
|
||||||
final CostRemoveAnyCounter remCounter = (CostRemoveAnyCounter) part;
|
final CostRemoveAnyCounter remCounter = (CostRemoveAnyCounter) part;
|
||||||
|
|
||||||
PaymentDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa).visit(remCounter);
|
PaymentDecision pay = decision.visit(remCounter);
|
||||||
return decision != null;
|
return pay != null;
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -619,7 +613,8 @@ public class ComputerUtilCost {
|
|||||||
if (combat.getAttackers().isEmpty()) {
|
if (combat.getAttackers().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if ("nonToken".equals(aiLogic) && AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0).isToken()) {
|
} else if ("nonToken".equals(aiLogic) && !AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).isEmpty()
|
||||||
|
&& AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0).isToken()) {
|
||||||
return false;
|
return false;
|
||||||
} else if ("LowPriority".equals(aiLogic) && MyRandom.getRandom().nextInt(100) < 67) {
|
} else if ("LowPriority".equals(aiLogic) && MyRandom.getRandom().nextInt(100) < 67) {
|
||||||
return false;
|
return false;
|
||||||
@@ -676,4 +671,61 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getMaxXValue(SpellAbility sa, Player ai) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final Cost abCost = sa.getPayCosts();
|
||||||
|
if (abCost == null || !abCost.hasXInAnyCostPart()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer val = null;
|
||||||
|
|
||||||
|
if (sa.costHasManaX()) {
|
||||||
|
val = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
// if announce is used as min targets, check what the max possible number would be
|
||||||
|
if ("X".equals(sa.getTargetRestrictions().getMinTargets())) {
|
||||||
|
val = ObjectUtils.min(val, CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("AIMaxTgtsCount")) {
|
||||||
|
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified
|
||||||
|
// TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable?
|
||||||
|
val = ObjectUtils.min(val, AbilityUtils.calculateAmount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount"), sa));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val = ObjectUtils.min(val, abCost.getMaxForNonManaX(sa, ai));
|
||||||
|
|
||||||
|
if (val != null && val > 0) {
|
||||||
|
// filter cost parts for preferences, don't choose X > than possible preferences
|
||||||
|
for (final CostPart part : abCost.getCostParts()) {
|
||||||
|
if (part instanceof CostSacrifice) {
|
||||||
|
if (part.payCostFromSource()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!part.getAmount().equals("X")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), source.getController(), source, null);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
while (count < val) {
|
||||||
|
Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList);
|
||||||
|
if (prefCard == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
typeList.remove(prefCard);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
val = ObjectUtils.min(val, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ObjectUtils.defaultIfNull(val, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.*;
|
import com.google.common.collect.*;
|
||||||
import forge.ai.ability.AnimateAi;
|
import forge.ai.ability.AnimateAi;
|
||||||
import forge.card.CardStateName;
|
|
||||||
import forge.card.ColorSet;
|
import forge.card.ColorSet;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.card.mana.ManaAtom;
|
import forge.card.mana.ManaAtom;
|
||||||
@@ -119,10 +118,6 @@ public class ComputerUtilMana {
|
|||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sortManaAbilities(final Multimap<ManaCostShard, SpellAbility> manaAbilityMap) {
|
|
||||||
sortManaAbilities(manaAbilityMap, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void sortManaAbilities(final Multimap<ManaCostShard, SpellAbility> manaAbilityMap, final SpellAbility sa) {
|
private static void sortManaAbilities(final Multimap<ManaCostShard, SpellAbility> manaAbilityMap, final SpellAbility sa) {
|
||||||
final Map<Card, Integer> manaCardMap = Maps.newHashMap();
|
final Map<Card, Integer> manaCardMap = Maps.newHashMap();
|
||||||
final List<Card> orderedCards = Lists.newArrayList();
|
final List<Card> orderedCards = Lists.newArrayList();
|
||||||
@@ -484,7 +479,7 @@ public class ComputerUtilMana {
|
|||||||
SpellAbility saPayment = saList.isEmpty() ? null : chooseManaAbility(cost, sa, ai, toPay, saList, checkPlayable || !test);
|
SpellAbility saPayment = saList.isEmpty() ? null : chooseManaAbility(cost, sa, ai, toPay, saList, checkPlayable || !test);
|
||||||
|
|
||||||
if (saPayment != null && ComputerUtilCost.isSacrificeSelfCost(saPayment.getPayCosts())) {
|
if (saPayment != null && ComputerUtilCost.isSacrificeSelfCost(saPayment.getPayCosts())) {
|
||||||
if (sa.getTargets() != null && sa.getTargets().isTargeting(saPayment.getHostCard())) {
|
if (sa.getTargets() != null && sa.getTargets().contains(saPayment.getHostCard())) {
|
||||||
saExcludeList.add(saPayment); // not a good idea to sac a card that you're targeting with the SA you're paying for
|
saExcludeList.add(saPayment); // not a good idea to sac a card that you're targeting with the SA you're paying for
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -623,13 +618,6 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: manaSpentToPay shouldn't be cleared here, since it needs to remain
|
|
||||||
// on the SpellAbility in order for effects that check mana spent cost to work.
|
|
||||||
|
|
||||||
sa.getHostCard().setColorsPaid(cost.getColorsPaid());
|
|
||||||
// if (sa instanceof Spell_Permanent) // should probably add this
|
|
||||||
sa.getHostCard().setSunburstValue(cost.getSunburst());
|
|
||||||
|
|
||||||
if (test) {
|
if (test) {
|
||||||
refundMana(manaSpentToPay, ai, sa);
|
refundMana(manaSpentToPay, ai, sa);
|
||||||
resetPayment(paymentList);
|
resetPayment(paymentList);
|
||||||
@@ -1174,7 +1162,7 @@ public class ComputerUtilMana {
|
|||||||
* @param extraMana extraMana
|
* @param extraMana extraMana
|
||||||
* @return ManaCost
|
* @return ManaCost
|
||||||
*/
|
*/
|
||||||
static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) {
|
public static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) {
|
||||||
Card card = sa.getHostCard();
|
Card card = sa.getHostCard();
|
||||||
ZoneType castFromBackup = null;
|
ZoneType castFromBackup = null;
|
||||||
if (test && sa.isSpell()) {
|
if (test && sa.isSpell()) {
|
||||||
@@ -1201,11 +1189,11 @@ public class ComputerUtilMana {
|
|||||||
} else {
|
} else {
|
||||||
// For Count$xPaid set PayX in the AFs then use that here
|
// For Count$xPaid set PayX in the AFs then use that here
|
||||||
// Else calculate it as appropriate.
|
// Else calculate it as appropriate.
|
||||||
final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
|
final String xSvar = sa.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
|
||||||
if (!sa.getSVar(xSvar).isEmpty() || card.hasSVar(xSvar) || card.getState(CardStateName.Original).hasSVar(xSvar)) {
|
if (sa.hasSVar(xSvar)) {
|
||||||
if (xSvar.equals("PayX") && (card.hasSVar(xSvar) || card.getState(CardStateName.Original).hasSVar(xSvar))) {
|
if (xSvar.equals("PayX")) {
|
||||||
// X SVar may end up being an empty string when copying a spell with no cost (e.g. Jhoira Avatar)
|
// X SVar may end up being an empty string when copying a spell with no cost (e.g. Jhoira Avatar)
|
||||||
String xValue = card.hasSVar(xSvar) ? card.getSVar(xSvar) : card.getState(CardStateName.Original).getSVar(xSvar);
|
String xValue = sa.getSVar(xSvar);
|
||||||
manaToAdd = xValue.isEmpty() ? 0 : Integer.parseInt(xValue) * cost.getXcounter(); // X
|
manaToAdd = xValue.isEmpty() ? 0 : Integer.parseInt(xValue) * cost.getXcounter(); // X
|
||||||
} else {
|
} else {
|
||||||
manaToAdd = AbilityUtils.calculateAmount(card, xSvar, sa) * cost.getXcounter();
|
manaToAdd = AbilityUtils.calculateAmount(card, xSvar, sa) * cost.getXcounter();
|
||||||
@@ -1213,9 +1201,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String manaXColor = sa.getParam("XColor");
|
cost.increaseShard(ManaCostShard.parseNonGeneric(sa.getParamOrDefault("XColor", "1")), manaToAdd);
|
||||||
ManaCostShard shardToGrow = ManaCostShard.parseNonGeneric(manaXColor == null ? "1" : manaXColor);
|
|
||||||
cost.increaseShard(shardToGrow, manaToAdd);
|
|
||||||
|
|
||||||
if (!test) {
|
if (!test) {
|
||||||
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
||||||
@@ -1641,31 +1627,4 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
return convoke;
|
return convoke;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int determineMaxAffordableX(Player ai, SpellAbility sa) {
|
|
||||||
if (sa.getPayCosts().getCostMana() == null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int numTgts = 0;
|
|
||||||
int numX = sa.getPayCosts().getCostMana().getAmountOfX();
|
|
||||||
|
|
||||||
if (numX == 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int testX = 1;
|
|
||||||
while (testX <= 100) {
|
|
||||||
if (ComputerUtilMana.canPayManaCost(sa, ai, testX)) {
|
|
||||||
numTgts++;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
testX++;
|
|
||||||
}
|
|
||||||
|
|
||||||
numTgts /= numX;
|
|
||||||
|
|
||||||
return numTgts;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ public abstract class GameState {
|
|||||||
private final Map<Card, List<String>> cardToRememberedId = new HashMap<>();
|
private final Map<Card, List<String>> cardToRememberedId = new HashMap<>();
|
||||||
private final Map<Card, List<String>> cardToImprintedId = new HashMap<>();
|
private final Map<Card, List<String>> cardToImprintedId = new HashMap<>();
|
||||||
private final Map<Card, String> cardToNamedCard = new HashMap<>();
|
private final Map<Card, String> cardToNamedCard = new HashMap<>();
|
||||||
|
private final Map<Card, String> cardToNamedCard2 = new HashMap<>();
|
||||||
private final Map<Card, String> cardToExiledWithId = new HashMap<>();
|
private final Map<Card, String> cardToExiledWithId = new HashMap<>();
|
||||||
private final Map<Card, Card> cardAttackMap = new HashMap<>();
|
private final Map<Card, Card> cardAttackMap = new HashMap<>();
|
||||||
|
|
||||||
@@ -325,6 +326,9 @@ public abstract class GameState {
|
|||||||
if (!c.getNamedCard().isEmpty()) {
|
if (!c.getNamedCard().isEmpty()) {
|
||||||
newText.append("|NamedCard:").append(c.getNamedCard());
|
newText.append("|NamedCard:").append(c.getNamedCard());
|
||||||
}
|
}
|
||||||
|
if (!c.getNamedCard2().isEmpty()) {
|
||||||
|
newText.append("|NamedCard2:").append(c.getNamedCard2());
|
||||||
|
}
|
||||||
|
|
||||||
List<String> chosenCardIds = Lists.newArrayList();
|
List<String> chosenCardIds = Lists.newArrayList();
|
||||||
for (Object obj : c.getChosenCards()) {
|
for (Object obj : c.getChosenCards()) {
|
||||||
@@ -783,6 +787,7 @@ public abstract class GameState {
|
|||||||
|
|
||||||
Card exiledWith = idToCard.get(Integer.parseInt(id));
|
Card exiledWith = idToCard.get(Integer.parseInt(id));
|
||||||
c.setExiledWith(exiledWith);
|
c.setExiledWith(exiledWith);
|
||||||
|
c.setExiledBy(exiledWith.getController());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,7 +824,7 @@ public abstract class GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("RememberTargets")) {
|
if (sa.hasParam("RememberTargets")) {
|
||||||
for (final GameObject o : sa.getTargets().getTargets()) {
|
for (final GameObject o : sa.getTargets()) {
|
||||||
sa.getHostCard().addRemembered(o);
|
sa.getHostCard().addRemembered(o);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1056,6 +1061,12 @@ public abstract class GameState {
|
|||||||
c.setNamedCard(entry.getValue());
|
c.setNamedCard(entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Named card 2
|
||||||
|
for (Entry<Card,String> entry : cardToNamedCard2.entrySet()) {
|
||||||
|
Card c = entry.getKey();
|
||||||
|
c.setNamedCard2(entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
// Chosen cards
|
// Chosen cards
|
||||||
for (Entry<Card, CardCollection> entry : cardToChosenCards.entrySet()) {
|
for (Entry<Card, CardCollection> entry : cardToChosenCards.entrySet()) {
|
||||||
Card c = entry.getKey();
|
Card c = entry.getKey();
|
||||||
@@ -1241,6 +1252,7 @@ public abstract class GameState {
|
|||||||
saAdventure.setActivatingPlayer(c.getOwner());
|
saAdventure.setActivatingPlayer(c.getOwner());
|
||||||
saAdventure.resolve();
|
saAdventure.resolve();
|
||||||
c.setExiledWith(c); // This seems to be the way it's set up internally. Potentially not needed here?
|
c.setExiledWith(c); // This seems to be the way it's set up internally. Potentially not needed here?
|
||||||
|
c.setExiledBy(c.getController());
|
||||||
} else if (info.startsWith("IsCommander")) {
|
} else if (info.startsWith("IsCommander")) {
|
||||||
// TODO: This doesn't seem to properly restore the ability to play the commander. Why?
|
// TODO: This doesn't seem to properly restore the ability to play the commander. Why?
|
||||||
c.setCommander(true);
|
c.setCommander(true);
|
||||||
@@ -1283,6 +1295,8 @@ public abstract class GameState {
|
|||||||
cardToChosenCards.put(c, chosen);
|
cardToChosenCards.put(c, chosen);
|
||||||
} else if (info.startsWith("NamedCard:")) {
|
} else if (info.startsWith("NamedCard:")) {
|
||||||
cardToNamedCard.put(c, info.substring(info.indexOf(':') + 1));
|
cardToNamedCard.put(c, info.substring(info.indexOf(':') + 1));
|
||||||
|
} else if (info.startsWith("NamedCard2:")) {
|
||||||
|
cardToNamedCard2.put(c, info.substring(info.indexOf(':') + 1));
|
||||||
} else if (info.startsWith("ExecuteScript:")) {
|
} else if (info.startsWith("ExecuteScript:")) {
|
||||||
cardToScript.put(c, info.substring(info.indexOf(':') + 1));
|
cardToScript.put(c, info.substring(info.indexOf(':') + 1));
|
||||||
} else if (info.startsWith("RememberedCards:")) {
|
} else if (info.startsWith("RememberedCards:")) {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer announceRequirements(SpellAbility ability, String announce, boolean allowZero) {
|
public Integer announceRequirements(SpellAbility ability, String announce) {
|
||||||
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
|
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
|
||||||
if (ability.getApi() != null) {
|
if (ability.getApi() != null) {
|
||||||
switch (ability.getApi()) {
|
switch (ability.getApi()) {
|
||||||
@@ -267,7 +267,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Player chooseStartingPlayer(boolean isFirstGame) {
|
public Player chooseStartingPlayer(boolean isFirstgame) {
|
||||||
return this.player; // AI is brave :)
|
return this.player; // AI is brave :)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,7 +364,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
if (destinationZone == ZoneType.Graveyard) {
|
if (destinationZone == ZoneType.Graveyard) {
|
||||||
// In presence of Volrath's Shapeshifter in deck, try to place the best creature on top of the graveyard
|
// In presence of Volrath's Shapeshifter in deck, try to place the best creature on top of the graveyard
|
||||||
if (!CardLists.filter(game.getCardsInGame(), new Predicate<Card>() {
|
if (!CardLists.filter(getGame().getCardsInGame(), new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Card card) {
|
public boolean apply(Card card) {
|
||||||
// need a custom predicate here since Volrath's Shapeshifter may have a different name OTB
|
// need a custom predicate here since Volrath's Shapeshifter may have a different name OTB
|
||||||
@@ -485,7 +485,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) {
|
public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) {
|
||||||
if (canSetupTargets)
|
if (canSetupTargets)
|
||||||
brains.doTrigger(effectSA, true); // first parameter does not matter, since return value won't be used
|
brains.doTrigger(effectSA, true); // first parameter does not matter, since return value won't be used
|
||||||
ComputerUtil.playNoStack(player, effectSA, game);
|
ComputerUtil.playNoStack(player, effectSA, getGame());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -522,7 +522,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
chosen = validTypes.iterator().next();
|
chosen = validTypes.iterator().next();
|
||||||
System.err.println("AI has no idea how to choose " + kindOfType +", defaulting to arbitrary element: chosen");
|
System.err.println("AI has no idea how to choose " + kindOfType +", defaulting to arbitrary element: chosen");
|
||||||
}
|
}
|
||||||
game.getAction().nofityOfValue(sa, player, chosen, player);
|
getGame().getAction().nofityOfValue(sa, player, chosen, player);
|
||||||
return chosen;
|
return chosen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,10 +617,10 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
if (sa instanceof LandAbility) {
|
if (sa instanceof LandAbility) {
|
||||||
if (sa.canPlay()) {
|
if (sa.canPlay()) {
|
||||||
sa.resolve();
|
sa.resolve();
|
||||||
game.updateLastStateForCard(sa.getHostCard());
|
getGame().updateLastStateForCard(sa.getHostCard());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ComputerUtil.handlePlayingSpellAbility(player, sa, game);
|
ComputerUtil.handlePlayingSpellAbility(player, sa, getGame());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,7 +659,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
// - End of hack for Exile a card from library Cumulative Upkeep -
|
// - End of hack for Exile a card from library Cumulative Upkeep -
|
||||||
|
|
||||||
if (ComputerUtilCost.canPayCost(ability, c.getController())) {
|
if (ComputerUtilCost.canPayCost(ability, c.getController())) {
|
||||||
ComputerUtil.playNoStack(c.getController(), ability, game);
|
ComputerUtil.playNoStack(c.getController(), ability, getGame());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -800,8 +800,8 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat) {
|
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> possible, int min, int num, boolean allowRepeat) {
|
||||||
List<AbilitySub> result = brains.chooseModeForAbility(sa, min, num, allowRepeat);
|
List<AbilitySub> result = brains.chooseModeForAbility(sa, possible, min, num, allowRepeat);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -889,8 +889,8 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
public String chooseProtectionType(String string, SpellAbility sa, List<String> choices) {
|
public String chooseProtectionType(String string, SpellAbility sa, List<String> choices) {
|
||||||
String choice = choices.get(0);
|
String choice = choices.get(0);
|
||||||
SpellAbility hostsa = null; //for Protect sub-ability
|
SpellAbility hostsa = null; //for Protect sub-ability
|
||||||
if (game.stack.size() > 1) {
|
if (getGame().stack.size() > 1) {
|
||||||
for (SpellAbilityStackInstance si : game.getStack()) {
|
for (SpellAbilityStackInstance si : getGame().getStack()) {
|
||||||
SpellAbility spell = si.getSpellAbility(true);
|
SpellAbility spell = si.getSpellAbility(true);
|
||||||
if (sa != spell && sa.getHostCard() != spell.getHostCard()) {
|
if (sa != spell && sa.getHostCard() != spell.getHostCard()) {
|
||||||
String s = ProtectAi.toProtectFrom(spell.getHostCard(), sa);
|
String s = ProtectAi.toProtectFrom(spell.getHostCard(), sa);
|
||||||
@@ -901,10 +901,10 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final Combat combat = game.getCombat();
|
final Combat combat = getGame().getCombat();
|
||||||
if (combat != null) {
|
if (combat != null) {
|
||||||
if (game.stack.size() == 1) {
|
if (getGame().stack.size() == 1) {
|
||||||
SpellAbility topstack = game.stack.peekAbility();
|
SpellAbility topstack = getGame().stack.peekAbility();
|
||||||
if (topstack.getSubAbility() == sa) {
|
if (topstack.getSubAbility() == sa) {
|
||||||
hostsa = topstack;
|
hostsa = topstack;
|
||||||
}
|
}
|
||||||
@@ -927,7 +927,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = getGame().getPhaseHandler();
|
||||||
if (ph.getPlayerTurn() == sa.getActivatingPlayer() && ph.getPhase() == PhaseType.MAIN1 && sa.getTargetCard() != null) {
|
if (ph.getPlayerTurn() == sa.getActivatingPlayer() && ph.getPhase() == PhaseType.MAIN1 && sa.getTargetCard() != null) {
|
||||||
AiAttackController aiAtk = new AiAttackController(sa.getActivatingPlayer(), sa.getTargetCard());
|
AiAttackController aiAtk = new AiAttackController(sa.getActivatingPlayer(), sa.getTargetCard());
|
||||||
String s = aiAtk.toProtectAttacker(sa);
|
String s = aiAtk.toProtectAttacker(sa);
|
||||||
@@ -942,7 +942,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
list.addAll(opp.getCreaturesInPlay());
|
list.addAll(opp.getCreaturesInPlay());
|
||||||
}
|
}
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
list = CardLists.filterControlledBy(game.getCardsInGame(), player.getOpponents());
|
list = CardLists.filterControlledBy(getGame().getCardsInGame(), player.getOpponents());
|
||||||
}
|
}
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
choice = ComputerUtilCard.getMostProminentColor(list);
|
choice = ComputerUtilCard.getMostProminentColor(list);
|
||||||
@@ -957,11 +957,9 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
|
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
|
||||||
emptyAbility.setActivatingPlayer(player);
|
emptyAbility.setActivatingPlayer(player);
|
||||||
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
||||||
for (String sVar : sa.getSVars()) {
|
emptyAbility.setSVars(sa.getSVars());
|
||||||
emptyAbility.setSVar(sVar, sa.getSVar(sVar));
|
|
||||||
}
|
|
||||||
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
||||||
ComputerUtil.playNoStack(player, emptyAbility, game); // AI needs something to resolve to pay that cost
|
ComputerUtil.playNoStack(player, emptyAbility, getGame()); // AI needs something to resolve to pay that cost
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -970,8 +968,26 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
@Override
|
@Override
|
||||||
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
|
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
|
||||||
for (final SpellAbility sa : getAi().orderPlaySa(activePlayerSAs)) {
|
for (final SpellAbility sa : getAi().orderPlaySa(activePlayerSAs)) {
|
||||||
if (prepareSingleSa(sa.getHostCard(),sa,true)) {
|
if (sa.isTrigger()) {
|
||||||
ComputerUtil.playStack(sa, player, game);
|
if (prepareSingleSa(sa.getHostCard(), sa, true)) {
|
||||||
|
ComputerUtil.playStack(sa, player, getGame());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sa.isCopied()) {
|
||||||
|
if (sa.isSpell()) {
|
||||||
|
player.getGame().getStackZone().add(sa.getHostCard());
|
||||||
|
}
|
||||||
|
// TODO check if static abilities needs to be run for things affecting the copy?
|
||||||
|
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
|
||||||
|
// if targets can't be done, remove copy from existence
|
||||||
|
if (sa.isSpell()) {
|
||||||
|
sa.getHostCard().ceaseToExist();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// need finally add the new spell to the stack
|
||||||
|
player.getGame().getStack().add(sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -989,7 +1005,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
@Override
|
@Override
|
||||||
public void playTrigger(Card host, WrappedAbility wrapperAbility, boolean isMandatory) {
|
public void playTrigger(Card host, WrappedAbility wrapperAbility, boolean isMandatory) {
|
||||||
if (prepareSingleSa(host, wrapperAbility, isMandatory)) {
|
if (prepareSingleSa(host, wrapperAbility, isMandatory)) {
|
||||||
ComputerUtil.playNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility, game);
|
ComputerUtil.playNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility, getGame());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1001,9 +1017,9 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
Spell spell = (Spell) tgtSA;
|
Spell spell = (Spell) tgtSA;
|
||||||
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
|
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
|
||||||
if (noManaCost) {
|
if (noManaCost) {
|
||||||
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, game);
|
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
|
||||||
} else {
|
} else {
|
||||||
return ComputerUtil.playStack(tgtSA, player, game);
|
return ComputerUtil.playStack(tgtSA, player, getGame());
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
return false; // didn't play spell
|
return false; // didn't play spell
|
||||||
@@ -1149,7 +1165,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return SpecialCardAi.CursedScroll.chooseCard(player, sa);
|
return SpecialCardAi.CursedScroll.chooseCard(player, sa);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
CardCollectionView list = CardLists.filterControlledBy(game.getCardsInGame(), player.getOpponents());
|
CardCollectionView list = CardLists.filterControlledBy(getGame().getCardsInGame(), player.getOpponents());
|
||||||
list = CardLists.filter(list, Predicates.not(Presets.LANDS));
|
list = CardLists.filter(list, Predicates.not(Presets.LANDS));
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
return list.get(0).getName();
|
return list.get(0).getName();
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
import forge.ai.ability.TokenAi;
|
import forge.ai.ability.TokenAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -12,8 +16,6 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -31,17 +33,34 @@ public class SpecialAiLogic {
|
|||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
Game game = source.getGame();
|
Game game = source.getGame();
|
||||||
PhaseHandler ph = game.getPhaseHandler();
|
PhaseHandler ph = game.getPhaseHandler();
|
||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
boolean isDestroy = ApiType.Destroy.equals(sa.getApi());
|
||||||
|
SpellAbility tokenSA = sa.findSubAbilityByType(ApiType.Token);
|
||||||
|
if (tokenSA == null) {
|
||||||
|
// Used wrong AI logic?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
CardCollection listOpp = CardLists.getValidCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source, sa);
|
List<Card> targetable = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa);
|
||||||
listOpp = CardLists.getTargetableCards(listOpp, sa);
|
|
||||||
|
|
||||||
Card choice = ComputerUtilCard.getMostExpensivePermanentAI(listOpp);
|
CardCollection listOpp = CardLists.filterControlledBy(targetable, ai.getOpponents());
|
||||||
|
if (isDestroy) {
|
||||||
|
listOpp = CardLists.getNotKeyword(listOpp, Keyword.INDESTRUCTIBLE);
|
||||||
|
// TODO add handling for cards like targeting dies
|
||||||
|
}
|
||||||
|
|
||||||
final Card token = choice != null ? TokenAi.spawnToken(choice.getController(), sa.getSubAbility()) : null;
|
Card choice = null;
|
||||||
if (token == null || !token.isCreature() || token.getNetToughness() < 1) {
|
if (!listOpp.isEmpty()) {
|
||||||
return true; // becomes Terminate
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(listOpp);
|
||||||
} else if (choice != null && choice.isPlaneswalker()) {
|
// can choice even be null?
|
||||||
|
|
||||||
|
if (choice != null) {
|
||||||
|
final Card token = TokenAi.spawnToken(choice.getController(), tokenSA);
|
||||||
|
if (!token.isCreature() || token.getNetToughness() < 1) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (choice.isPlaneswalker()) {
|
||||||
if (choice.getCurrentLoyalty() * 35 > ComputerUtilCard.evaluateCreature(token)) {
|
if (choice.getCurrentLoyalty() * 35 > ComputerUtilCard.evaluateCreature(token)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
@@ -49,42 +68,48 @@ public class SpecialAiLogic {
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
boolean hasOppTarget = true;
|
if ((!choice.isCreature() || choice.isTapped()) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && ph.isPlayerTurn(ai) // prevent surprise combatant
|
||||||
if (choice != null
|
|
||||||
&& ((!choice.isCreature() || choice.isTapped()) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && ph.getPlayerTurn() == ai) // prevent surprise combatant
|
|
||||||
|| ComputerUtilCard.evaluateCreature(choice) < 1.5 * ComputerUtilCard.evaluateCreature(token)) {
|
|| ComputerUtilCard.evaluateCreature(choice) < 1.5 * ComputerUtilCard.evaluateCreature(token)) {
|
||||||
|
choice = null;
|
||||||
hasOppTarget = false;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if we have anything we can upgrade
|
// See if we have anything we can upgrade
|
||||||
if (!hasOppTarget) {
|
if (choice == null) {
|
||||||
CardCollection listOwn = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source, sa);
|
CardCollection listOwn = CardLists.filterControlledBy(targetable, ai);
|
||||||
listOwn = CardLists.getTargetableCards(listOwn, sa);
|
final Card token = TokenAi.spawnToken(ai, tokenSA);
|
||||||
|
|
||||||
Card bestOwnCardToUpgrade = ComputerUtilCard.getWorstCreatureAI(CardLists.filter(listOwn, new Predicate<Card>() {
|
Card bestOwnCardToUpgrade = null;
|
||||||
|
if (isDestroy) {
|
||||||
|
// just choose any Indestructible
|
||||||
|
// TODO maybe filter something that doesn't like to be targeted, or does something benefit by targeting
|
||||||
|
bestOwnCardToUpgrade = Iterables.getFirst(CardLists.getKeyword(listOwn, Keyword.INDESTRUCTIBLE), null);
|
||||||
|
}
|
||||||
|
if (bestOwnCardToUpgrade == null) {
|
||||||
|
bestOwnCardToUpgrade = ComputerUtilCard.getWorstCreatureAI(CardLists.filter(listOwn, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Card card) {
|
public boolean apply(Card card) {
|
||||||
return card.isCreature() && (ComputerUtilCard.isUselessCreature(ai, card)
|
return card.isCreature() && (ComputerUtilCard.isUselessCreature(ai, card)
|
||||||
|| ComputerUtilCard.evaluateCreature(token) > 2 * ComputerUtilCard.evaluateCreature(card));
|
|| ComputerUtilCard.evaluateCreature(token) > 2 * ComputerUtilCard.evaluateCreature(card));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
if (bestOwnCardToUpgrade != null) {
|
if (bestOwnCardToUpgrade != null) {
|
||||||
if (ComputerUtilCard.isUselessCreature(ai, bestOwnCardToUpgrade) || (ph.getPhase().isAfter(PhaseType.COMBAT_END) || ph.getPlayerTurn() != ai)) {
|
if (ComputerUtilCard.isUselessCreature(ai, bestOwnCardToUpgrade) || (ph.getPhase().isAfter(PhaseType.COMBAT_END) || !ph.isPlayerTurn(ai))) {
|
||||||
sa.resetTargets();
|
choice = bestOwnCardToUpgrade;
|
||||||
sa.getTargets().add(bestOwnCardToUpgrade);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (choice != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasOppTarget;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A logic for cards that say "Sacrifice a creature: CARDNAME gets +X/+X until EOT"
|
// A logic for cards that say "Sacrifice a creature: CARDNAME gets +X/+X until EOT"
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.SpellAbilityPredicates;
|
||||||
import forge.game.spellability.SpellPermanent;
|
import forge.game.spellability.SpellPermanent;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
@@ -194,7 +195,7 @@ public class SpecialCardAi {
|
|||||||
sa.getTargets().add(worstCreat);
|
sa.getTargets().add(worstCreat);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getTargets().getNumTargeted() > 0;
|
return sa.getTargets().size() > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -875,7 +876,7 @@ public class SpecialCardAi {
|
|||||||
tokenSize = 11;
|
tokenSize = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
source.setSVar("PayX", Integer.toString(tokenSize));
|
sa.setSVar("PayX", Integer.toString(tokenSize));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1302,7 +1303,7 @@ public class SpecialCardAi {
|
|||||||
sa.getTargets().add(worstOwnCreat);
|
sa.getTargets().add(worstOwnCreat);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getTargets().getNumTargeted() > 0;
|
return sa.getTargets().size() > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1400,7 +1401,7 @@ public class SpecialCardAi {
|
|||||||
int x = -1, best = 0;
|
int x = -1, best = 0;
|
||||||
Card single = null;
|
Card single = null;
|
||||||
for (int i = 0; i < loyalty; i++) {
|
for (int i = 0; i < loyalty; i++) {
|
||||||
sa.setSVar("ChosenX", "Number$" + i);
|
sa.setXManaCostPaid(i);
|
||||||
oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
|
oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
|
||||||
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
|
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
|
||||||
computerType = AbilityUtils.filterListByType(ai.getCardsIn(origin), sa.getParam("ChangeType"), sa);
|
computerType = AbilityUtils.filterListByType(ai.getCardsIn(origin), sa.getParam("ChangeType"), sa);
|
||||||
@@ -1417,13 +1418,8 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
// check if +1 would be sufficient
|
// check if +1 would be sufficient
|
||||||
if (single != null) {
|
if (single != null) {
|
||||||
SpellAbility ugin_burn = null;
|
// TODO use better logic to find the right Deal Damage Effect?
|
||||||
for (final SpellAbility s : source.getSpellAbilities()) {
|
SpellAbility ugin_burn = Iterables.find(source.getSpellAbilities(), SpellAbilityPredicates.isApi(ApiType.DealDamage), null);
|
||||||
if (s.getApi() == ApiType.DealDamage) {
|
|
||||||
ugin_burn = s;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ugin_burn != null) {
|
if (ugin_burn != null) {
|
||||||
// basic logic copied from DamageDealAi::dealDamageChooseTgtC
|
// basic logic copied from DamageDealAi::dealDamageChooseTgtC
|
||||||
if (ugin_burn.canTarget(single)) {
|
if (ugin_burn.canTarget(single)) {
|
||||||
@@ -1434,17 +1430,18 @@ public class SpecialCardAi {
|
|||||||
if (can_kill) {
|
if (can_kill) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// simple check to burn player instead of exiling planeswalker
|
// simple check to burn player instead of exiling planeswalker
|
||||||
if (single.isPlaneswalker() && single.getCurrentLoyalty() <= 3) {
|
if (single.isPlaneswalker() && single.getCurrentLoyalty() <= 3) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (x == -1) {
|
if (x == -1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
sa.setSVar("ChosenX", "Number$" + x);
|
sa.setXManaCostPaid(x);
|
||||||
|
sa.setSVar("PayX", String.valueOf(x));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,16 +78,14 @@ public abstract class SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("AITgtBeforeCostEval")) {
|
if (!checkApiLogic(ai, sa)) {
|
||||||
// Cost payment requires a valid target to be specified, e.g. Quillmane Baku, so run the API logic first
|
return false;
|
||||||
// to set the target, then decide on paying costs (slower, so only use for cards where it matters)
|
|
||||||
return checkApiLogic(ai, sa) && (cost == null || willPayCosts(ai, sa, cost, source));
|
|
||||||
}
|
}
|
||||||
|
// needs to be after API logic because needs to check possible X Cost?
|
||||||
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
|
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return checkApiLogic(ai, sa);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
|
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
|
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
|
||||||
.put(ApiType.Goad, GoadAi.class)
|
.put(ApiType.Goad, GoadAi.class)
|
||||||
.put(ApiType.Haunt, HauntAi.class)
|
.put(ApiType.Haunt, HauntAi.class)
|
||||||
.put(ApiType.ImmediateTrigger, AlwaysPlayAi.class)
|
.put(ApiType.ImmediateTrigger, ImmediateTriggerAi.class)
|
||||||
.put(ApiType.Investigate, InvestigateAi.class)
|
.put(ApiType.Investigate, InvestigateAi.class)
|
||||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||||
.put(ApiType.LosesGame, GameLossAi.class)
|
.put(ApiType.LosesGame, GameLossAi.class)
|
||||||
@@ -142,7 +142,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.RevealHand, RevealHandAi.class)
|
.put(ApiType.RevealHand, RevealHandAi.class)
|
||||||
.put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class)
|
.put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class)
|
||||||
.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class)
|
.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class)
|
||||||
.put(ApiType.RunSVarAbility, AlwaysPlayAi.class)
|
.put(ApiType.RunChaos, AlwaysPlayAi.class)
|
||||||
.put(ApiType.Sacrifice, SacrificeAi.class)
|
.put(ApiType.Sacrifice, SacrificeAi.class)
|
||||||
.put(ApiType.SacrificeAll, SacrificeAllAi.class)
|
.put(ApiType.SacrificeAll, SacrificeAllAi.class)
|
||||||
.put(ApiType.Scry, ScryAi.class)
|
.put(ApiType.Scry, ScryAi.class)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import com.google.common.collect.Maps;
|
|||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityFactory;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
@@ -14,15 +13,10 @@ import forge.game.cost.CostPutCounter;
|
|||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
|
||||||
import forge.game.replacement.ReplacementHandler;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.staticability.StaticAbilityContinuous;
|
import forge.game.staticability.StaticAbilityContinuous;
|
||||||
import forge.game.staticability.StaticAbilityLayer;
|
import forge.game.staticability.StaticAbilityLayer;
|
||||||
import forge.game.trigger.Trigger;
|
|
||||||
import forge.game.trigger.TriggerHandler;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -43,15 +37,13 @@ import forge.game.ability.effects.AnimateEffectBase;
|
|||||||
public class AnimateAi extends SpellAbilityAi {
|
public class AnimateAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
if ("Attacking".equals(aiLogic)) { // Launch the Fleet
|
if ("Attacking".equals(aiLogic)) { // Launch the Fleet
|
||||||
if (ph.getPlayerTurn().isOpponentOf(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
if (ph.getPlayerTurn().isOpponentOf(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
List<Card> list = CardLists.getValidCards(ai.getCreaturesInPlay(), tgt.getValidTgts(), ai, source, sa);
|
List<Card> list = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
|
||||||
for (Card c : list) {
|
for (Card c : list) {
|
||||||
if (ComputerUtilCard.doesCreatureAttackAI(ai, c)) {
|
if (ComputerUtilCard.doesCreatureAttackAI(ai, c)) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
@@ -229,10 +221,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
} else if (sa.usesTargeting() && mandatory) {
|
} else if (sa.usesTargeting() && mandatory) {
|
||||||
// fallback if animate is mandatory
|
// fallback if animate is mandatory
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
List<Card> list = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa);
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
CardCollectionView list = aiPlayer.getGame().getCardsIn(tgt.getZone());
|
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), aiPlayer, source, sa);
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -251,19 +240,15 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
&& sa.getTargetRestrictions() != null
|
&& sa.getTargetRestrictions() != null
|
||||||
&& sa.getTargetRestrictions().getMinTargets(sa.getHostCard(), sa) == 0;
|
&& sa.getTargetRestrictions().getMinTargets(sa.getHostCard(), sa) == 0;
|
||||||
|
|
||||||
final CardType types = new CardType();
|
final CardType types = new CardType(true);
|
||||||
if (sa.hasParam("Types")) {
|
if (sa.hasParam("Types")) {
|
||||||
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
|
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// something is used for animate into creature
|
// something is used for animate into creature
|
||||||
if (types.isCreature()) {
|
if (types.isCreature()) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final Game game = ai.getGame();
|
||||||
final Card source = sa.getHostCard();
|
CardCollectionView list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
CardCollectionView list = ai.getGame().getCardsIn(tgt.getZone());
|
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source, sa);
|
|
||||||
// need to targetable
|
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
|
||||||
|
|
||||||
// Filter AI-specific targets if provided
|
// Filter AI-specific targets if provided
|
||||||
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
|
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
|
||||||
@@ -340,8 +325,18 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// select the worst of the best
|
// select the worst of the best
|
||||||
final Card worst = ComputerUtilCard.getWorstAI(maxList);
|
final Card worst = ComputerUtilCard.getWorstAI(maxList);
|
||||||
|
if (worst != null) {
|
||||||
|
if (worst.isLand()) {
|
||||||
|
// e.g. Clan Guildmage, make sure we're not using the same land we want to animate to activate the ability
|
||||||
|
this.holdAnimatedTillMain2(ai, worst);
|
||||||
|
if (!ComputerUtilMana.canPayManaCost(sa, ai, 0)) {
|
||||||
|
this.releaseHeldTillMain2(ai, worst);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
this.rememberAnimatedThisTurn(ai, worst);
|
this.rememberAnimatedThisTurn(ai, worst);
|
||||||
sa.getTargets().add(worst);
|
sa.getTargets().add(worst);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,12 +378,12 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final CardType types = new CardType();
|
final CardType types = new CardType(true);
|
||||||
if (sa.hasParam("Types")) {
|
if (sa.hasParam("Types")) {
|
||||||
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
|
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
|
||||||
}
|
}
|
||||||
|
|
||||||
final CardType removeTypes = new CardType();
|
final CardType removeTypes = new CardType(true);
|
||||||
if (sa.hasParam("RemoveTypes")) {
|
if (sa.hasParam("RemoveTypes")) {
|
||||||
removeTypes.addAll(Arrays.asList(sa.getParam("RemoveTypes").split(",")));
|
removeTypes.addAll(Arrays.asList(sa.getParam("RemoveTypes").split(",")));
|
||||||
}
|
}
|
||||||
@@ -464,75 +459,15 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
sVars.addAll(Arrays.asList(sa.getParam("sVars").split(",")));
|
sVars.addAll(Arrays.asList(sa.getParam("sVars").split(",")));
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimateEffectBase.doAnimate(card, sa, power, toughness, types, removeTypes, finalDesc, keywords, removeKeywords, hiddenKeywords, timestamp);
|
AnimateEffectBase.doAnimate(card, sa, power, toughness, types, removeTypes, finalDesc,
|
||||||
|
keywords, removeKeywords, hiddenKeywords,
|
||||||
|
abilities, triggers, replacements, stAbs,
|
||||||
|
timestamp);
|
||||||
|
|
||||||
|
// check if animate added static Abilities
|
||||||
// remove abilities
|
CardTraitChanges traits = card.getChangedCardTraits().get(timestamp);
|
||||||
final List<SpellAbility> removedAbilities = Lists.newArrayList();
|
if (traits != null) {
|
||||||
boolean clearSpells = sa.hasParam("OverwriteSpells");
|
for (StaticAbility stAb : traits.getStaticAbilities()) {
|
||||||
boolean removeAll = sa.hasParam("RemoveAllAbilities");
|
|
||||||
boolean removeIntrinsic = sa.hasParam("RemoveIntrinsicAbilities");
|
|
||||||
|
|
||||||
if (clearSpells) {
|
|
||||||
removedAbilities.addAll(Lists.newArrayList(card.getSpells()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.hasParam("RemoveThisAbility") && !removedAbilities.contains(sa)) {
|
|
||||||
removedAbilities.add(sa);
|
|
||||||
}
|
|
||||||
|
|
||||||
// give abilities
|
|
||||||
final List<SpellAbility> addedAbilities = Lists.newArrayList();
|
|
||||||
if (abilities.size() > 0) {
|
|
||||||
for (final String s : abilities) {
|
|
||||||
final String actualAbility = source.getSVar(s);
|
|
||||||
addedAbilities.add(AbilityFactory.getAbility(actualAbility, card));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grant triggers
|
|
||||||
final List<Trigger> addedTriggers = Lists.newArrayList();
|
|
||||||
if (triggers.size() > 0) {
|
|
||||||
for (final String s : triggers) {
|
|
||||||
final String actualTrigger = source.getSVar(s);
|
|
||||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, false);
|
|
||||||
addedTriggers.add(parsedTrigger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// give replacement effects
|
|
||||||
final List<ReplacementEffect> addedReplacements = Lists.newArrayList();
|
|
||||||
if (replacements.size() > 0) {
|
|
||||||
for (final String s : replacements) {
|
|
||||||
final String actualReplacement = source.getSVar(s);
|
|
||||||
final ReplacementEffect parsedReplacement = ReplacementHandler.parseReplacement(actualReplacement, card, false);
|
|
||||||
addedReplacements.add(parsedReplacement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// give static abilities (should only be used by cards to give
|
|
||||||
// itself a static ability)
|
|
||||||
final List<StaticAbility> addedStaticAbilities = Lists.newArrayList();
|
|
||||||
if (stAbs.size() > 0) {
|
|
||||||
for (final String s : stAbs) {
|
|
||||||
final String actualAbility = source.getSVar(s);
|
|
||||||
addedStaticAbilities.add(new StaticAbility(actualAbility, card));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removeAll || removeIntrinsic
|
|
||||||
|| !addedAbilities.isEmpty() || !removedAbilities.isEmpty() || !addedTriggers.isEmpty()
|
|
||||||
|| !addedReplacements.isEmpty() || !addedStaticAbilities.isEmpty()) {
|
|
||||||
card.addChangedCardTraits(addedAbilities, removedAbilities, addedTriggers, addedReplacements,
|
|
||||||
addedStaticAbilities, removeAll, false, removeIntrinsic, timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// give static abilities (should only be used by cards to give
|
|
||||||
// itself a static ability)
|
|
||||||
if (stAbs.size() > 0) {
|
|
||||||
for (final String s : stAbs) {
|
|
||||||
final String actualAbility = source.getSVar(s);
|
|
||||||
final StaticAbility stAb = card.addStaticAbility(actualAbility);
|
|
||||||
if ("Continuous".equals(stAb.getParam("Mode"))) {
|
if ("Continuous".equals(stAb.getParam("Mode"))) {
|
||||||
for (final StaticAbilityLayer layer : stAb.getLayers()) {
|
for (final StaticAbilityLayer layer : stAb.getLayers()) {
|
||||||
StaticAbilityContinuous.applyContinuousAbility(stAb, new CardCollection(card), layer);
|
StaticAbilityContinuous.applyContinuousAbility(stAb, new CardCollection(card), layer);
|
||||||
@@ -564,4 +499,12 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
public static boolean isAnimatedThisTurn(Player ai, Card c) {
|
public static boolean isAnimatedThisTurn(Player ai, Card c) {
|
||||||
return AiCardMemory.isRememberedCard(ai, c, AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
|
return AiCardMemory.isRememberedCard(ai, c, AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void holdAnimatedTillMain2(Player ai, Card c) {
|
||||||
|
AiCardMemory.rememberCard(ai, c, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseHeldTillMain2(Player ai, Card c) {
|
||||||
|
AiCardMemory.forgetCard(ai, c, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,11 +69,6 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
|
||||||
&& !"Curse".equals(sa.getParam("AILogic"))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
// prevent run-away activations - first time will always return true
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -93,11 +88,12 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (ai.getController().isAI()) {
|
if (ai.getController().isAI()) {
|
||||||
advancedFlash = ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.FLASH_ENABLE_ADVANCED_LOGIC);
|
advancedFlash = ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.FLASH_ENABLE_ADVANCED_LOGIC);
|
||||||
}
|
}
|
||||||
if (source.withFlash(ai) && source.isAura() && advancedFlash && !doAdvancedFlashAuraLogic(ai, sa, sa.getTargetCard())) {
|
if (!ai.canCastSorcery() && sa.canCastTiming(ai)
|
||||||
|
&& source.isAura() && advancedFlash && !doAdvancedFlashAuraLogic(ai, sa, sa.getTargetCard())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abCost.getTotalMana().countX() > 0 && source.getSVar("X").equals("Count$xPaid")) {
|
if (abCost.getTotalMana().countX() > 0 && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value. (Endless Scream and Venarian
|
// Set PayX here to maximum value. (Endless Scream and Venarian
|
||||||
// Gold)
|
// Gold)
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
@@ -106,7 +102,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Chained to the Rocks")) {
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Chained to the Rocks")) {
|
||||||
@@ -977,7 +973,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
} else {
|
} else {
|
||||||
AttachAi.attachPreference(sa, tgt, mandatory);
|
AttachAi.attachPreference(sa, tgt, mandatory);
|
||||||
targets = sa.getTargets().getTargets();
|
targets = sa.getTargets();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
||||||
@@ -1573,7 +1569,6 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
} else if (keyword.equals("Haste")) {
|
} else if (keyword.equals("Haste")) {
|
||||||
return card.hasSickness() && ph.isPlayerTurn(sa.getActivatingPlayer()) && !card.isTapped()
|
return card.hasSickness() && ph.isPlayerTurn(sa.getActivatingPlayer()) && !card.isTapped()
|
||||||
&& card.getNetCombatDamage() + powerBonus > 0
|
&& card.getNetCombatDamage() + powerBonus > 0
|
||||||
&& !card.hasKeyword("CARDNAME can attack as though it had haste.")
|
|
||||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& ComputerUtilCombat.canAttackNextTurn(card);
|
&& ComputerUtilCombat.canAttackNextTurn(card);
|
||||||
} else if (keyword.endsWith("Indestructible")) {
|
} else if (keyword.endsWith("Indestructible")) {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class BecomesBlockedAi extends SpellAbilityAi {
|
|||||||
list = CardLists.getTargetableCards(list, sa);
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
list = CardLists.getNotKeyword(list, Keyword.TRAMPLE);
|
list = CardLists.getNotKeyword(list, Keyword.TRAMPLE);
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) {
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getTargets().getNumTargeted() != 0) {
|
if (sa.getTargets().size() != 0) {
|
||||||
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
||||||
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
||||||
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
||||||
&& topSa.getTargets().getTargets().contains(aiPlayer)) {
|
&& topSa.getTargets().contains(aiPlayer)) {
|
||||||
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
|
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -339,10 +339,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
String type = sa.getParam("ChangeType");
|
String type = sa.getParam("ChangeType");
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
if (type.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (type.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
type = type.replace("X", Integer.toString(xPay));
|
type = type.replace("X", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -384,11 +384,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
String num = sa.getParam("ChangeNum");
|
String num = sa.getParam("ChangeNum");
|
||||||
if (num != null) {
|
if (num != null) {
|
||||||
if (num.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (num.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
xPay = Math.min(xPay, list.size());
|
xPay = Math.min(xPay, list.size());
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,8 +474,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
// Fetching should occur fairly often as it helps cast more spells, and
|
// Fetching should occur fairly often as it helps cast more spells, and
|
||||||
// have access to more mana
|
// have access to more mana
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
|
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
if (sa.getParam("AILogic").equals("Never")) {
|
if (sa.getParam("AILogic").equals("Never")) {
|
||||||
/*
|
/*
|
||||||
@@ -496,10 +494,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// this works for hidden because the mana is paid first.
|
// this works for hidden because the mana is paid first.
|
||||||
final String type = sa.getParam("ChangeType");
|
final String type = sa.getParam("ChangeType");
|
||||||
if (type != null && type.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (type != null && type.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<Player> pDefined;
|
Iterable<Player> pDefined;
|
||||||
@@ -847,7 +845,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
final AbilitySub abSub = sa.getSubAbility();
|
final AbilitySub abSub = sa.getSubAbility();
|
||||||
@@ -861,8 +858,17 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(origin), tgt.getValidTgts(), ai, source, sa);
|
// X controls the minimum targets
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
if ("X".equals(sa.getTargetRestrictions().getMinTargets()) && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
|
// Set PayX here to maximum value.
|
||||||
|
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
|
|
||||||
|
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
||||||
|
sa.setXManaCostPaid(xPay);
|
||||||
|
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
||||||
|
}
|
||||||
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
||||||
|
|
||||||
// Filter AI-specific targets if provided
|
// Filter AI-specific targets if provided
|
||||||
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
||||||
@@ -898,7 +904,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
//System.out.println("isPreferredTarget ok " + list);
|
//System.out.println("isPreferredTarget ok " + list);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
if (list.size() < sa.getMinTargets()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -923,7 +929,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Combat bouncing
|
// Combat bouncing
|
||||||
if (tgt.getMinTargets(sa.getHostCard(), sa) <= 1) {
|
if (sa.getMinTargets() <= 1) {
|
||||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
Combat currCombat = game.getCombat();
|
Combat currCombat = game.getCombat();
|
||||||
CardCollection attackers = currCombat.getAttackers();
|
CardCollection attackers = currCombat.getAttackers();
|
||||||
@@ -966,7 +972,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
// if it's blink or bounce, try to save my about to die stuff
|
// if it's blink or bounce, try to save my about to die stuff
|
||||||
final boolean blink = (destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger
|
final boolean blink = (destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger
|
||||||
|| "DelayedBlink".equals(sa.getParam("AILogic")) || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered"))));
|
|| "DelayedBlink".equals(sa.getParam("AILogic")) || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered"))));
|
||||||
if ((destination.equals(ZoneType.Hand) || blink) && (tgt.getMinTargets(sa.getHostCard(), sa) <= 1)) {
|
if ((destination.equals(ZoneType.Hand) || blink) && (sa.getMinTargets() <= 1)) {
|
||||||
// save my about to die stuff
|
// save my about to die stuff
|
||||||
Card tobounce = canBouncePermanent(ai, sa, list);
|
Card tobounce = canBouncePermanent(ai, sa, list);
|
||||||
if (tobounce != null) {
|
if (tobounce != null) {
|
||||||
@@ -976,7 +982,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
sa.getTargets().add(tobounce);
|
sa.getTargets().add(tobounce);
|
||||||
|
|
||||||
boolean saheeliFelidarCombo = sa.getHostCard().getName().equals("Felidar Guardian")
|
boolean saheeliFelidarCombo = ComputerUtilAbility.getAbilitySourceName(sa).equals("Felidar Guardian")
|
||||||
&& tobounce.getName().equals("Saheeli Rai")
|
&& tobounce.getName().equals("Saheeli Rai")
|
||||||
&& CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Felidar Guardian")).size() <
|
&& CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Felidar Guardian")).size() <
|
||||||
CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Creature")).size() + ai.getOpponentsGreatestLifeTotal() + 10;
|
CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Creature")).size() + ai.getOpponentsGreatestLifeTotal() + 10;
|
||||||
@@ -1109,7 +1115,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.usesTargeting()
|
boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.usesTargeting()
|
||||||
&& sa.getTargetRestrictions().getMinTargets(source, sa) == 0
|
&& sa.getMinTargets() == 0
|
||||||
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class);
|
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class);
|
||||||
|
|
||||||
if (list.isEmpty() && !doWithoutTarget) {
|
if (list.isEmpty() && !doWithoutTarget) {
|
||||||
@@ -1120,12 +1126,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
// the Unless cost (for example, Erratic Portal)
|
// the Unless cost (for example, Erratic Portal)
|
||||||
list.removeAll(getSafeTargetsIfUnlessCostPaid(ai, sa, list));
|
list.removeAll(getSafeTargetsIfUnlessCostPaid(ai, sa, list));
|
||||||
|
|
||||||
if (!mandatory && list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
if (!mandatory && sa.isTargetNumberValid()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// target loop
|
// target loop
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
while (sa.canAddMoreTarget()) {
|
||||||
// AI Targeting
|
// AI Targeting
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
|
|
||||||
@@ -1154,7 +1160,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//option to hold removal instead only applies for single targeted removal
|
//option to hold removal instead only applies for single targeted removal
|
||||||
if (!immediately && tgt.getMaxTargets(source, sa) == 1) {
|
if (!immediately && sa.getMaxTargets() == 1) {
|
||||||
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, destination)) {
|
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, destination)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1190,7 +1196,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (sa.getTargets().getNumTargeted() == 0 || sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
if (sa.getTargets().size() == 0 || !sa.isTargetNumberValid()) {
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
}
|
}
|
||||||
@@ -1204,7 +1210,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
boolean aiTgtsOK = false;
|
boolean aiTgtsOK = false;
|
||||||
if (sa.hasParam("AIMinTgts")) {
|
if (sa.hasParam("AIMinTgts")) {
|
||||||
int minTgts = Integer.parseInt(sa.getParam("AIMinTgts"));
|
int minTgts = Integer.parseInt(sa.getParam("AIMinTgts"));
|
||||||
if (sa.getTargets().getNumTargeted() >= minTgts) {
|
if (sa.getTargets().size() >= minTgts) {
|
||||||
aiTgtsOK = true;
|
aiTgtsOK = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1225,7 +1231,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// honor the Same Creature Type restriction
|
// honor the Same Creature Type restriction
|
||||||
if (tgt.isWithSameCreatureType()) {
|
if (sa.getTargetRestrictions().isWithSameCreatureType()) {
|
||||||
Card firstTarget = sa.getTargetCard();
|
Card firstTarget = sa.getTargetCard();
|
||||||
if (firstTarget != null && !choice.sharesCreatureTypeWith(firstTarget)) {
|
if (firstTarget != null && !choice.sharesCreatureTypeWith(firstTarget)) {
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
@@ -1353,46 +1359,31 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
|
||||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
|
||||||
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(origin), tgt.getValidTgts(), ai, source, sa);
|
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), ai, source, sa);
|
||||||
|
|
||||||
// Narrow down the list:
|
|
||||||
if (origin.equals(ZoneType.Battlefield)) {
|
|
||||||
// filter out untargetables
|
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
|
|
||||||
// if Destination is hand, either bounce opponents dangerous stuff
|
list.removeAll(sa.getTargets().getTargetCards());
|
||||||
// or save my about to die stuff
|
|
||||||
|
|
||||||
// if Destination is exile, filter out my cards
|
|
||||||
}
|
|
||||||
else if (origin.equals(ZoneType.Graveyard)) {
|
|
||||||
// Retrieve from Graveyard to:
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
|
||||||
list.remove(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// target loop
|
// target loop
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
while (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
// AI Targeting
|
// AI Targeting
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
|
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
if (ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false).isCreature()
|
Card mostExpensivePermanent = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false);
|
||||||
&& (destination.equals(ZoneType.Battlefield) || origin.equals(ZoneType.Battlefield))) {
|
if (mostExpensivePermanent.isCreature()
|
||||||
|
&& (destination.equals(ZoneType.Battlefield) || tgt.getZone().contains(ZoneType.Battlefield))) {
|
||||||
// if a creature is most expensive take the best
|
// if a creature is most expensive take the best
|
||||||
choice = ComputerUtilCard.getBestCreatureToBounceAI(list);
|
choice = ComputerUtilCard.getBestCreatureToBounceAI(list);
|
||||||
} else if (destination.equals(ZoneType.Battlefield) || origin.equals(ZoneType.Battlefield)) {
|
} else if (destination.equals(ZoneType.Battlefield) || tgt.getZone().contains(ZoneType.Battlefield)) {
|
||||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false);
|
choice = mostExpensivePermanent;
|
||||||
} else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) {
|
} else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) {
|
||||||
List<Card> nonLands = CardLists.getNotType(list, "Land");
|
List<Card> nonLands = CardLists.getNotType(list, "Land");
|
||||||
// Prefer to pull a creature, generally more useful for AI.
|
// Prefer to pull a creature, generally more useful for AI.
|
||||||
@@ -1424,7 +1415,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (sa.getTargets().getNumTargeted() == 0 || sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
if (sa.getTargets().size() == 0 || sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -1454,10 +1445,14 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private static boolean knownOriginTriggerAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
private static boolean knownOriginTriggerAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
if ("DeathgorgeScavenger".equals(sa.getParam("AILogic"))) {
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
|
if ("DeathgorgeScavenger".equals(logic)) {
|
||||||
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
||||||
} else if ("ExtraplanarLens".equals(sa.getParam("AILogic"))) {
|
} else if ("ExtraplanarLens".equals(logic)) {
|
||||||
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
||||||
|
} else if ("ExileCombatThreat".equals(logic)) {
|
||||||
|
return doExileCombatThreatLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getTargetRestrictions() == null) {
|
if (sa.getTargetRestrictions() == null) {
|
||||||
@@ -1485,7 +1480,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
if ("NeverBounceItself".equals(logic)) {
|
if ("NeverBounceItself".equals(logic)) {
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
if (fetchList.contains(source) && (fetchList.size() > 1 && !sa.getTriggeringAbility().isMandatory())) {
|
if (fetchList.contains(source) && (fetchList.size() > 1 || !sa.getTriggeringAbility().isMandatory())) {
|
||||||
// For cards that should never be bounced back to hand with their own [e.g. triggered] abilities, such as guild lands.
|
// For cards that should never be bounced back to hand with their own [e.g. triggered] abilities, such as guild lands.
|
||||||
fetchList.remove(source);
|
fetchList.remove(source);
|
||||||
}
|
}
|
||||||
@@ -1830,6 +1825,51 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean doExileCombatThreatLogic(final Player aiPlayer, final SpellAbility sa) {
|
||||||
|
final Combat combat = aiPlayer.getGame().getCombat();
|
||||||
|
|
||||||
|
if (combat == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Card choice = null;
|
||||||
|
int highestEval = -1;
|
||||||
|
if (combat.getAttackingPlayer().isOpponentOf(aiPlayer)) {
|
||||||
|
for (Card attacker : combat.getAttackers()) {
|
||||||
|
if (sa.canTarget(attacker) && attacker.canBeTargetedBy(sa)) {
|
||||||
|
int eval = ComputerUtilCard.evaluateCreature(attacker);
|
||||||
|
if (combat.isUnblocked(attacker)) {
|
||||||
|
eval += 100; // TODO: make this smarter
|
||||||
|
}
|
||||||
|
if (eval > highestEval) {
|
||||||
|
highestEval = eval;
|
||||||
|
choice = attacker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// either the current AI player or one of its teammates is attacking, the opponent(s) are blocking
|
||||||
|
for (Card blocker : combat.getAllBlockers()) {
|
||||||
|
if (sa.canTarget(blocker) && blocker.canBeTargetedBy(sa)) {
|
||||||
|
if (blocker.getController().isOpponentOf(aiPlayer)) { // TODO: unnecessary sanity check?
|
||||||
|
int eval = ComputerUtilCard.evaluateCreature(blocker);
|
||||||
|
if (eval > highestEval) {
|
||||||
|
highestEval = eval;
|
||||||
|
choice = blocker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice != null) {
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static CardCollection getSafeTargetsIfUnlessCostPaid(Player ai, SpellAbility sa, Iterable<Card> potentialTgts) {
|
private static CardCollection getSafeTargetsIfUnlessCostPaid(Player ai, SpellAbility sa, Iterable<Card> potentialTgts) {
|
||||||
// Determines if the controller of each potential target can negate the ChangeZone effect
|
// Determines if the controller of each potential target can negate the ChangeZone effect
|
||||||
// by paying the Unless cost. Returns the list of targets that can be saved that way.
|
// by paying the Unless cost. Returns the list of targets that can be saved that way.
|
||||||
@@ -1845,7 +1885,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int toPay = 0;
|
int toPay = 0;
|
||||||
boolean setPayX = false;
|
boolean setPayX = false;
|
||||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||||
setPayX = true;
|
setPayX = true;
|
||||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
} else {
|
} else {
|
||||||
@@ -1861,7 +1901,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (setPayX) {
|
if (setPayX) {
|
||||||
source.setSVar("PayX", Integer.toString(toPay));
|
sa.setSVar("PayX", Integer.toString(toPay));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.effects.CharmEffect;
|
import forge.game.ability.effects.CharmEffect;
|
||||||
|
import forge.game.card.Card;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -21,8 +23,11 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
|
final Card source = sa.getHostCard();
|
||||||
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
|
|
||||||
|
final int num = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa);
|
||||||
|
final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num;
|
||||||
|
|
||||||
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
|
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
|
||||||
|
|
||||||
// Reset the chosen list otherwise it will be locked in forever by earlier calls
|
// Reset the chosen list otherwise it will be locked in forever by earlier calls
|
||||||
|
|||||||
@@ -102,9 +102,10 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
} else if (aiLogic.equals("Ashiok")) {
|
} else if (aiLogic.equals("Ashiok")) {
|
||||||
final int loyalty = host.getCounters(CounterEnumType.LOYALTY) - 1;
|
final int loyalty = host.getCounters(CounterEnumType.LOYALTY) - 1;
|
||||||
for (int i = loyalty; i >= 0; i--) {
|
for (int i = loyalty; i >= 0; i--) {
|
||||||
host.setSVar("ChosenX", "Number$" + i);
|
sa.setSVar("PayX", String.valueOf(i));
|
||||||
|
sa.setXManaCostPaid(i);
|
||||||
choices = ai.getGame().getCardsIn(choiceZone);
|
choices = ai.getGame().getCardsIn(choiceZone);
|
||||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host, sa);
|
||||||
if (!choices.isEmpty()) {
|
if (!choices.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -130,6 +131,12 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
return (ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) < ComputerUtilCard
|
return (ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) < ComputerUtilCard
|
||||||
.evaluateCreatureList(oppCreatures);
|
.evaluateCreatureList(oppCreatures);
|
||||||
|
} else if (aiLogic.equals("OwnCard")) {
|
||||||
|
CardCollectionView ownChoices = CardLists.filter(choices, CardPredicates.isController(ai));
|
||||||
|
if (ownChoices.isEmpty()) {
|
||||||
|
ownChoices = CardLists.filter(choices, CardPredicates.isControlledByAnyOf(ai.getAllies()));
|
||||||
|
}
|
||||||
|
return !ownChoices.isEmpty();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -156,6 +163,12 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
choice = ComputerUtilCard.getBestAI(options);
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
} else if ("WorstCard".equals(logic)) {
|
} else if ("WorstCard".equals(logic)) {
|
||||||
choice = ComputerUtilCard.getWorstAI(options);
|
choice = ComputerUtilCard.getWorstAI(options);
|
||||||
|
} else if ("OwnCard".equals(logic)) {
|
||||||
|
CardCollectionView ownChoices = CardLists.filter(options, CardPredicates.isController(ai));
|
||||||
|
if (ownChoices.isEmpty()) {
|
||||||
|
ownChoices = CardLists.filter(options, CardPredicates.isControlledByAnyOf(ai.getAllies()));
|
||||||
|
}
|
||||||
|
choice = ComputerUtilCard.getBestAI(ownChoices);
|
||||||
} else if (logic.equals("BestBlocker")) {
|
} else if (logic.equals("BestBlocker")) {
|
||||||
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
|
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
|
||||||
options = CardLists.filter(options, Presets.UNTAPPED);
|
options = CardLists.filter(options, Presets.UNTAPPED);
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
// TODO - there is no AILogic implemented yet
|
// TODO - there is no AILogic implemented yet
|
||||||
return false;
|
return mandatory;
|
||||||
}
|
}
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ public class ChooseColorAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
@@ -44,7 +43,7 @@ public class ChooseColorAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(x));
|
sa.setSVar("PayX", Integer.toString(x));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,7 +219,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
private static List<GameObject> getTargets(final SpellAbility sa) {
|
private static List<GameObject> getTargets(final SpellAbility sa) {
|
||||||
return sa.usesTargeting() && (!sa.hasParam("Defined"))
|
return sa.usesTargeting() && (!sa.hasParam("Defined"))
|
||||||
? Lists.newArrayList(sa.getTargets().getTargets())
|
? Lists.newArrayList(sa.getTargets())
|
||||||
: AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
: AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,20 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Lists;
|
import forge.ai.*;
|
||||||
import forge.ai.AiCardMemory;
|
|
||||||
import forge.ai.ComputerUtilAbility;
|
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.ComputerUtilMana;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.*;
|
||||||
import forge.game.card.CardCollectionView;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.card.CardPredicates;
|
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import java.util.List;
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class ChooseTypeAi extends SpellAbilityAi {
|
public class ChooseTypeAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
@@ -31,6 +25,8 @@ public class ChooseTypeAi extends SpellAbilityAi {
|
|||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
|
||||||
return doMirrorEntityLogic(aiPlayer, sa);
|
return doMirrorEntityLogic(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
} else if ("MostProminentOppControls".equals(sa.getParam("AILogic"))) {
|
||||||
|
return !chooseType(sa, aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield)).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
return doTriggerAINoCost(aiPlayer, sa, false);
|
return doTriggerAINoCost(aiPlayer, sa, false);
|
||||||
@@ -44,28 +40,12 @@ public class ChooseTypeAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollectionView otb = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
String chosenType = chooseType(sa, aiPlayer.getCardsIn(ZoneType.Battlefield));
|
||||||
List<String> valid = Lists.newArrayList(CardType.getAllCreatureTypes());
|
|
||||||
|
|
||||||
String chosenType = ComputerUtilCard.getMostProminentType(otb, valid);
|
|
||||||
if (chosenType.isEmpty()) {
|
if (chosenType.isEmpty()) {
|
||||||
// Account for the situation when only changelings are on the battlefield
|
|
||||||
boolean allChangeling = false;
|
|
||||||
for (Card c : otb) {
|
|
||||||
if (c.isCreature() && c.hasStartOfKeyword(Keyword.CHANGELING.toString())) {
|
|
||||||
chosenType = Aggregates.random(valid); // just choose a random type for changelings
|
|
||||||
allChangeling = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!allChangeling) {
|
|
||||||
// Still empty, probably no creatures on board
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int maxX = ComputerUtilMana.determineMaxAffordableX(aiPlayer, sa);
|
int maxX = ComputerUtilMana.determineLeftoverMana(sa, aiPlayer);
|
||||||
int avgPower = 0;
|
int avgPower = 0;
|
||||||
|
|
||||||
// predict the opposition
|
// predict the opposition
|
||||||
@@ -127,4 +107,40 @@ public class ChooseTypeAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String chooseType(SpellAbility sa, CardCollectionView cards) {
|
||||||
|
Set<String> valid = new HashSet<>();
|
||||||
|
|
||||||
|
if (sa.getSubAbility() != null && sa.getSubAbility().getApi() == ApiType.PumpAll
|
||||||
|
&& sa.getSubAbility().isCurse() && sa.getSubAbility().hasParam("NumDef")) {
|
||||||
|
final SpellAbility pumpSa = sa.getSubAbility();
|
||||||
|
final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), pumpSa.getParam("NumDef"), pumpSa);
|
||||||
|
for (Card c : cards) {
|
||||||
|
if (c.isCreature() && c.getNetToughness() <= -defense) {
|
||||||
|
valid.addAll(c.getType().getCreatureTypes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
valid.addAll(CardType.getAllCreatureTypes());
|
||||||
|
}
|
||||||
|
|
||||||
|
String chosenType = ComputerUtilCard.getMostProminentType(cards, valid);
|
||||||
|
if (chosenType.isEmpty()) {
|
||||||
|
// Account for the situation when only changelings are on the battlefield
|
||||||
|
boolean allChangeling = false;
|
||||||
|
for (Card c : cards) {
|
||||||
|
if (c.isCreature() && c.hasStartOfKeyword(Keyword.CHANGELING.toString())) {
|
||||||
|
chosenType = Aggregates.random(valid); // just choose a random type for changelings
|
||||||
|
allChangeling = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allChangeling) {
|
||||||
|
// Still empty, probably no creatures on board
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chosenType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ public class ClashAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getTargets().getNumTargeted() > 0;
|
return sa.getTargets().size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,11 +197,11 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
Card t = null;
|
Card t = null;
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -280,7 +280,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
if (!sa.usesTargeting()) {
|
||||||
if (sa.hasParam("AllValid")) {
|
if (sa.hasParam("AllValid")) {
|
||||||
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
// target loop
|
// target loop
|
||||||
while (sa.canAddMoreTarget()) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
if (!sa.isTargetNumberValid() || (sa.getTargets().size() == 0)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -159,7 +159,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
if (!sa.isTargetNumberValid() || (sa.getTargets().size() == 0)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import forge.game.Game;
|
|||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.spellability.AbilityActivated;
|
|
||||||
import forge.game.spellability.Spell;
|
import forge.game.spellability.Spell;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
@@ -63,7 +62,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (top.isWrapper() || !(top instanceof SpellAbility || top instanceof AbilityActivated)) {
|
if (top.isWrapper() || !(top instanceof SpellAbility || top.isActivatedAbility())) {
|
||||||
// Shouldn't even try with triggered or wrapped abilities at this time, will crash
|
// Shouldn't even try with triggered or wrapped abilities at this time, will crash
|
||||||
return false;
|
return false;
|
||||||
} else if (top.getApi() == ApiType.CopySpellAbility) {
|
} else if (top.getApi() == ApiType.CopySpellAbility) {
|
||||||
@@ -91,7 +90,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
|||||||
AiPlayDecision decision = AiPlayDecision.CantPlaySa;
|
AiPlayDecision decision = AiPlayDecision.CantPlaySa;
|
||||||
if (top instanceof Spell) {
|
if (top instanceof Spell) {
|
||||||
decision = ((PlayerControllerAi) aiPlayer.getController()).getAi().canPlayFromEffectAI((Spell) topCopy, true, true);
|
decision = ((PlayerControllerAi) aiPlayer.getController()).getAi().canPlayFromEffectAI((Spell) topCopy, true, true);
|
||||||
} else if (top instanceof AbilityActivated && top.getActivatingPlayer().equals(aiPlayer)
|
} else if (top.isActivatedAbility() && top.getActivatingPlayer().equals(aiPlayer)
|
||||||
&& logic.contains("CopyActivatedAbilities")) {
|
&& logic.contains("CopyActivatedAbilities")) {
|
||||||
decision = AiPlayDecision.WillPlay; // FIXME: we activated it once, why not again? Or bad idea?
|
decision = AiPlayDecision.WillPlay; // FIXME: we activated it once, why not again? Or bad idea?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int toPay = 0;
|
int toPay = 0;
|
||||||
boolean setPayX = false;
|
boolean setPayX = false;
|
||||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||||
setPayX = true;
|
setPayX = true;
|
||||||
toPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), usableManaSources + 1);
|
toPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), usableManaSources + 1);
|
||||||
} else {
|
} else {
|
||||||
@@ -123,7 +123,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (setPayX) {
|
if (setPayX) {
|
||||||
source.setSVar("PayX", Integer.toString(toPay));
|
sa.setSVar("PayX", Integer.toString(toPay));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int toPay = 0;
|
int toPay = 0;
|
||||||
boolean setPayX = false;
|
boolean setPayX = false;
|
||||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||||
setPayX = true;
|
setPayX = true;
|
||||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
} else {
|
} else {
|
||||||
@@ -289,7 +289,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (setPayX) {
|
if (setPayX) {
|
||||||
source.setSVar("PayX", Integer.toString(toPay));
|
sa.setSVar("PayX", Integer.toString(toPay));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CounterEnumType.P1P1.equals(cType) && sa.hasParam("Source")) {
|
if (cType != null && cType.is(CounterEnumType.P1P1) && sa.hasParam("Source")) {
|
||||||
int amount = calcAmount(sa, cType);
|
int amount = calcAmount(sa, cType);
|
||||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||||
@@ -92,7 +92,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
// for Simic Fluxmage and other
|
// for Simic Fluxmage and other
|
||||||
return ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN);
|
return ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN);
|
||||||
|
|
||||||
} else if (CounterEnumType.P1P1.equals(cType) && sa.hasParam("Defined")) {
|
} else if (cType != null && cType.is(CounterEnumType.P1P1) && sa.hasParam("Defined")) {
|
||||||
// something like Cyptoplast Root-kin
|
// something like Cyptoplast Root-kin
|
||||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||||
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
@@ -279,7 +279,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// do not steal a P1P1 from Undying if it would die
|
// do not steal a P1P1 from Undying if it would die
|
||||||
// this way
|
// this way
|
||||||
if (CounterEnumType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
if (cType != null && cType.is(CounterEnumType.P1P1) && srcCardCpy.getNetToughness() <= 0) {
|
||||||
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken();
|
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -322,13 +322,13 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// try to remove P1P1 from undying or evolve
|
// try to remove P1P1 from undying or evolve
|
||||||
if (CounterEnumType.P1P1.equals(cType)) {
|
if (cType != null && cType.is(CounterEnumType.P1P1)) {
|
||||||
if (card.hasKeyword(Keyword.UNDYING) || card.hasKeyword(Keyword.EVOLVE)
|
if (card.hasKeyword(Keyword.UNDYING) || card.hasKeyword(Keyword.EVOLVE)
|
||||||
|| card.hasKeyword(Keyword.ADAPT)) {
|
|| card.hasKeyword(Keyword.ADAPT)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (CounterEnumType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
|
if (cType != null && cType.is(CounterEnumType.M1M1) && card.hasKeyword(Keyword.PERSIST)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,10 +383,10 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cType != null) {
|
if (cType != null) {
|
||||||
if (CounterEnumType.P1P1.equals(cType) && card.hasKeyword(Keyword.UNDYING)) {
|
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (CounterEnumType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
|
if (cType.is(CounterEnumType.M1M1) && card.hasKeyword(Keyword.PERSIST)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||||
final CounterType counterType = getCounterType(sa);
|
final CounterType counterType = getCounterType(sa);
|
||||||
|
|
||||||
if (!CounterEnumType.P1P1.equals(counterType) && counterType != null) {
|
if (counterType != null && !counterType.is(CounterEnumType.P1P1)) {
|
||||||
if (!sa.hasParam("ActivationPhases")) {
|
if (!sa.hasParam("ActivationPhases")) {
|
||||||
// Don't use non P1P1/M1M1 counters before main 2 if possible
|
// Don't use non P1P1/M1M1 counters before main 2 if possible
|
||||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
@@ -173,7 +173,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// targeting does failed
|
// targeting does failed
|
||||||
if (!sa.isTargetNumberValid() || sa.getTargets().getNumTargeted() == 0) {
|
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (amountStr.equals("X")) {
|
if (amountStr.equals("X")) {
|
||||||
if (source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
// By default, set PayX here to maximum value (used for most SAs of this type).
|
// By default, set PayX here to maximum value (used for most SAs of this type).
|
||||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
|
|
||||||
@@ -359,7 +359,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source.setSVar("PayX", Integer.toString(amount));
|
sa.setSVar("PayX", Integer.toString(amount));
|
||||||
} else if ("ExiledCreatureFromGraveCMC".equals(logic)) {
|
} else if ("ExiledCreatureFromGraveCMC".equals(logic)) {
|
||||||
// e.g. Necropolis
|
// e.g. Necropolis
|
||||||
amount = Aggregates.max(CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES), CardPredicates.Accessors.fnGetCmc);
|
amount = Aggregates.max(CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES), CardPredicates.Accessors.fnGetCmc);
|
||||||
@@ -403,7 +403,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
if (sa.usesTargeting() && abTgt.getMinTargets(source, sa) < 2) {
|
if (sa.usesTargeting() && abTgt.getMinTargets(source, sa) < 2) {
|
||||||
if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) {
|
if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) {
|
||||||
Card c = sa.getTargets().getFirstTargetedCard();
|
Card c = sa.getTargets().getFirstTargetedCard();
|
||||||
if (sa.getTargets().getNumTargeted() > 1) {
|
if (sa.getTargets().size() > 1) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
@@ -456,7 +456,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
&& sa.hasParam("Planeswalker")
|
&& sa.hasParam("Planeswalker")
|
||||||
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
|
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
|
||||||
&& sa.isTargetNumberValid()
|
&& sa.isTargetNumberValid()
|
||||||
&& sa.getTargets().getNumTargeted() == 0
|
&& sa.getTargets().size() == 0
|
||||||
&& ai.getGame().getPhaseHandler().is(PhaseType.MAIN2, ai)) {
|
&& ai.getGame().getPhaseHandler().is(PhaseType.MAIN2, ai)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -475,7 +475,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
abTgt.addDividedAllocation(c, i);
|
abTgt.addDividedAllocation(c, i);
|
||||||
left -= i;
|
left -= i;
|
||||||
}
|
}
|
||||||
if (left < i || sa.getTargets().getNumTargeted() == abTgt.getMaxTargets(source, sa)) {
|
if (left < i || sa.getTargets().size() == abTgt.getMaxTargets(source, sa)) {
|
||||||
abTgt.addDividedAllocation(sa.getTargets().getFirstTargetedCard(), left + i);
|
abTgt.addDividedAllocation(sa.getTargets().getFirstTargetedCard(), left + i);
|
||||||
left = 0;
|
left = 0;
|
||||||
break;
|
break;
|
||||||
@@ -492,7 +492,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
// target loop
|
// target loop
|
||||||
while (sa.canAddMoreTarget()) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
if (!sa.isTargetNumberValid() || (sa.getTargets().size() == 0)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -530,7 +530,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (!sa.isTargetNumberValid() || sa.getTargets().getNumTargeted() == 0) {
|
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -637,7 +637,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (!sa.isTargetNumberValid()
|
if (!sa.isTargetNumberValid()
|
||||||
|| sa.getTargets().getNumTargeted() == 0) {
|
|| sa.getTargets().size() == 0) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -661,7 +661,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if ((!sa.isTargetNumberValid())
|
if ((!sa.isTargetNumberValid())
|
||||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
|| (sa.getTargets().size() == 0)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -698,8 +698,8 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
list = new CardCollection(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa));
|
list = new CardCollection(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa));
|
||||||
|
|
||||||
if (amountStr.equals("X")
|
if (amountStr.equals("X")
|
||||||
&& !source.hasSVar("PayX") /* SubAbility on something that already had set PayX, e.g. Endless One ETB counters */
|
&& !sa.hasSVar("PayX") /* SubAbility on something that already had set PayX, e.g. Endless One ETB counters */
|
||||||
&& ((sa.hasParam(amountStr) && sa.getSVar(amountStr).equals("Count$xPaid")) || source.getSVar(amountStr).equals("Count$xPaid") )) {
|
&& ((sa.hasParam(amountStr) && sa.getSVar(amountStr).equals("Count$xPaid")))) {
|
||||||
|
|
||||||
// detect if there's more than one X in the cost (Hangarback Walker, Walking Ballista, etc.)
|
// detect if there's more than one X in the cost (Hangarback Walker, Walking Ballista, etc.)
|
||||||
SpellAbility testSa = sa;
|
SpellAbility testSa = sa;
|
||||||
@@ -725,7 +725,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
// Account for the multiple X in cost
|
// Account for the multiple X in cost
|
||||||
if (countX > 1) { payX /= countX; }
|
if (countX > 1) { payX /= countX; }
|
||||||
|
|
||||||
source.setSVar("PayX", Integer.toString(payX));
|
sa.setSVar("PayX", Integer.toString(payX));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
@@ -819,7 +819,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
if (choice != null && divided) {
|
if (choice != null && divided) {
|
||||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||||
int alloc = Math.max(amount / totalTargets, 1);
|
int alloc = Math.max(amount / totalTargets, 1);
|
||||||
if (sa.getTargets().getNumTargeted() == Math.min(totalTargets, abTgt.getMaxTargets(sa.getHostCard(), sa)) - 1) {
|
if (sa.getTargets().size() == Math.min(totalTargets, abTgt.getMaxTargets(sa.getHostCard(), sa)) - 1) {
|
||||||
abTgt.addDividedAllocation(choice, left);
|
abTgt.addDividedAllocation(choice, left);
|
||||||
} else {
|
} else {
|
||||||
abTgt.addDividedAllocation(choice, alloc);
|
abTgt.addDividedAllocation(choice, alloc);
|
||||||
@@ -1031,7 +1031,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (!ComputerUtil.isNegativeCounter(type, c) && !ComputerUtil.isUselessCounter(type)) {
|
if (!ComputerUtil.isNegativeCounter(type, c) && !ComputerUtil.isUselessCounter(type, c)) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,10 +82,10 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
// TODO improve X value to don't overpay when extra mana won't do
|
// TODO improve X value to don't overpay when extra mana won't do
|
||||||
// anything more useful
|
// anything more useful
|
||||||
final int amount;
|
final int amount;
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(amount));
|
sa.setSVar("PayX", Integer.toString(amount));
|
||||||
} else {
|
} else {
|
||||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
return true;
|
return true;
|
||||||
} else if (!ComputerUtil.isUselessCounter(aType)) {
|
} else if (!ComputerUtil.isUselessCounter(aType, best)) {
|
||||||
// whould remove positive counter
|
// whould remove positive counter
|
||||||
if (best.getCounters(aType) <= amount) {
|
if (best.getCounters(aType) <= amount) {
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
// variable amount for Hex Parasite
|
// variable amount for Hex Parasite
|
||||||
int amount;
|
int amount;
|
||||||
boolean xPay = false;
|
boolean xPay = false;
|
||||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
|
|
||||||
if (manaLeft == 0) {
|
if (manaLeft == 0) {
|
||||||
@@ -167,7 +167,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (amount >= ice) {
|
if (amount >= ice) {
|
||||||
sa.getTargets().add(depth);
|
sa.getTargets().add(depth);
|
||||||
if (xPay) {
|
if (xPay) {
|
||||||
source.setSVar("PayX", Integer.toString(ice));
|
sa.setSVar("PayX", Integer.toString(ice));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -186,7 +186,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
|
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
if (xPay) {
|
if (xPay) {
|
||||||
source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty()));
|
sa.setSVar("PayX", Integer.toString(best.getCurrentLoyalty()));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -297,7 +297,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
int amount;
|
int amount;
|
||||||
boolean xPay = false;
|
boolean xPay = false;
|
||||||
// Timecrafting has X R
|
// Timecrafting has X R
|
||||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
|
|
||||||
if (manaLeft == 0) {
|
if (manaLeft == 0) {
|
||||||
@@ -317,7 +317,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
int timeCount = best.getCounters(CounterEnumType.TIME);
|
int timeCount = best.getCounters(CounterEnumType.TIME);
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
if (xPay) {
|
if (xPay) {
|
||||||
source.setSVar("PayX", Integer.toString(timeCount));
|
sa.setSVar("PayX", Integer.toString(timeCount));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -387,9 +387,9 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
} else if (target instanceof Player) {
|
} else if (target instanceof Player) {
|
||||||
Player targetPlayer = (Player) target;
|
Player targetPlayer = (Player) target;
|
||||||
if (targetPlayer.isOpponentOf(player)) {
|
if (targetPlayer.isOpponentOf(player)) {
|
||||||
return !type.equals(CounterEnumType.POISON) ? max : min;
|
return !type.is(CounterEnumType.POISON) ? max : min;
|
||||||
} else {
|
} else {
|
||||||
return type.equals(CounterEnumType.POISON) ? max : min;
|
return type.is(CounterEnumType.POISON) ? max : min;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,13 +438,13 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
Player targetPlayer = (Player) target;
|
Player targetPlayer = (Player) target;
|
||||||
if (targetPlayer.isOpponentOf(ai)) {
|
if (targetPlayer.isOpponentOf(ai)) {
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (!type.equals(CounterEnumType.POISON)) {
|
if (!type.is(CounterEnumType.POISON)) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (type.equals(CounterEnumType.POISON)) {
|
if (type.is(CounterEnumType.POISON)) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
|||||||
if (!sa.canTarget(enemy)) {
|
if (!sa.canTarget(enemy)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) {
|
if (sa.getTargets() != null && sa.getTargets().contains(enemy)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (best_x > 0) {
|
if (best_x > 0) {
|
||||||
if (sa.getSVar(damage).equals("Count$xPaid")) {
|
if (sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
source.setSVar("PayX", Integer.toString(best_x));
|
sa.setSVar("PayX", Integer.toString(best_x));
|
||||||
}
|
}
|
||||||
if (damage.equals("ChosenX")) {
|
if (damage.equals("ChosenX")) {
|
||||||
source.setSVar("ChosenX", "Number$" + best_x);
|
source.setSVar("ChosenX", "Number$" + best_x);
|
||||||
@@ -203,7 +203,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(dmg));
|
sa.setSVar("PayX", Integer.toString(dmg));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("ValidPlayers")) {
|
if (sa.hasParam("ValidPlayers")) {
|
||||||
@@ -285,7 +285,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(dmg));
|
sa.setSVar("PayX", Integer.toString(dmg));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("ValidPlayers")) {
|
if (sa.hasParam("ValidPlayers")) {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import forge.game.ability.AbilityUtils;
|
|||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostPart;
|
|
||||||
import forge.game.cost.CostPartMana;
|
import forge.game.cost.CostPartMana;
|
||||||
import forge.game.cost.CostRemoveCounter;
|
import forge.game.cost.CostRemoveCounter;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
@@ -75,8 +74,8 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(dmg));
|
sa.setSVar("PayX", Integer.toString(dmg));
|
||||||
} else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.isInZone(ZoneType.Hand)) {
|
} else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.isInZone(ZoneType.Hand)) {
|
||||||
dmg--; // the card will be spent casting the spell, so actual damage is 1 less
|
dmg--; // the card will be spent casting the spell, so actual damage is 1 less
|
||||||
}
|
}
|
||||||
@@ -96,7 +95,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
if (damage.equals("X")) {
|
if (damage.equals("X")) {
|
||||||
if (sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
|
if (sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
|
||||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
|
|
||||||
// Try not to waste spells like Blaze or Fireball on early targets, try to do more damage with them if possible
|
// Try not to waste spells like Blaze or Fireball on early targets, try to do more damage with them if possible
|
||||||
if (ai.getController().isAI()) {
|
if (ai.getController().isAI()) {
|
||||||
@@ -113,7 +112,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set PayX here to maximum value. It will be adjusted later depending on the target.
|
// Set PayX here to maximum value. It will be adjusted later depending on the target.
|
||||||
source.setSVar("PayX", Integer.toString(dmg));
|
sa.setSVar("PayX", Integer.toString(dmg));
|
||||||
} else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) {
|
} else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) {
|
||||||
dmg = CardFactoryUtil.xCount(source, sa.getSVar(damage)) - 1; // the card will be spent casting the spell, so actual damage is 1 less
|
dmg = CardFactoryUtil.xCount(source, sa.getSVar(damage)) - 1; // the card will be spent casting the spell, so actual damage is 1 less
|
||||||
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
|
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
|
||||||
@@ -225,7 +224,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +265,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((damage.equals("X") && source.getSVar(damage).equals("Count$xPaid")) ||
|
if ((damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) ||
|
||||||
sourceName.equals("Crater's Claws")){
|
sourceName.equals("Crater's Claws")){
|
||||||
// If I can kill my target by paying less mana, do it
|
// If I can kill my target by paying less mana, do it
|
||||||
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) {
|
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) {
|
||||||
@@ -281,25 +280,13 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (sourceName.equals("Crater's Claws") && ai.hasFerocious()) {
|
if (sourceName.equals("Crater's Claws") && ai.hasFerocious()) {
|
||||||
actualPay = actualPay > 2 ? actualPay - 2 : 0;
|
actualPay = actualPay > 2 ? actualPay - 2 : 0;
|
||||||
}
|
}
|
||||||
source.setSVar("PayX", Integer.toString(actualPay));
|
sa.setSVar("PayX", Integer.toString(actualPay));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("XCountersDamage".equals(logic)) {
|
|
||||||
// Check to ensure that we have enough counters to remove per the defined PayX
|
|
||||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
|
||||||
if (part instanceof CostRemoveCounter) {
|
|
||||||
if (source.getCounters(((CostRemoveCounter) part).counter) < Integer.valueOf(source.getSVar("PayX"))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
||||||
final int CMC = Integer.parseInt(source.getSVar("PayX"));
|
final int cmc = Integer.parseInt(sa.getSVar("PayX"));
|
||||||
return !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(CMC)).isEmpty();
|
return !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc)).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -497,7 +484,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
hPlay = CardLists.filterControlledBy(hPlay, pl);
|
hPlay = CardLists.filterControlledBy(hPlay, pl);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<GameObject> objects = Lists.newArrayList(sa.getTargets().getTargets());
|
final List<GameObject> objects = Lists.newArrayList(sa.getTargets());
|
||||||
if (sa.hasParam("TargetUnique")) {
|
if (sa.hasParam("TargetUnique")) {
|
||||||
objects.addAll(sa.getUniqueTargets());
|
objects.addAll(sa.getUniqueTargets());
|
||||||
}
|
}
|
||||||
@@ -655,13 +642,13 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int totalTargetedSoFar = -1;
|
int totalTargetedSoFar = -1;
|
||||||
while (tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
while (tcs.size() < tgt.getMaxTargets(source, sa)) {
|
||||||
if (totalTargetedSoFar == tcs.getNumTargeted()) {
|
if (totalTargetedSoFar == tcs.size()) {
|
||||||
// Avoid looping endlessly when choosing targets for cards with variable target number and type
|
// Avoid looping endlessly when choosing targets for cards with variable target number and type
|
||||||
// like Jaya's Immolating Inferno
|
// like Jaya's Immolating Inferno
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
totalTargetedSoFar = tcs.getNumTargeted();
|
totalTargetedSoFar = tcs.size();
|
||||||
if (oppTargetsChoice && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) {
|
if (oppTargetsChoice && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) {
|
||||||
// canPlayAI (sa activated by ai)
|
// canPlayAI (sa activated by ai)
|
||||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
@@ -699,7 +686,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ("RoundedDown".equals(sa.getParam("DivideEvenly"))) {
|
if ("RoundedDown".equals(sa.getParam("DivideEvenly"))) {
|
||||||
dmg = dmg * sa.getTargets().getNumTargeted() / (sa.getTargets().getNumTargeted() +1);
|
dmg = dmg * sa.getTargets().size() / (sa.getTargets().size() +1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// look for creature targets; currently also catches planeswalkers that can be killed immediately
|
// look for creature targets; currently also catches planeswalkers that can be killed immediately
|
||||||
@@ -735,7 +722,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
boolean freePing = immediately || abCost == null
|
boolean freePing = immediately || abCost == null
|
||||||
|| sa.getTargets().getNumTargeted() > 0;
|
|| sa.getTargets().size() > 0;
|
||||||
|
|
||||||
if (!source.isSpell()) {
|
if (!source.isSpell()) {
|
||||||
if (phase.is(PhaseType.END_OF_TURN) && sa.isAbility() && abCost.isReusuableResource()) {
|
if (phase.is(PhaseType.END_OF_TURN) && sa.isAbility() && abCost.isReusuableResource()) {
|
||||||
@@ -783,14 +770,14 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
} else if ("OppAtTenLife".equals(logic)) {
|
} else if ("OppAtTenLife".equals(logic)) {
|
||||||
for (final Player p : ai.getOpponents()) {
|
for (final Player p : ai.getOpponents()) {
|
||||||
if (sa.canTarget(p) && p.getLife() == 10 && tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
if (sa.canTarget(p) && p.getLife() == 10 && tcs.size() < tgt.getMaxTargets(source, sa)) {
|
||||||
tcs.add(p);
|
tcs.add(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: Improve Damage, we shouldn't just target the player just
|
// TODO: Improve Damage, we shouldn't just target the player just
|
||||||
// because we can
|
// because we can
|
||||||
if (sa.canTarget(enemy) && tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
if (sa.canTarget(enemy) && tcs.size() < tgt.getMaxTargets(source, sa)) {
|
||||||
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||||
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
||||||
|| ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai))
|
|| ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai))
|
||||||
@@ -805,7 +792,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fell through all the choices, no targets left?
|
// fell through all the choices, no targets left?
|
||||||
if (tcs.getNumTargeted() < tgt.getMinTargets(source, sa) || tcs.getNumTargeted() == 0) {
|
if (tcs.size() < tgt.getMinTargets(source, sa) || tcs.size() == 0) {
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
@@ -896,7 +883,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ai.getWeakestOpponent();
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
while (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
if (tgt.canTgtPlaneswalker()) {
|
if (tgt.canTgtPlaneswalker()) {
|
||||||
final Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, ai, true);
|
final Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, ai, true);
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
@@ -990,7 +977,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(dmg));
|
sa.setSVar("PayX", Integer.toString(dmg));
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
@@ -1002,7 +989,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (damage.equals("X") && source.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) {
|
||||||
// If I can kill my target by paying less mana, do it
|
// If I can kill my target by paying less mana, do it
|
||||||
int actualPay = 0;
|
int actualPay = 0;
|
||||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||||
@@ -1018,7 +1005,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source.setSVar("PayX", Integer.toString(actualPay));
|
sa.setSVar("PayX", Integer.toString(actualPay));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1078,7 +1065,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
saTgt.resetTargets();
|
saTgt.resetTargets();
|
||||||
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
|
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
|
||||||
|
|
||||||
source.setSVar("PayX", Integer.toString(dmg));
|
sa.setSVar("PayX", Integer.toString(dmg));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,15 +139,15 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||||
|
|
||||||
for (final Card c : combatants) {
|
for (final Card c : combatants) {
|
||||||
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) {
|
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.size() < tgt.getMaxTargets(hostCard, sa)) {
|
||||||
tcs.add(c);
|
tcs.add(c);
|
||||||
chance = true;
|
chance = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
|
if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().isEmpty()) {
|
||||||
tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
tgt.addDividedAllocation(sa.getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance;
|
return chance;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class DamagePreventAllAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
|
if (!sa.usesTargeting()) {
|
||||||
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
if (!sa.usesTargeting()) {
|
||||||
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
|
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
|
||||||
// here?
|
// here?
|
||||||
} else {
|
} else {
|
||||||
@@ -138,12 +138,12 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
|
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
Card t = null;
|
Card t = null;
|
||||||
// boolean goodt = false;
|
// boolean goodt = false;
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return debuffMandatoryTarget(ai, sa, mandatory);
|
return debuffMandatoryTarget(ai, sa, mandatory);
|
||||||
}
|
}
|
||||||
@@ -220,7 +220,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
final CardCollection forced = CardLists.filterControlledBy(list, ai);
|
final CardCollection forced = CardLists.filterControlledBy(list, ai);
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) {
|
||||||
if (pref.isEmpty()) {
|
if (pref.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -237,7 +237,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
while (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
if (forced.isEmpty()) {
|
if (forced.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.AiController;
|
import com.google.common.base.Predicate;
|
||||||
import forge.ai.AiPlayDecision;
|
import forge.ai.*;
|
||||||
import forge.ai.PlayerControllerAi;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.ai.SpellApiToAi;
|
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class DelayedTriggerAi extends SpellAbilityAi {
|
public class DelayedTriggerAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -53,6 +57,92 @@ public class DelayedTriggerAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
// Card-specific logic
|
||||||
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
if (logic.equals("SpellCopy")) {
|
||||||
|
// fetch Instant or Sorcery and AI has reason to play this turn
|
||||||
|
// does not try to get itself
|
||||||
|
final ManaCost costSa = sa.getPayCosts().getTotalMana();
|
||||||
|
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
if (!(c.isInstant() || c.isSorcery()) || c.equals(sa.getHostCard())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (SpellAbility ab : c.getSpellAbilities()) {
|
||||||
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(ComputerUtilAbility.getAbilitySourceName(ab))
|
||||||
|
|| ab.hasParam("AINoRecursiveCheck")) {
|
||||||
|
// prevent infinitely recursing mana ritual and other abilities with reentry
|
||||||
|
continue;
|
||||||
|
} else if ("SpellCopy".equals(ab.getParam("AILogic")) && ab.getApi() == ApiType.DelayedTrigger) {
|
||||||
|
// don't copy another copy spell, too complex for the AI
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!ab.canPlay()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(ab);
|
||||||
|
// see if we can pay both for this spell and for the Effect spell we're considering
|
||||||
|
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
|
||||||
|
ManaCost costAb = ab.getPayCosts().getTotalMana();
|
||||||
|
ManaCost total = ManaCost.combine(costSa, costAb);
|
||||||
|
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
|
||||||
|
// can we pay both costs?
|
||||||
|
if (ComputerUtilMana.canPayManaCost(combinedAb, ai, 0)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(count == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (logic.equals("NarsetRebound")) {
|
||||||
|
// should be done in Main2, but it might broke for other cards
|
||||||
|
//if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
|
// return false;
|
||||||
|
//}
|
||||||
|
|
||||||
|
// fetch Instant or Sorcery without Rebound and AI has reason to play this turn
|
||||||
|
// only need count, not the list
|
||||||
|
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
if (!(c.isInstant() || c.isSorcery()) || c.hasKeyword(Keyword.REBOUND)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (SpellAbility ab : c.getSpellAbilities()) {
|
||||||
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(ComputerUtilAbility.getAbilitySourceName(ab))
|
||||||
|
|| ab.hasParam("AINoRecursiveCheck")) {
|
||||||
|
// prevent infinitely recursing mana ritual and other abilities with reentry
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!ab.canPlay()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
AiPlayDecision decision = ((PlayerControllerAi) ai.getController()).getAi().canPlaySa(ab);
|
||||||
|
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
|
||||||
|
if (ComputerUtilMana.canPayManaCost(ab, ai, 0)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic logic
|
||||||
SpellAbility trigsa = null;
|
SpellAbility trigsa = null;
|
||||||
if (sa.hasAdditionalAbility("Execute")) {
|
if (sa.hasAdditionalAbility("Execute")) {
|
||||||
trigsa = sa.getAdditionalAbility("Execute");
|
trigsa = sa.getAdditionalAbility("Execute");
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class DestroyAi extends SpellAbilityAi {
|
public class DestroyAi extends SpellAbilityAi {
|
||||||
@@ -23,89 +22,19 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
// AI needs to be expanded, since this function can be pretty complex
|
|
||||||
// based on what the expected targets could be
|
|
||||||
final Cost abCost = sa.getPayCosts();
|
|
||||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final boolean noRegen = sa.hasParam("NoRegen");
|
if (sa.usesTargeting()) {
|
||||||
final String logic = sa.getParam("AILogic");
|
|
||||||
boolean hasXCost = false;
|
|
||||||
|
|
||||||
CardCollection list;
|
|
||||||
|
|
||||||
if (abCost != null) {
|
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasXCost = abCost.getCostMana() != null && abCost.getCostMana().getAmountOfX() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("AtOpponentsCombatOrAfter".equals(sa.getParam("AILogic"))) {
|
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
|
||||||
if (ph.getPlayerTurn() == ai || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if ("AtEOT".equals(sa.getParam("AILogic"))) {
|
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
|
||||||
if (!ph.is(PhaseType.END_OF_TURN)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) {
|
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
|
||||||
if (!ph.is(PhaseType.END_OF_TURN) || !ai.getCreaturesAttackedThisTurn().isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ability that's intended to destroy own useless token to trigger Grave Pacts
|
|
||||||
// should be fired at end of turn or when under attack after blocking to make opponent sac something
|
|
||||||
boolean havepact = false;
|
|
||||||
|
|
||||||
// TODO replace it with look for a dies -> sacrifice trigger check
|
|
||||||
havepact |= ai.isCardInPlay("Grave Pact");
|
|
||||||
havepact |= ai.isCardInPlay("Butcher of Malakir");
|
|
||||||
havepact |= ai.isCardInPlay("Dictate of Erebos");
|
|
||||||
if ("Pactivator".equals(logic) && havepact) {
|
|
||||||
if ((!ai.getGame().getPhaseHandler().isPlayerTurn(ai))
|
|
||||||
&& ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)) || (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)))
|
|
||||||
&& (ai.getOpponents().getCreaturesInPlay().size() > 0)) {
|
|
||||||
ai.getController().chooseTargetsFor(sa);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Targeting
|
|
||||||
if (abTgt != null) {
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.hasParam("TargetingPlayer")) {
|
if ("MadSarkhanDragon".equals(aiLogic)) {
|
||||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
|
||||||
sa.setTargetingPlayer(targetingPlayer);
|
|
||||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
|
||||||
}
|
|
||||||
if ("MadSarkhanDragon".equals(logic)) {
|
|
||||||
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
|
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
|
||||||
} else if (logic != null && logic.startsWith("MinLoyalty.")) {
|
} else if (aiLogic.startsWith("MinLoyalty.")) {
|
||||||
int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
int minLoyalty = Integer.parseInt(aiLogic.substring(aiLogic.indexOf(".") + 1));
|
||||||
if (source.getCounters(CounterEnumType.LOYALTY) < minLoyalty) {
|
if (source.getCounters(CounterEnumType.LOYALTY) < minLoyalty) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if ("Polymorph".equals(logic)) {
|
} else if ("Polymorph".equals(aiLogic)) {
|
||||||
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -124,7 +53,103 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
sa.getTargets().add(worst);
|
sa.getTargets().add(worst);
|
||||||
return true;
|
return true;
|
||||||
|
} else if ("Pongify".equals(aiLogic)) {
|
||||||
|
return SpecialAiLogic.doPongifyLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return super.checkAiLogic(ai, sa, aiLogic);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph,
|
||||||
|
final String logic) {
|
||||||
|
if ("AtOpponentsCombatOrAfter".equals(logic)) {
|
||||||
|
if (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if ("AtEOT".equals(logic)) {
|
||||||
|
if (!ph.is(PhaseType.END_OF_TURN)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if ("AtEOTIfNotAttacking".equals(logic)) {
|
||||||
|
if (!ph.is(PhaseType.END_OF_TURN) || !ai.getCreaturesAttackedThisTurn().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if ("Pactivator".equals(logic)) {
|
||||||
|
// Ability that's intended to destroy own useless token to trigger Grave Pacts
|
||||||
|
// should be fired at end of turn or when under attack after blocking to make opponent sac something
|
||||||
|
boolean havepact = false;
|
||||||
|
|
||||||
|
// TODO replace it with look for a dies -> sacrifice trigger check
|
||||||
|
havepact |= ai.isCardInPlay("Grave Pact");
|
||||||
|
havepact |= ai.isCardInPlay("Butcher of Malakir");
|
||||||
|
havepact |= ai.isCardInPlay("Dictate of Erebos");
|
||||||
|
if (havepact) {
|
||||||
|
if ((!ph.isPlayerTurn(ai))
|
||||||
|
&& ((ph.is(PhaseType.END_OF_TURN)) || (ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)))
|
||||||
|
&& (ai.getOpponents().getCreaturesInPlay().size() > 0)) {
|
||||||
|
CardCollection list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
Card worst = ComputerUtilCard.getWorstAI(list);
|
||||||
|
if (worst != null) {
|
||||||
|
sa.getTargets().add(worst);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final boolean noRegen = sa.hasParam("NoRegen");
|
||||||
|
final String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
|
CardCollection list;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Targeting
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
// Assume there where already enough targets chosen by AI Logic Above
|
||||||
|
if (!sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset targets before AI Logic part
|
||||||
|
sa.resetTargets();
|
||||||
|
int maxTargets;
|
||||||
|
|
||||||
|
if (sa.costHasManaX()) {
|
||||||
|
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
||||||
|
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
|
// need to set XPaid to get the right number for
|
||||||
|
sa.setXManaCostPaid(maxTargets);
|
||||||
|
// need to check for maxTargets
|
||||||
|
maxTargets = Math.min(maxTargets, sa.getMaxTargets());
|
||||||
|
} else {
|
||||||
|
maxTargets = sa.getMaxTargets();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxTargets == 0) {
|
||||||
|
// can't afford X or otherwise target anything
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("TargetingPlayer")) {
|
||||||
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
|
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI doesn't destroy own cards if it isn't defined in AI logic
|
||||||
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
if ("FatalPush".equals(logic)) {
|
if ("FatalPush".equals(logic)) {
|
||||||
final int cmcMax = ai.hasRevolt() ? 4 : 2;
|
final int cmcMax = ai.hasRevolt() ? 4 : 2;
|
||||||
@@ -184,33 +209,12 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int maxTargets = abTgt.getMaxTargets(sa.getHostCard(), sa);
|
|
||||||
|
|
||||||
if (hasXCost) {
|
|
||||||
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
|
||||||
maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa));
|
|
||||||
// X can't be more than the lands we have in our hand for "discard X lands"!
|
|
||||||
if ("ScorchedEarth".equals(logic)) {
|
|
||||||
int lands = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
|
|
||||||
maxTargets = Math.min(maxTargets, lands);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sa.hasParam("AIMaxTgtsCount")) {
|
|
||||||
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified
|
|
||||||
// TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable?
|
|
||||||
maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxTargets == 0) {
|
|
||||||
// can't afford X or otherwise target anything
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// target loop
|
// target loop
|
||||||
while (sa.getTargets().getNumTargeted() < maxTargets) {
|
// TODO use can add more Targets
|
||||||
|
while (sa.getTargets().size() < maxTargets) {
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -222,10 +226,6 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
Card choice = null;
|
Card choice = null;
|
||||||
// If the targets are only of one type, take the best
|
// If the targets are only of one type, take the best
|
||||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||||
if ("Pongify".equals(logic)) {
|
|
||||||
return SpecialAiLogic.doPongifyLogic(ai, sa);
|
|
||||||
}
|
|
||||||
|
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
if ("OppDestroyYours".equals(logic)) {
|
if ("OppDestroyYours".equals(logic)) {
|
||||||
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
||||||
@@ -246,15 +246,14 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||||
}
|
}
|
||||||
//option to hold removal instead only applies for single targeted removal
|
//option to hold removal instead only applies for single targeted removal
|
||||||
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
|
if (!sa.isTrigger() && sa.getMaxTargets() == 1) {
|
||||||
if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -298,22 +297,19 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
final boolean noRegen = sa.hasParam("NoRegen");
|
final boolean noRegen = sa.hasParam("NoRegen");
|
||||||
if (tgt != null) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
|
||||||
|
if (list.isEmpty() || list.size() < sa.getMinTargets()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Try to avoid targeting creatures that are dead on board
|
// Try to avoid targeting creatures that are dead on board
|
||||||
list = ComputerUtil.filterCreaturesThatWillDieThisTurn(ai, list, sa);
|
list = ComputerUtil.filterCreaturesThatWillDieThisTurn(ai, list, sa);
|
||||||
|
|
||||||
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CardCollection preferred = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
|
CardCollection preferred = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
|
||||||
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
|
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
|
||||||
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
||||||
@@ -344,10 +340,9 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (preferred.isEmpty()) {
|
if (preferred.isEmpty()) {
|
||||||
if (sa.getTargets().getNumTargeted() == 0
|
if (!sa.isMinTargetChosen()) {
|
||||||
|| sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
@@ -371,7 +366,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
@@ -392,7 +387,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getTargets().getNumTargeted() >= tgt.getMinTargets(sa.getHostCard(), sa);
|
return sa.isTargetNumberValid();
|
||||||
} else {
|
} else {
|
||||||
return mandatory;
|
return mandatory;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.base.Predicates;
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
|
import forge.card.MagicColor;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.combat.Combat;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.game.combat.Combat;
|
|
||||||
|
|
||||||
public class DestroyAllAi extends SpellAbilityAi {
|
public class DestroyAllAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -78,10 +80,10 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
valid = sa.getParam("ValidCards");
|
valid = sa.getParam("ValidCards");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
valid = valid.replace("X", Integer.toString(xPay));
|
valid = valid.replace("X", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +108,14 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special handling for Raiding Party
|
||||||
|
if (logic.equals("RaidingParty")) {
|
||||||
|
int numAiCanSave = Math.min(CardLists.filter(ai.getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, ailist.size());
|
||||||
|
int numOppsCanSave = Math.min(CardLists.filter(ai.getOpponents().getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, opplist.size());
|
||||||
|
|
||||||
|
return numOppsCanSave < opplist.size() && (ailist.size() - numAiCanSave < opplist.size() - numOppsCanSave);
|
||||||
|
}
|
||||||
|
|
||||||
// If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what!
|
// If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what!
|
||||||
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
||||||
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
|
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
|
|
||||||
|
if (!willPayCosts(ai, sa, sa.getPayCosts(), host)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
@@ -71,9 +75,9 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final String num = sa.getParam("DigNum");
|
final String num = sa.getParam("DigNum");
|
||||||
final boolean payXLogic = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayX");
|
final boolean payXLogic = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayX");
|
||||||
if (num != null && (num.equals("X") && host.getSVar(num).equals("Count$xPaid")) || payXLogic) {
|
if (num != null && (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) || payXLogic) {
|
||||||
// By default, set PayX here to maximum value.
|
// By default, set PayX here to maximum value.
|
||||||
if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) {
|
if (!(sa instanceof AbilitySub) || sa.getSVar("PayX").equals("")) {
|
||||||
int manaToSave = 0;
|
int manaToSave = 0;
|
||||||
|
|
||||||
// Special logic that asks the AI to conserve a certain amount of mana when paying X
|
// Special logic that asks the AI to conserve a certain amount of mana when paying X
|
||||||
@@ -85,7 +89,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
if (numCards <= 0) {
|
if (numCards <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
host.setSVar("PayX", Integer.toString(numCards));
|
sa.setSVar("PayX", Integer.toString(numCards));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,14 +76,14 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String num = sa.getParam("Amount");
|
final String num = sa.getParam("Amount");
|
||||||
if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
|
if ((num != null) && num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) {
|
if (!(sa instanceof AbilitySub) || sa.getSVar("PayX").equals("")) {
|
||||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
if (numCards <= 0) {
|
if (numCards <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
source.setSVar("PayX", Integer.toString(numCards));
|
sa.setSVar("PayX", Integer.toString(numCards));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,14 +85,14 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("NumCards")) {
|
if (sa.hasParam("NumCards")) {
|
||||||
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (sa.getParam("NumCards").equals("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getWeakestOpponent()
|
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getWeakestOpponent()
|
||||||
.getCardsIn(ZoneType.Hand).size());
|
.getCardsIn(ZoneType.Hand).size());
|
||||||
if (cardsToDiscard < 1) {
|
if (cardsToDiscard < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
sa.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||||
} else {
|
} else {
|
||||||
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
|
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ import forge.game.Game;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.card.CardPredicates;
|
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.cost.*;
|
import forge.game.cost.*;
|
||||||
@@ -180,8 +178,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int numHand = ai.getCardsIn(ZoneType.Hand).size();
|
int numHand = ai.getCardsIn(ZoneType.Hand).size();
|
||||||
if ("Jace, Vryn's Prodigy".equals(sourceName) && ai.getCardsIn(ZoneType.Graveyard).size() > 3) {
|
if ("Jace, Vryn's Prodigy".equals(sourceName) && ai.getCardsIn(ZoneType.Graveyard).size() > 3) {
|
||||||
return CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS,
|
return !ai.isCardInPlay("Jace, Telepath Unbound");
|
||||||
CardPredicates.isType("Jace")).size() <= 0;
|
|
||||||
}
|
}
|
||||||
if (source.isSpell() && ai.getCardsIn(ZoneType.Hand).contains(source)) {
|
if (source.isSpell() && ai.getCardsIn(ZoneType.Hand).contains(source)) {
|
||||||
numHand--; // remember to count looter card if it is a spell in hand
|
numHand--; // remember to count looter card if it is a spell in hand
|
||||||
@@ -222,6 +219,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size();
|
final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size();
|
||||||
final int computerMaxHandSize = ai.getMaxHandSize();
|
final int computerMaxHandSize = ai.getMaxHandSize();
|
||||||
|
|
||||||
|
final SpellAbility gainLife = sa.findSubAbilityByType(ApiType.GainLife);
|
||||||
final SpellAbility loseLife = sa.findSubAbilityByType(ApiType.LoseLife);
|
final SpellAbility loseLife = sa.findSubAbilityByType(ApiType.LoseLife);
|
||||||
final SpellAbility getPoison = sa.findSubAbilityByType(ApiType.Poison);
|
final SpellAbility getPoison = sa.findSubAbilityByType(ApiType.Poison);
|
||||||
|
|
||||||
@@ -238,22 +236,21 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
boolean xPaid = false;
|
boolean xPaid = false;
|
||||||
final String num = sa.getParam("NumCards");
|
final String num = sa.getParam("NumCards");
|
||||||
if (num != null && num.equals("X")) {
|
if (num != null && num.equals("X")) {
|
||||||
if (source.getSVar(num).equals("Count$xPaid")) {
|
if (sa.getSVar(num).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
if (drawback && !source.getSVar("PayX").equals("")) {
|
if (drawback && !sa.getSVar("PayX").equals("")) {
|
||||||
numCards = Integer.parseInt(source.getSVar("PayX"));
|
numCards = Integer.parseInt(sa.getSVar("PayX"));
|
||||||
} else {
|
} else {
|
||||||
numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
numCards = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
// try not to overdraw
|
// try not to overdraw
|
||||||
int safeDraw = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
|
int safeDraw = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
|
||||||
if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent
|
if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent
|
||||||
numCards = Math.min(numCards, safeDraw);
|
numCards = Math.min(numCards, safeDraw);
|
||||||
source.setSVar("PayX", Integer.toString(numCards));
|
sa.setSVar("PayX", Integer.toString(numCards));
|
||||||
assumeSafeX = true;
|
assumeSafeX = true;
|
||||||
}
|
}
|
||||||
xPaid = true;
|
xPaid = true;
|
||||||
}
|
} else if (sa.getSVar(num).equals("Count$Converge")) {
|
||||||
if (sa.getSVar(num).equals("Count$Converge")) {
|
|
||||||
numCards = ComputerUtilMana.getConvergeCount(sa, ai);
|
numCards = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,14 +267,6 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
|
while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
|
||||||
numCards--;
|
numCards--;
|
||||||
}
|
}
|
||||||
} else if (sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
|
||||||
// [e.g. Krav, the Unredeemed and other cases which say "Sacrifice X creatures: draw X cards]
|
|
||||||
// TODO: Add special logic to limit/otherwise modify the ChosenX value here
|
|
||||||
|
|
||||||
// Skip this ability if nothing is to be chosen for sacrifice
|
|
||||||
if (numCards <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.setSVar("ChosenX", Integer.toString(numCards));
|
sa.setSVar("ChosenX", Integer.toString(numCards));
|
||||||
@@ -340,13 +329,27 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
// for drawing and losing life
|
// for drawing and losing life
|
||||||
if (numCards >= oppA.getLife()) {
|
if (numCards >= oppA.getLife()) {
|
||||||
if (xPaid) {
|
if (xPaid) {
|
||||||
source.setSVar("PayX", Integer.toString(oppA.getLife()));
|
sa.setSVar("PayX", Integer.toString(oppA.getLife()));
|
||||||
}
|
}
|
||||||
sa.getTargets().add(oppA);
|
sa.getTargets().add(oppA);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// that opponent can gain life and also lose life and that life gain is negative
|
||||||
|
if (gainLife != null && oppA.canGainLife() && oppA.canLoseLife() && ComputerUtil.lifegainNegative(oppA, source)) {
|
||||||
|
if (gainLife.hasParam("Defined") && "Targeted".equals(gainLife.getParam("Defined"))) {
|
||||||
|
if (numCards >= oppA.getLife()) {
|
||||||
|
if (xPaid) {
|
||||||
|
sa.setSVar("PayX", Integer.toString(oppA.getLife()));
|
||||||
|
}
|
||||||
|
sa.getTargets().add(oppA);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// try to make opponent lose to poison
|
// try to make opponent lose to poison
|
||||||
// currently only Caress of Phyrexia
|
// currently only Caress of Phyrexia
|
||||||
if (getPoison != null && oppA.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
if (getPoison != null && oppA.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
@@ -400,7 +403,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (xPaid) {
|
if (xPaid) {
|
||||||
source.setSVar("PayX", Integer.toString(numCards));
|
sa.setSVar("PayX", Integer.toString(numCards));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +414,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
if (sa.getHostCard().isInZone(ZoneType.Hand)) {
|
if (sa.getHostCard().isInZone(ZoneType.Hand)) {
|
||||||
numCards++; // the card will be spent
|
numCards++; // the card will be spent
|
||||||
}
|
}
|
||||||
source.setSVar("PayX", Integer.toString(numCards));
|
sa.setSVar("PayX", Integer.toString(numCards));
|
||||||
} else {
|
} else {
|
||||||
// Don't draw too many cards and then risk discarding
|
// Don't draw too many cards and then risk discarding
|
||||||
// cards at EOT
|
// cards at EOT
|
||||||
|
|||||||
@@ -7,14 +7,11 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.card.mana.ManaCost;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.cost.Cost;
|
|
||||||
import forge.game.keyword.Keyword;
|
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -109,89 +106,6 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
|| CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Planeswalker").isEmpty()) {
|
|| CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Planeswalker").isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
randomReturn = true;
|
|
||||||
} else if (logic.equals("SpellCopy")) {
|
|
||||||
// fetch Instant or Sorcery and AI has reason to play this turn
|
|
||||||
// does not try to get itself
|
|
||||||
final ManaCost costSa = sa.getPayCosts().getTotalMana();
|
|
||||||
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(final Card c) {
|
|
||||||
if (!(c.isInstant() || c.isSorcery()) || c.equals(sa.getHostCard())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (SpellAbility ab : c.getSpellAbilities()) {
|
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(ComputerUtilAbility.getAbilitySourceName(ab))
|
|
||||||
|| ab.hasParam("AINoRecursiveCheck")) {
|
|
||||||
// prevent infinitely recursing mana ritual and other abilities with reentry
|
|
||||||
continue;
|
|
||||||
} else if ("SpellCopy".equals(ab.getParam("AILogic")) && ab.getApi() == ApiType.Effect) {
|
|
||||||
// don't copy another copy spell, too complex for the AI
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!ab.canPlay()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(ab);
|
|
||||||
// see if we can pay both for this spell and for the Effect spell we're considering
|
|
||||||
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
|
|
||||||
ManaCost costAb = ab.getPayCosts().getTotalMana();
|
|
||||||
ManaCost total = ManaCost.combine(costSa, costAb);
|
|
||||||
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
|
|
||||||
// can we pay both costs?
|
|
||||||
if (ComputerUtilMana.canPayManaCost(combinedAb, ai, 0)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(count == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
randomReturn = true;
|
|
||||||
} else if (logic.equals("NarsetRebound")) {
|
|
||||||
// should be done in Main2, but it might broke for other cards
|
|
||||||
//if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
|
||||||
// return false;
|
|
||||||
//}
|
|
||||||
|
|
||||||
// fetch Instant or Sorcery without Rebound and AI has reason to play this turn
|
|
||||||
// only need count, not the list
|
|
||||||
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(final Card c) {
|
|
||||||
if (!(c.isInstant() || c.isSorcery()) || c.hasKeyword(Keyword.REBOUND)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (SpellAbility ab : c.getSpellAbilities()) {
|
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(ComputerUtilAbility.getAbilitySourceName(ab))
|
|
||||||
|| ab.hasParam("AINoRecursiveCheck")) {
|
|
||||||
// prevent infinitely recursing mana ritual and other abilities with reentry
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!ab.canPlay()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(ab);
|
|
||||||
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
|
|
||||||
if (ComputerUtilMana.canPayManaCost(ab, ai, 0)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(count == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
randomReturn = true;
|
randomReturn = true;
|
||||||
} else if (logic.equals("Always")) {
|
} else if (logic.equals("Always")) {
|
||||||
randomReturn = true;
|
randomReturn = true;
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.*;
|
||||||
|
import forge.game.ability.AbilityFactory;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
public class ImmediateTriggerAi extends SpellAbilityAi {
|
||||||
|
// TODO: this class is largely reused from DelayedTriggerAi, consider updating
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
if (logic.equals("Always")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpellAbility trigsa = null;
|
||||||
|
if (sa.hasAdditionalAbility("Execute")) {
|
||||||
|
trigsa = sa.getAdditionalAbility("Execute");
|
||||||
|
} else {
|
||||||
|
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||||
|
}
|
||||||
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
|
if (trigsa instanceof AbilitySub) {
|
||||||
|
return SpellApiToAi.Converter.get(trigsa.getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
||||||
|
} else {
|
||||||
|
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
SpellAbility trigsa = null;
|
||||||
|
if (sa.hasAdditionalAbility("Execute")) {
|
||||||
|
trigsa = sa.getAdditionalAbility("Execute");
|
||||||
|
} else {
|
||||||
|
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||||
|
}
|
||||||
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
|
if (!sa.hasParam("OptionalDecider")) {
|
||||||
|
return aic.doTrigger(trigsa, true);
|
||||||
|
} else {
|
||||||
|
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
if (logic.equals("Always")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpellAbility trigsa = null;
|
||||||
|
if (sa.hasAdditionalAbility("Execute")) {
|
||||||
|
trigsa = sa.getAdditionalAbility("Execute");
|
||||||
|
} else {
|
||||||
|
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||||
|
}
|
||||||
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -125,10 +125,10 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
int lifeAmount = 0;
|
int lifeAmount = 0;
|
||||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
lifeAmount = xPay;
|
lifeAmount = xPay;
|
||||||
} else {
|
} else {
|
||||||
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
@@ -213,12 +213,11 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -34,14 +34,14 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
int amount = 0;
|
int amount = 0;
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
// something already set PayX
|
// something already set PayX
|
||||||
if (source.hasSVar("PayX")) {
|
if (sa.hasSVar("PayX")) {
|
||||||
amount = Integer.parseInt(source.getSVar("PayX"));
|
amount = Integer.parseInt(sa.getSVar("PayX"));
|
||||||
} else {
|
} else {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
amount = xPay;
|
amount = xPay;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -72,10 +72,9 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
int amount = 0;
|
int amount = 0;
|
||||||
|
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
// source.setSVar("PayX", Integer.toString(amount));
|
|
||||||
} else {
|
} else {
|
||||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
}
|
}
|
||||||
@@ -101,10 +100,10 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
int amount = 0;
|
int amount = 0;
|
||||||
|
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(amount));
|
sa.setSVar("PayX", Integer.toString(amount));
|
||||||
} else {
|
} else {
|
||||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
}
|
}
|
||||||
@@ -172,10 +171,10 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
int amount = 0;
|
int amount = 0;
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
amount = xPay;
|
amount = xPay;
|
||||||
} else {
|
} else {
|
||||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
// Ability_Cost abCost = sa.getPayCosts();
|
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
final int myLife = ai.getLife();
|
final int myLife = ai.getLife();
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = ai.getWeakestOpponent();
|
||||||
final int hlife = opponent.getLife();
|
final int hlife = opponent.getLife();
|
||||||
@@ -42,10 +40,10 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
// would be paid
|
// would be paid
|
||||||
int amount;
|
int amount;
|
||||||
// we shouldn't have to worry too much about PayX for SetLife
|
// we shouldn't have to worry too much about PayX for SetLife
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
amount = xPay;
|
amount = xPay;
|
||||||
} else {
|
} else {
|
||||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
@@ -114,10 +112,10 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
|
|
||||||
int amount;
|
int amount;
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
amount = xPay;
|
amount = xPay;
|
||||||
} else {
|
} else {
|
||||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
|
|||||||
@@ -118,8 +118,7 @@ public class ManaEffectAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int numCounters = 0;
|
int numCounters = 0;
|
||||||
int manaSurplus = 0;
|
int manaSurplus = 0;
|
||||||
if ("XChoice".equals(host.getSVar("X"))
|
if ("Count$xPaid".equals(host.getSVar("X")) && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
||||||
&& sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
|
||||||
CounterType ctrType = CounterType.get(CounterEnumType.KI); // Petalmane Baku
|
CounterType ctrType = CounterType.get(CounterEnumType.KI); // Petalmane Baku
|
||||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||||
if (part instanceof CostRemoveCounter) {
|
if (part instanceof CostRemoveCounter) {
|
||||||
@@ -206,7 +205,9 @@ public class ManaEffectAi extends SpellAbilityAi {
|
|||||||
// Don't remove more counters than would be needed to cast the more expensive thing we want to cast,
|
// Don't remove more counters than would be needed to cast the more expensive thing we want to cast,
|
||||||
// otherwise the AI grabs too many counters at once.
|
// otherwise the AI grabs too many counters at once.
|
||||||
int maxCtrs = Aggregates.max(castableSpells, CardPredicates.Accessors.fnGetCmc) - manaSurplus;
|
int maxCtrs = Aggregates.max(castableSpells, CardPredicates.Accessors.fnGetCmc) - manaSurplus;
|
||||||
sa.setSVar("ChosenX", "Number$" + Math.min(numCounters, maxCtrs));
|
int min = Math.min(numCounters, maxCtrs);
|
||||||
|
sa.setXManaCostPaid(min);
|
||||||
|
sa.setSVar("PayX", Integer.toString(min));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this will probably still waste the card from time to time. Somehow improve detection of castable material.
|
// TODO: this will probably still waste the card from time to time. Somehow improve detection of castable material.
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ public class ManifestAi extends SpellAbilityAi {
|
|||||||
// Handle either Manifest X cards, or Manifest 1 card and give it X P1P1s
|
// Handle either Manifest X cards, or Manifest 1 card and give it X P1P1s
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(x));
|
sa.setSVar("PayX", Integer.toString(x));
|
||||||
if (x <= 0) {
|
if (x <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
* - check for Laboratory Maniac effect (needs to check for actual
|
* - check for Laboratory Maniac effect (needs to check for actual
|
||||||
* effect due to possibility of "lose abilities" effect)
|
* effect due to possibility of "lose abilities" effect)
|
||||||
*/
|
*/
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false; // prevents mill 0 infinite loop?
|
return false; // prevents mill 0 infinite loop?
|
||||||
}
|
}
|
||||||
@@ -90,10 +89,10 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z"))
|
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z"))
|
||||||
&& source.getSVar("X").startsWith("Count$xPaid")) {
|
&& sa.getSVar("X").startsWith("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int cardsToDiscard = getNumToDiscard(ai, sa);
|
final int cardsToDiscard = getNumToDiscard(ai, sa);
|
||||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
sa.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||||
return cardsToDiscard > 0;
|
return cardsToDiscard > 0;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -182,11 +181,10 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
if (sa.getParam("NumCards").equals("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int cardsToDiscard = getNumToDiscard(aiPlayer, sa);
|
final int cardsToDiscard = getNumToDiscard(aiPlayer, sa);
|
||||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
sa.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.ai.SpellApiToAi;
|
import forge.ai.SpellApiToAi;
|
||||||
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
@@ -23,10 +24,17 @@ public class PeekAndRevealAi extends SpellAbilityAi {
|
|||||||
if (sa instanceof AbilityStatic) {
|
if (sa instanceof AbilityStatic) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ("Main2".equals(sa.getParam("AILogic"))) {
|
|
||||||
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
if ("Main2".equals(logic)) {
|
||||||
if (aiPlayer.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
if (aiPlayer.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if ("EndOfOppTurn".equals(logic)) {
|
||||||
|
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||||
|
if (!(ph.getNextTurn() == aiPlayer && ph.is(PhaseType.END_OF_TURN))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// So far this only appears on Triggers, but will expand
|
// So far this only appears on Triggers, but will expand
|
||||||
// once things get converted from Dig + NoMove
|
// once things get converted from Dig + NoMove
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
ManaCost mana = sa.getPayCosts().getTotalMana();
|
ManaCost mana = sa.getPayCosts().getTotalMana();
|
||||||
if (mana.countX() > 0) {
|
if (mana.countX() > 0) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
if (source.hasConverge()) {
|
if (source.hasConverge()) {
|
||||||
card.setSVar("PayX", Integer.toString(0));
|
card.setSVar("PayX", Integer.toString(0));
|
||||||
@@ -134,6 +134,20 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("SacToReduceCost".equals(sa.getParam("AILogic"))) {
|
||||||
|
// reset X to better calculate
|
||||||
|
sa.setXManaCostPaid(0);
|
||||||
|
ManaCostBeingPaid paidCost = ComputerUtilMana.calculateManaCost(sa, true, 0);
|
||||||
|
|
||||||
|
int generic = paidCost.getGenericManaAmount();
|
||||||
|
// Set PayX here to maximum value.
|
||||||
|
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
|
// currently cards with SacToReduceCost reduce by 2 generic
|
||||||
|
xPay = Math.min(xPay, generic / 2);
|
||||||
|
card.setSVar("PayX", Integer.toString(xPay));
|
||||||
|
sa.setXManaCostPaid(xPay);
|
||||||
|
}
|
||||||
|
|
||||||
if (sa.hasParam("Announce") && sa.getParam("Announce").startsWith("Multikicker")) {
|
if (sa.hasParam("Announce") && sa.getParam("Announce").startsWith("Multikicker")) {
|
||||||
// String announce = sa.getParam("Announce");
|
// String announce = sa.getParam("Announce");
|
||||||
ManaCost mkCost = sa.getMultiKickerManaCost();
|
ManaCost mkCost = sa.getMultiKickerManaCost();
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
if (ai.getController().isAI()) {
|
if (ai.getController().isAI()) {
|
||||||
advancedFlash = ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.FLASH_ENABLE_ADVANCED_LOGIC);
|
advancedFlash = ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.FLASH_ENABLE_ADVANCED_LOGIC);
|
||||||
}
|
}
|
||||||
if (card.withFlash(ai)) {
|
if (!ai.canCastSorcery() && sa.canCastTiming(ai)) {
|
||||||
if (advancedFlash) {
|
if (advancedFlash) {
|
||||||
return doAdvancedFlashLogic(card, ai, sa);
|
return doAdvancedFlashLogic(card, ai, sa);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
if (!sa.usesTargeting()) {
|
||||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
if (cards.size() == 0) {
|
if (cards.size() == 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -226,12 +226,12 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
return mandatory && protectMandatoryTarget(ai, sa, mandatory);
|
return mandatory && protectMandatoryTarget(ai, sa, mandatory);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) {
|
||||||
Card t = null;
|
Card t = null;
|
||||||
// boolean goodt = false;
|
// boolean goodt = false;
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) || sa.getTargets().getNumTargeted() == 0) {
|
if ((sa.getTargets().size() < tgt.getMinTargets(source, sa)) || sa.getTargets().size() == 0) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return protectMandatoryTarget(ai, sa, mandatory);
|
return protectMandatoryTarget(ai, sa, mandatory);
|
||||||
}
|
}
|
||||||
@@ -285,7 +285,7 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
final List<Card> forced = CardLists.filterControlledBy(list, ai);
|
final List<Card> forced = CardLists.filterControlledBy(list, ai);
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) {
|
||||||
if (pref.isEmpty()) {
|
if (pref.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -302,7 +302,7 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) {
|
||||||
if (pref2.isEmpty()) {
|
if (pref2.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -319,7 +319,7 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) {
|
while (sa.getTargets().size() < tgt.getMinTargets(source, sa)) {
|
||||||
if (forced.isEmpty()) {
|
if (forced.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -336,7 +336,7 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -359,12 +359,7 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
final Card host = sa.getHostCard();
|
if (sa.usesTargeting()) {
|
||||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
|
||||||
if (host.isCreature()) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return protectTgtAI(ai, sa, false);
|
return protectTgtAI(ai, sa, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class ProtectAllAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import forge.game.ability.ApiType;
|
|||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostPart;
|
|
||||||
import forge.game.cost.CostRemoveCounter;
|
|
||||||
import forge.game.cost.CostTapType;
|
import forge.game.cost.CostTapType;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
@@ -76,6 +74,12 @@ public class PumpAi extends PumpAiBase {
|
|||||||
if (ai.getCardsIn(ZoneType.Hand).size() + 3 >= ai.getMaxHandSize()) {
|
if (ai.getCardsIn(ZoneType.Hand).size() + 3 >= ai.getMaxHandSize()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (aiLogic.equals("SwitchPT")) {
|
||||||
|
// Some more AI would be even better, but this is a good start to prevent spamming
|
||||||
|
if (sa.isAbility() && sa.getActivationsThisTurn() > 0 && !sa.usesTargeting()) {
|
||||||
|
// Will prevent flipping back and forth
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.checkAiLogic(ai, sa, aiLogic);
|
return super.checkAiLogic(ai, sa, aiLogic);
|
||||||
@@ -96,6 +100,11 @@ public class PumpAi extends PumpAiBase {
|
|||||||
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && !isThreatened) {
|
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && !isThreatened) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (logic.equals("SwitchPT")) {
|
||||||
|
// Some more AI would be even better, but this is a good start to prevent spamming
|
||||||
|
if (ph.getPhase().isAfter(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE) || !ph.inCombat()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||||
}
|
}
|
||||||
@@ -183,7 +192,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
// cant use substract on Copy
|
// cant use substract on Copy
|
||||||
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
||||||
|
|
||||||
if (CounterEnumType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
if (cType.is(CounterEnumType.P1P1) && srcCardCpy.getNetToughness() <= 0) {
|
||||||
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|
||||||
|| card.isToken();
|
|| card.isToken();
|
||||||
}
|
}
|
||||||
@@ -233,7 +242,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
// cant use substract on Copy
|
// cant use substract on Copy
|
||||||
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
||||||
|
|
||||||
if (CounterEnumType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
if (cType.is(CounterEnumType.P1P1) && srcCardCpy.getNetToughness() <= 0) {
|
||||||
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|
||||||
|| card.isToken();
|
|| card.isToken();
|
||||||
}
|
}
|
||||||
@@ -288,19 +297,19 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source.getSVar("X").equals("Count$xPaid")) {
|
if (sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
source.setSVar("PayX", "");
|
sa.setSVar("PayX", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
int defense;
|
int defense;
|
||||||
if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
if (sourceName.equals("Necropolis Fiend")) {
|
if (sourceName.equals("Necropolis Fiend")) {
|
||||||
xPay = Math.min(xPay, sa.getActivatingPlayer().getCardsIn(ZoneType.Graveyard).size());
|
xPay = Math.min(xPay, sa.getActivatingPlayer().getCardsIn(ZoneType.Graveyard).size());
|
||||||
sa.setSVar("X", Integer.toString(xPay));
|
sa.setSVar("X", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
defense = xPay;
|
defense = xPay;
|
||||||
if (numDefense.equals("-X")) {
|
if (numDefense.equals("-X")) {
|
||||||
defense = -xPay;
|
defense = -xPay;
|
||||||
@@ -313,13 +322,13 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int attack;
|
int attack;
|
||||||
if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final String toPay = source.getSVar("PayX");
|
final String toPay = sa.getSVar("PayX");
|
||||||
|
|
||||||
if (toPay.equals("")) {
|
if (toPay.equals("")) {
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
attack = xPay;
|
attack = xPay;
|
||||||
} else {
|
} else {
|
||||||
attack = Integer.parseInt(toPay);
|
attack = Integer.parseInt(toPay);
|
||||||
@@ -353,7 +362,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Untargeted
|
//Untargeted
|
||||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
if (!sa.usesTargeting()) {
|
||||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
@@ -398,19 +407,10 @@ public class PumpAi extends PumpAiBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("DebuffForXCounters".equals(sa.getParam("AILogic")) && sa.getTargetCard() != null) {
|
if (!sa.getSVar("PayX").isEmpty() && sa.getTargetCard() != null) {
|
||||||
// e.g. Skullmane Baku
|
|
||||||
CounterType ctrType = CounterType.get(CounterEnumType.KI);
|
|
||||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
|
||||||
if (part instanceof CostRemoveCounter) {
|
|
||||||
ctrType = ((CostRemoveCounter)part).counter;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not pay more counters than necessary to kill the targeted creature
|
// Do not pay more counters than necessary to kill the targeted creature
|
||||||
int chosenX = Math.min(source.getCounters(ctrType), sa.getTargetCard().getNetToughness());
|
int chosenX = Math.min(sa.getSVarInt("PayX"), sa.getTargetCard().getNetToughness());
|
||||||
sa.setSVar("ChosenX", String.valueOf(chosenX));
|
sa.setSVar("PayX", String.valueOf(chosenX));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -573,12 +573,12 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
while (sa.canAddMoreTarget()) {
|
||||||
Card t = null;
|
Card t = null;
|
||||||
// boolean goodt = false;
|
// boolean goodt = false;
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa) || sa.getTargets().getNumTargeted() == 0) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
if (mandatory || ComputerUtil.activateForCost(sa, ai)) {
|
if (mandatory || ComputerUtil.activateForCost(sa, ai)) {
|
||||||
return pumpMandatoryTarget(ai, sa);
|
return pumpMandatoryTarget(ai, sa);
|
||||||
}
|
}
|
||||||
@@ -634,7 +634,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
forced = CardLists.filterControlledBy(list, ai.getOpponents());
|
forced = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) {
|
||||||
if (pref.isEmpty()) {
|
if (pref.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -651,7 +651,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) {
|
while (sa.getTargets().size() < tgt.getMinTargets(source, sa)) {
|
||||||
if (forced.isEmpty()) {
|
if (forced.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -668,7 +668,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) {
|
if (sa.getTargets().size() < tgt.getMinTargets(source, sa)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -678,28 +678,27 @@ public class PumpAi extends PumpAiBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : "";
|
final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : "";
|
||||||
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
|
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
|
||||||
|
|
||||||
int defense;
|
int defense;
|
||||||
if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
defense = xPay;
|
defense = xPay;
|
||||||
} else {
|
} else {
|
||||||
defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa);
|
defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
int attack;
|
int attack;
|
||||||
if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final String toPay = source.getSVar("PayX");
|
final String toPay = sa.getSVar("PayX");
|
||||||
|
|
||||||
if (toPay.equals("")) {
|
if (toPay.equals("")) {
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
attack = xPay;
|
attack = xPay;
|
||||||
} else {
|
} else {
|
||||||
attack = Integer.parseInt(toPay);
|
attack = Integer.parseInt(toPay);
|
||||||
@@ -708,7 +707,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa);
|
attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getTargetRestrictions() == null) {
|
if (!sa.usesTargeting()) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -749,28 +748,28 @@ public class PumpAi extends PumpAiBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int defense;
|
|
||||||
if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
|
||||||
defense = Integer.parseInt(source.getSVar("PayX"));
|
|
||||||
} else {
|
|
||||||
defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa);
|
|
||||||
}
|
|
||||||
|
|
||||||
int attack;
|
int attack;
|
||||||
if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
if (source.getSVar("PayX").equals("")) {
|
if (sa.getSVar("PayX").equals("")) {
|
||||||
// X is not set yet
|
// X is not set yet
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa.getRootAbility(), ai);
|
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
attack = xPay;
|
attack = xPay;
|
||||||
} else {
|
} else {
|
||||||
attack = Integer.parseInt(source.getSVar("PayX"));
|
attack = Integer.parseInt(sa.getSVar("PayX"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa);
|
attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
int defense;
|
||||||
|
if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
|
defense = Integer.parseInt(sa.getSVar("PayX"));
|
||||||
|
} else {
|
||||||
|
defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sa.usesTargeting()) {
|
||||||
if (source.isCreature()) {
|
if (source.isCreature()) {
|
||||||
if (!source.hasKeyword(Keyword.INDESTRUCTIBLE) && source.getNetToughness() + defense <= source.getDamage()) {
|
if (!source.hasKeyword(Keyword.INDESTRUCTIBLE) && source.getNetToughness() + defense <= source.getDamage()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -270,7 +270,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
} else if (keyword.endsWith("Haste")) {
|
} else if (keyword.endsWith("Haste")) {
|
||||||
return card.hasSickness() && !ph.isPlayerTurn(opp) && !card.isTapped()
|
return card.hasSickness() && !ph.isPlayerTurn(opp) && !card.isTapped()
|
||||||
&& newPower > 0
|
&& newPower > 0
|
||||||
&& !card.hasKeyword("CARDNAME can attack as though it had haste.")
|
|
||||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& ComputerUtilCombat.canAttackNextTurn(card);
|
&& ComputerUtilCombat.canAttackNextTurn(card);
|
||||||
} else if (keyword.endsWith("Indestructible")) {
|
} else if (keyword.endsWith("Indestructible")) {
|
||||||
|
|||||||
@@ -2,22 +2,18 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
|
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.game.card.Card;
|
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
|
|
||||||
public class RepeatAi extends SpellAbilityAi {
|
public class RepeatAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ai.getWeakestOpponent();
|
||||||
|
|
||||||
if (tgt != null) {
|
if (sa.usesTargeting()) {
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -31,7 +27,7 @@ public class RepeatAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int max = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int max = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(max));
|
sa.setSVar("PayX", Integer.toString(max));
|
||||||
return max > 0;
|
return max > 0;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -93,8 +96,21 @@ public class RepeatEachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// would not hit oppoent, don't do that
|
// would not hit opponent, don't do that
|
||||||
return hitOpp;
|
return hitOpp;
|
||||||
|
} else if ("EquipAll".equals(logic)) {
|
||||||
|
if (aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1, aiPlayer)) {
|
||||||
|
final CardCollection unequipped = CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(Card card) {
|
||||||
|
return card.isEquipment() && card.getAttachedTo() != sa.getHostCard();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return !unequipped.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Add some normal AI variability here
|
// TODO Add some normal AI variability here
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class RevealAi extends RevealAiBase {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// use hard coded reduce cost
|
// use hard coded reduce cost
|
||||||
spell.getMapParams().put("ReduceCost", "2");
|
spell.putParam("ReduceCost", "2");
|
||||||
|
|
||||||
if (AiPlayDecision.WillPlay == ((PlayerControllerAi) ai.getController()).getAi()
|
if (AiPlayDecision.WillPlay == ((PlayerControllerAi) ai.getController()).getAi()
|
||||||
.canPlayFromEffectAI(spell, false, false)) {
|
.canPlayFromEffectAI(spell, false, false)) {
|
||||||
|
|||||||
@@ -103,10 +103,10 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
|
if (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount);
|
final int xPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
final int half = (amount / 2) + (amount % 2); // Half of amount
|
final int half = (amount / 2) + (amount % 2); // Half of amount
|
||||||
@@ -135,7 +135,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
|
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
|
||||||
int amount = AbilityUtils.calculateAmount(source, num, sa);
|
int amount = AbilityUtils.calculateAmount(source, num, sa);
|
||||||
|
|
||||||
if (num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
|
if (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
amount = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount);
|
amount = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
|||||||
valid = sa.getParam("ValidCards");
|
valid = sa.getParam("ValidCards");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
valid = TextUtil.fastReplace(valid, "X", Integer.toString(xPay));
|
valid = TextUtil.fastReplace(valid, "X", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
// has spell that can be cast if one counter is removed
|
// has spell that can be cast if one counter is removed
|
||||||
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum)).isEmpty()) {
|
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum)).isEmpty()) {
|
||||||
sa.setSVar("ChosenX", "Number$1");
|
sa.setSVar("PayX", "1");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,10 +142,10 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
if (maxToRemove <= 0) {
|
if (maxToRemove <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
sa.setSVar("ChosenX", "Number$" + maxToRemove);
|
sa.setSVar("PayX", String.valueOf(maxToRemove));
|
||||||
} else {
|
} else {
|
||||||
// no Instant or Sorceries anymore, just scry
|
// no Instant or Sorceries anymore, just scry
|
||||||
sa.setSVar("ChosenX", "Number$" + Math.min(counterNum, libsize));
|
sa.setSVar("PayX", String.valueOf(Math.min(counterNum, libsize)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -86,13 +86,13 @@ public class SetStateAi extends SpellAbilityAi {
|
|||||||
for (final Card c : list) {
|
for (final Card c : list) {
|
||||||
if (shouldTransformCard(c, ai, ph) || "Always".equals(logic)) {
|
if (shouldTransformCard(c, ai, ph) || "Always".equals(logic)) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
if (sa.getTargets().getNumTargeted() == tgt.getMaxTargets(source, sa)) {
|
if (sa.getTargets().size() == tgt.getMaxTargets(source, sa)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getTargets().getNumTargeted() >= tgt.getMinTargets(source, sa);
|
return sa.getTargets().size() >= tgt.getMinTargets(source, sa);
|
||||||
}
|
}
|
||||||
} else if ("TurnFace".equals(mode)) {
|
} else if ("TurnFace".equals(mode)) {
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
@@ -115,13 +115,13 @@ public class SetStateAi extends SpellAbilityAi {
|
|||||||
for (final Card c : list) {
|
for (final Card c : list) {
|
||||||
if (shouldTurnFace(c, ai, ph) || "Always".equals(logic)) {
|
if (shouldTurnFace(c, ai, ph) || "Always".equals(logic)) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
if (sa.getTargets().getNumTargeted() == tgt.getMaxTargets(source, sa)) {
|
if (sa.getTargets().size() == tgt.getMaxTargets(source, sa)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getTargets().getNumTargeted() >= tgt.getMinTargets(source, sa);
|
return sa.getTargets().size() >= tgt.getMinTargets(source, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class StoreSVarAi extends SpellAbilityAi {
|
|||||||
// Set PayX here to half the remaining mana to allow for Main 2 and other combat shenanigans.
|
// Set PayX here to half the remaining mana to allow for Main 2 and other combat shenanigans.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai) / 2;
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai) / 2;
|
||||||
if (xPay == 0) { return false; }
|
if (xPay == 0) { return false; }
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
final String logic = sa.getParam("AILogic");
|
final String logic = sa.getParam("AILogic");
|
||||||
|
|||||||
@@ -3,18 +3,11 @@ package forge.ai.ability;
|
|||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CounterEnumType;
|
|
||||||
import forge.game.card.CounterType;
|
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostPart;
|
|
||||||
import forge.game.cost.CostRemoveCounter;
|
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class TapAi extends TapAiBase {
|
public class TapAi extends TapAiBase {
|
||||||
@Override
|
@Override
|
||||||
@@ -48,7 +41,6 @@ public class TapAi extends TapAiBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
@@ -57,32 +49,27 @@ public class TapAi extends TapAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgt == null) {
|
if (!sa.usesTargeting()) {
|
||||||
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
|
||||||
|
|
||||||
boolean bFlag = false;
|
boolean bFlag = false;
|
||||||
for (final Card c : defined) {
|
for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) {
|
||||||
bFlag |= c.isUntapped();
|
bFlag |= c.isUntapped();
|
||||||
}
|
}
|
||||||
|
|
||||||
return bFlag;
|
return bFlag;
|
||||||
} else {
|
} else {
|
||||||
if ("TapForXCounters".equals(sa.getParam("AILogic"))) {
|
// X controls the minimum targets
|
||||||
// e.g. Waxmane Baku
|
if ("X".equals(sa.getTargetRestrictions().getMinTargets()) && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
CounterType ctrType = CounterType.get(CounterEnumType.KI);
|
// Set PayX here to maximum value.
|
||||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
if (part instanceof CostRemoveCounter) {
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
ctrType = ((CostRemoveCounter)part).counter;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int numTargetable = Math.min(sa.getHostCard().getCounters(ctrType), ai.getOpponents().getCreaturesInPlay().size());
|
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
||||||
sa.setSVar("ChosenX", String.valueOf(numTargetable));
|
sa.setXManaCostPaid(xPay);
|
||||||
|
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return tapPrefTargeting(ai, source, tgt, sa, false);
|
return tapPrefTargeting(ai, source, sa, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -41,21 +40,18 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
private boolean tapTargetList(final Player ai, final SpellAbility sa, final CardCollection tapList, final boolean mandatory) {
|
private boolean tapTargetList(final Player ai, final SpellAbility sa, final CardCollection tapList, final boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
|
|
||||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
tapList.removeAll(sa.getTargets().getTargetCards());
|
||||||
tapList.remove(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tapList.isEmpty()) {
|
if (tapList.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
while (sa.canAddMoreTarget()) {
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
|
|
||||||
if (tapList.size() == 0) {
|
if (tapList.isEmpty()) {
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa) || sa.getTargets().getNumTargeted() == 0) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
}
|
}
|
||||||
@@ -76,7 +72,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().getNumTargeted() == 0) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
}
|
}
|
||||||
@@ -111,11 +107,10 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
* a boolean.
|
* a boolean.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
protected boolean tapPrefTargeting(final Player ai, final Card source, final TargetRestrictions tgt, final SpellAbility sa, final boolean mandatory) {
|
protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ai.getWeakestOpponent();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||||
tapList = CardLists.getValidCards(tapList, tgt.getValidTgts(), source.getController(), source, sa);
|
|
||||||
tapList = CardLists.getTargetableCards(tapList, sa);
|
tapList = CardLists.getTargetableCards(tapList, sa);
|
||||||
tapList = CardLists.filter(tapList, Presets.UNTAPPED);
|
tapList = CardLists.filter(tapList, Presets.UNTAPPED);
|
||||||
tapList = CardLists.filter(tapList, new Predicate<Card>() {
|
tapList = CardLists.filter(tapList, new Predicate<Card>() {
|
||||||
@@ -137,7 +132,6 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
//use broader approach when the cost is a positive thing
|
//use broader approach when the cost is a positive thing
|
||||||
if (tapList.isEmpty() && ComputerUtil.activateForCost(sa, ai)) {
|
if (tapList.isEmpty() && ComputerUtil.activateForCost(sa, ai)) {
|
||||||
tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||||
tapList = CardLists.getValidCards(tapList, tgt.getValidTgts(), source.getController(), source, sa);
|
|
||||||
tapList = CardLists.getTargetableCards(tapList, sa);
|
tapList = CardLists.getTargetableCards(tapList, sa);
|
||||||
tapList = CardLists.filter(tapList, new Predicate<Card>() {
|
tapList = CardLists.filter(tapList, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -166,11 +160,11 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean goodTargets = false;
|
boolean goodTargets = false;
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
while (sa.canAddMoreTarget()) {
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
|
|
||||||
if (tapList.isEmpty()) {
|
if (tapList.isEmpty()) {
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa) || sa.getTargets().getNumTargeted() == 0) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
}
|
}
|
||||||
@@ -231,7 +225,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().getNumTargeted() == 0) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
}
|
}
|
||||||
@@ -249,7 +243,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Nothing was ever targeted, so we need to bail.
|
// Nothing was ever targeted, so we need to bail.
|
||||||
return sa.getTargets().getNumTargeted() != 0;
|
return sa.getTargets().size() != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -265,11 +259,9 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
protected boolean tapUnpreferredTargeting(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
protected boolean tapUnpreferredTargeting(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), source.getController(), source, sa);
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
|
||||||
|
|
||||||
// try to tap anything controlled by the computer
|
// try to tap anything controlled by the computer
|
||||||
CardCollection tapList = CardLists.filterControlledBy(list, ai.getOpponents());
|
CardCollection tapList = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||||
@@ -277,7 +269,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getTargets().getNumTargeted() >= tgt.getMinTargets(sa.getHostCard(), sa)) {
|
if (sa.isMinTargetChosen()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +288,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getTargets().getNumTargeted() >= tgt.getMinTargets(sa.getHostCard(), sa)) {
|
if (sa.isMinTargetChosen()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,11 +300,9 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
if (tgt == null) {
|
if (!sa.usesTargeting()) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -322,7 +312,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (tapPrefTargeting(ai, source, tgt, sa, mandatory)) {
|
if (tapPrefTargeting(ai, source, sa, mandatory)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (mandatory) {
|
} else if (mandatory) {
|
||||||
// not enough preferred targets, but mandatory so keep going:
|
// not enough preferred targets, but mandatory so keep going:
|
||||||
@@ -335,17 +325,14 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
boolean randomReturn = true;
|
boolean randomReturn = true;
|
||||||
|
|
||||||
if (tgt == null) {
|
if (sa.usesTargeting()) {
|
||||||
// either self or defined, either way should be fine
|
|
||||||
} else {
|
|
||||||
// target section, maybe pull this out?
|
// target section, maybe pull this out?
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!tapPrefTargeting(ai, source, tgt, sa, false)) {
|
if (!tapPrefTargeting(ai, source, sa, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,8 @@ import forge.game.ability.AbilityUtils;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class TapOrUntapAi extends TapAiBase {
|
public class TapOrUntapAi extends TapAiBase {
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -16,19 +13,17 @@ public class TapOrUntapAi extends TapAiBase {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
|
|
||||||
if (tgt == null) {
|
if (!sa.usesTargeting()) {
|
||||||
// assume we are looking to tap human's stuff
|
// assume we are looking to tap human's stuff
|
||||||
// TODO - check for things with untap abilities, and don't tap
|
// TODO - check for things with untap abilities, and don't tap
|
||||||
// those.
|
// those.
|
||||||
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
|
||||||
|
|
||||||
boolean bFlag = false;
|
boolean bFlag = false;
|
||||||
for (final Card c : defined) {
|
for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) {
|
||||||
bFlag |= c.isUntapped();
|
bFlag |= c.isUntapped();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +32,7 @@ public class TapOrUntapAi extends TapAiBase {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!tapPrefTargeting(ai, source, tgt, sa, false)) {
|
if (!tapPrefTargeting(ai, source, sa, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,10 +85,10 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
if (source.getSVar("X").equals("Count$Converge")) {
|
if (source.getSVar("X").equals("Count$Converge")) {
|
||||||
x = ComputerUtilMana.getConvergeCount(sa, ai);
|
x = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||||
}
|
}
|
||||||
if (source.getSVar("X").equals("Count$xPaid")) {
|
if (sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(x));
|
sa.setSVar("PayX", Integer.toString(x));
|
||||||
}
|
}
|
||||||
if (x <= 0) {
|
if (x <= 0) {
|
||||||
return false; // 0 tokens or 0 toughness token(s)
|
return false; // 0 tokens or 0 toughness token(s)
|
||||||
@@ -261,10 +261,10 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) {
|
if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) {
|
||||||
int x = AbilityUtils.calculateAmount(source, tokenAmount, sa);
|
int x = AbilityUtils.calculateAmount(source, tokenAmount, sa);
|
||||||
if (source.getSVar("X").equals("Count$xPaid")) {
|
if (sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(x));
|
sa.setSVar("PayX", Integer.toString(x));
|
||||||
}
|
}
|
||||||
if (x <= 0) {
|
if (x <= 0) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class UnattachAllAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
sa.setSVar("PayX", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|
|||||||
@@ -51,27 +51,24 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgt == null) {
|
if (!sa.usesTargeting()) {
|
||||||
final List<Card> pDefined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
final List<Card> pDefined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai;
|
return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai;
|
||||||
} else {
|
} else {
|
||||||
return untapPrefTargeting(ai, tgt, sa, false);
|
return untapPrefTargeting(ai, sa, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
if (!sa.usesTargeting()) {
|
||||||
|
|
||||||
if (tgt == null) {
|
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -80,7 +77,7 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
final List<Card> pDefined = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
final List<Card> pDefined = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai;
|
return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai;
|
||||||
} else {
|
} else {
|
||||||
if (untapPrefTargeting(ai, tgt, sa, mandatory)) {
|
if (untapPrefTargeting(ai, sa, mandatory)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (mandatory) {
|
} else if (mandatory) {
|
||||||
// not enough preferred targets, but mandatory so keep going:
|
// not enough preferred targets, but mandatory so keep going:
|
||||||
@@ -100,7 +97,7 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
// who cares if its already untapped, it's only a subability?
|
// who cares if its already untapped, it's only a subability?
|
||||||
} else {
|
} else {
|
||||||
if (!untapPrefTargeting(ai, tgt, sa, false)) {
|
if (!untapPrefTargeting(ai, sa, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,15 +110,13 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
* untapPrefTargeting.
|
* untapPrefTargeting.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param tgt
|
|
||||||
* a {@link forge.game.spellability.TargetRestrictions} object.
|
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param mandatory
|
* @param mandatory
|
||||||
* a boolean.
|
* a boolean.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private static boolean untapPrefTargeting(final Player ai, final TargetRestrictions tgt, final SpellAbility sa, final boolean mandatory) {
|
private static boolean untapPrefTargeting(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
Player targetController = ai;
|
Player targetController = ai;
|
||||||
@@ -131,7 +126,6 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -175,7 +169,7 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
untapList.removeAll(toExclude);
|
untapList.removeAll(toExclude);
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
while (sa.canAddMoreTarget()) {
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
|
|
||||||
if (untapList.isEmpty()) {
|
if (untapList.isEmpty()) {
|
||||||
@@ -183,7 +177,7 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
if (sa.getSubAbility() != null && sa.getSubAbility().getApi() == ApiType.Animate && !list.isEmpty()
|
if (sa.getSubAbility() != null && sa.getSubAbility().getApi() == ApiType.Animate && !list.isEmpty()
|
||||||
&& ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
&& ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
choice = ComputerUtilCard.getWorstPermanentAI(list, false, false, false, false);
|
choice = ComputerUtilCard.getWorstPermanentAI(list, false, false, false, false);
|
||||||
} else if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().getNumTargeted() == 0) {
|
} else if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -204,7 +198,7 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().getNumTargeted() == 0) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -271,11 +265,11 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) {
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
|
|
||||||
if (tapList.isEmpty()) {
|
if (tapList.isEmpty()) {
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa) || sa.getTargets().getNumTargeted() == 0) {
|
if (sa.getTargets().size() < tgt.getMinTargets(source, sa) || sa.getTargets().size() == 0) {
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
}
|
}
|
||||||
@@ -293,7 +287,7 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().getNumTargeted() == 0) {
|
if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) {
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ public class GameCopier {
|
|||||||
if (newSa != null) {
|
if (newSa != null) {
|
||||||
newSa.setActivatingPlayer(map.map(origSa.getActivatingPlayer()));
|
newSa.setActivatingPlayer(map.map(origSa.getActivatingPlayer()));
|
||||||
if (origSa.usesTargeting()) {
|
if (origSa.usesTargeting()) {
|
||||||
for (GameObject o : origSa.getTargets().getTargets()) {
|
for (GameObject o : origSa.getTargets()) {
|
||||||
newSa.getTargets().add(map.map(o));
|
newSa.getTargets().add(map.map(o));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,7 +342,9 @@ public class GameCopier {
|
|||||||
if (!c.getNamedCard().isEmpty()) {
|
if (!c.getNamedCard().isEmpty()) {
|
||||||
newCard.setNamedCard(c.getNamedCard());
|
newCard.setNamedCard(c.getNamedCard());
|
||||||
}
|
}
|
||||||
|
if (!c.getNamedCard2().isEmpty()) {
|
||||||
|
newCard.setNamedCard2(c.getNamedCard());
|
||||||
|
}
|
||||||
newCard.setSVars(c.getSVars());
|
newCard.setSVars(c.getSVars());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,19 +127,11 @@ public class GameSimulator {
|
|||||||
private SpellAbility findSaInSimGame(SpellAbility sa) {
|
private SpellAbility findSaInSimGame(SpellAbility sa) {
|
||||||
Card origHostCard = sa.getHostCard();
|
Card origHostCard = sa.getHostCard();
|
||||||
Card hostCard = (Card) copier.find(origHostCard);
|
Card hostCard = (Card) copier.find(origHostCard);
|
||||||
SpellAbility saOriginal = sa.getMayPlayOriginal();
|
|
||||||
String desc = sa.getDescription();
|
String desc = sa.getDescription();
|
||||||
if (saOriginal != null) {
|
|
||||||
// This is needed when it's an alternate cost SA and the desc string has
|
|
||||||
// been modified to add "by Foo" to it. TODO: Do we also need to do this in
|
|
||||||
// other places where we compare descriptions?
|
|
||||||
desc = saOriginal.getDescription();
|
|
||||||
System.err.println(sa.getDescription() + "->" + desc);
|
|
||||||
}
|
|
||||||
// FIXME: This is a hack that makes testManifest pass - figure out why it's needed.
|
// FIXME: This is a hack that makes testManifest pass - figure out why it's needed.
|
||||||
desc = TextUtil.fastReplace(desc, "Unmanifest {0}", "Unmanifest no cost");
|
desc = TextUtil.fastReplace(desc, "Unmanifest {0}", "Unmanifest no cost");
|
||||||
for (SpellAbility cSa : hostCard.getSpellAbilities()) {
|
for (SpellAbility cSa : hostCard.getSpellAbilities()) {
|
||||||
if (desc.equals(cSa.getDescription())) {
|
if (desc.startsWith(cSa.getDescription())) {
|
||||||
return cSa;
|
return cSa;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,7 +166,7 @@ public class GameSimulator {
|
|||||||
final boolean divided = origSaOrSubSa.hasParam("DividedAsYouChoose");
|
final boolean divided = origSaOrSubSa.hasParam("DividedAsYouChoose");
|
||||||
final TargetRestrictions origTgtRes = origSaOrSubSa.getTargetRestrictions();
|
final TargetRestrictions origTgtRes = origSaOrSubSa.getTargetRestrictions();
|
||||||
final TargetRestrictions tgtRes = saOrSubSa.getTargetRestrictions();
|
final TargetRestrictions tgtRes = saOrSubSa.getTargetRestrictions();
|
||||||
for (final GameObject o : origSaOrSubSa.getTargets().getTargets()) {
|
for (final GameObject o : origSaOrSubSa.getTargets()) {
|
||||||
final GameObject target = copier.find(o);
|
final GameObject target = copier.find(o);
|
||||||
saOrSubSa.getTargets().add(target);
|
saOrSubSa.getTargets().add(target);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
@@ -189,7 +181,7 @@ public class GameSimulator {
|
|||||||
if (debugPrint && !sa.getAllTargetChoices().isEmpty()) {
|
if (debugPrint && !sa.getAllTargetChoices().isEmpty()) {
|
||||||
debugPrint("Targets: ");
|
debugPrint("Targets: ");
|
||||||
for (TargetChoices target : sa.getAllTargetChoices()) {
|
for (TargetChoices target : sa.getAllTargetChoices()) {
|
||||||
System.out.print(target.getTargetedString());
|
System.out.print(target);
|
||||||
}
|
}
|
||||||
System.out.println();
|
System.out.println();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ public class PossibleTargetSelector {
|
|||||||
private void selectTargetsByIndexImpl(int index) {
|
private void selectTargetsByIndexImpl(int index) {
|
||||||
targetingSa.resetTargets();
|
targetingSa.resetTargets();
|
||||||
|
|
||||||
while (targetingSa.getTargets().getNumTargeted() < maxTargets && index < validTargets.size()) {
|
while (targetingSa.getTargets().size() < maxTargets && index < validTargets.size()) {
|
||||||
targetingSa.getTargets().add(validTargets.get(index++));
|
targetingSa.getTargets().add(validTargets.get(index++));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ public class PossibleTargetSelector {
|
|||||||
final int amountPerCard = amount / targetCount;
|
final int amountPerCard = amount / targetCount;
|
||||||
int amountLeftOver = amount - (amountPerCard * targetCount);
|
int amountLeftOver = amount - (amountPerCard * targetCount);
|
||||||
final TargetRestrictions tgtRes = targetingSa.getTargetRestrictions();
|
final TargetRestrictions tgtRes = targetingSa.getTargetRestrictions();
|
||||||
for (GameObject target : targetingSa.getTargets().getTargets()) {
|
for (GameObject target : targetingSa.getTargets()) {
|
||||||
tgtRes.addDividedAllocation(target, amountPerCard + amountLeftOver);
|
tgtRes.addDividedAllocation(target, amountPerCard + amountLeftOver);
|
||||||
amountLeftOver = 0;
|
amountLeftOver = 0;
|
||||||
}
|
}
|
||||||
@@ -190,7 +190,7 @@ public class PossibleTargetSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Targets getLastSelectedTargets() {
|
public Targets getLastSelectedTargets() {
|
||||||
return new Targets(targetingSaIndex, validTargets.size(), targetIndex - 1, targetingSa.getTargets().getTargetedString());
|
return new Targets(targetingSaIndex, validTargets.size(), targetIndex - 1, targetingSa.getTargets().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean selectTargetsByIndex(int targetIndex) {
|
public boolean selectTargetsByIndex(int targetIndex) {
|
||||||
|
|||||||
@@ -177,10 +177,10 @@ public class SimulationController {
|
|||||||
saOrSubSa = saOrSubSa.getSubAbility();
|
saOrSubSa = saOrSubSa.getSubAbility();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saOrSubSa == null || saOrSubSa.getTargets() == null || saOrSubSa.getTargets().getTargets().size() != 1) {
|
if (saOrSubSa == null || saOrSubSa.getTargets() == null || saOrSubSa.getTargets().size() != 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
GameObject target = saOrSubSa.getTargets().getTargets().get(0);
|
GameObject target = saOrSubSa.getTargets().get(0);
|
||||||
GameObject originalTarget = target;
|
GameObject originalTarget = target;
|
||||||
if (!(target instanceof Card)) { return null; }
|
if (!(target instanceof Card)) { return null; }
|
||||||
Card hostCard = sa.getHostCard();
|
Card hostCard = sa.getHostCard();
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import forge.ai.ability.ExploreAi;
|
|||||||
import forge.ai.simulation.GameStateEvaluator.Score;
|
import forge.ai.simulation.GameStateEvaluator.Score;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.ability.effects.CharmEffect;
|
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -147,7 +146,7 @@ public class SpellAbilityPicker {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
return !sa.getHostCard().isInstant() && !sa.getHostCard().withFlash(player);
|
return !sa.withFlash(sa.getHostCard(), player);
|
||||||
}
|
}
|
||||||
if (sa.isPwAbility()) {
|
if (sa.isPwAbility()) {
|
||||||
return !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed.");
|
return !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed.");
|
||||||
@@ -285,7 +284,7 @@ public class SpellAbilityPicker {
|
|||||||
SpellAbility saOrSubSa = sa;
|
SpellAbility saOrSubSa = sa;
|
||||||
do {
|
do {
|
||||||
if (saOrSubSa.usesTargeting()) {
|
if (saOrSubSa.usesTargeting()) {
|
||||||
saString.append(" (targets: ").append(saOrSubSa.getTargets().getTargetedString()).append(")");
|
saString.append(" (targets: ").append(saOrSubSa.getTargets()).append(")");
|
||||||
}
|
}
|
||||||
saOrSubSa = saOrSubSa.getSubAbility();
|
saOrSubSa = saOrSubSa.getSubAbility();
|
||||||
} while (saOrSubSa != null);
|
} while (saOrSubSa != null);
|
||||||
@@ -371,14 +370,12 @@ public class SpellAbilityPicker {
|
|||||||
return bestScore;
|
return bestScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat) {
|
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> choices, int min, int num, boolean allowRepeat) {
|
||||||
if (interceptor != null) {
|
if (interceptor != null) {
|
||||||
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
|
||||||
return interceptor.chooseModesForAbility(choices, min, num, allowRepeat);
|
return interceptor.chooseModesForAbility(choices, min, num, allowRepeat);
|
||||||
}
|
}
|
||||||
if (plan != null && plan.getSelectedDecision() != null && plan.getSelectedDecision().modes != null) {
|
if (plan != null && plan.getSelectedDecision() != null && plan.getSelectedDecision().modes != null) {
|
||||||
Plan.Decision decision = plan.getSelectedDecision();
|
Plan.Decision decision = plan.getSelectedDecision();
|
||||||
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
|
||||||
// TODO: Validate that there's no discrepancies between choices and modes?
|
// TODO: Validate that there's no discrepancies between choices and modes?
|
||||||
List<AbilitySub> plannedModes = SpellAbilityChoicesIterator.getModeCombination(choices, decision.modes);
|
List<AbilitySub> plannedModes = SpellAbilityChoicesIterator.getModeCombination(choices, decision.modes);
|
||||||
if (plan.getSelectedDecision().targets != null) {
|
if (plan.getSelectedDecision().targets != null) {
|
||||||
|
|||||||
@@ -150,6 +150,9 @@ public final class ImageKeys {
|
|||||||
// try with upper case set
|
// try with upper case set
|
||||||
file = findFile(dir, setlessFilename + "_" + setCode.toUpperCase());
|
file = findFile(dir, setlessFilename + "_" + setCode.toUpperCase());
|
||||||
if (file != null) { return file; }
|
if (file != null) { return file; }
|
||||||
|
// try with lower case set
|
||||||
|
file = findFile(dir, setlessFilename + "_" + setCode.toLowerCase());
|
||||||
|
if (file != null) { return file; }
|
||||||
// try without set name
|
// try without set name
|
||||||
file = findFile(dir, setlessFilename);
|
file = findFile(dir, setlessFilename);
|
||||||
if (file != null) { return file; }
|
if (file != null) { return file; }
|
||||||
@@ -164,8 +167,12 @@ public final class ImageKeys {
|
|||||||
file = findFile(dir, setlessFilename);
|
file = findFile(dir, setlessFilename);
|
||||||
if (file != null) { return file; }
|
if (file != null) { return file; }
|
||||||
|
|
||||||
// try lowering the art index to the minimum for regular cards
|
|
||||||
if (setlessFilename.contains(".full")) {
|
if (setlessFilename.contains(".full")) {
|
||||||
|
//try fullborder
|
||||||
|
String fullborderFile = TextUtil.fastReplace(setlessFilename, ".full", ".fullborder");
|
||||||
|
file = findFile(dir, fullborderFile);
|
||||||
|
if (file != null) { return file; }
|
||||||
|
// try lowering the art index to the minimum for regular cards
|
||||||
file = findFile(dir, setlessFilename.replaceAll("[0-9]*[.]full", "1.full"));
|
file = findFile(dir, setlessFilename.replaceAll("[0-9]*[.]full", "1.full"));
|
||||||
if (file != null) { return file; }
|
if (file != null) { return file; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ public class StaticData {
|
|||||||
|
|
||||||
public IStorage<PrintSheet> getPrintSheets() {
|
public IStorage<PrintSheet> getPrintSheets() {
|
||||||
if (printSheets == null)
|
if (printSheets == null)
|
||||||
printSheets = new StorageBase<>("Special print runs", new PrintSheet.Reader(new File(blockDataFolder, "printsheets.txt")));
|
printSheets = PrintSheet.initializePrintSheets(new File(blockDataFolder, "printsheets.txt"), getEditions());
|
||||||
return printSheets;
|
return printSheets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
private final Map<String, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
|
private final Map<String, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
|
||||||
private final Map<String, Integer> artIds = new HashMap<>();
|
private final Map<String, Integer> artIds = new HashMap<>();
|
||||||
|
|
||||||
private final List<PaperCard> allCards = new ArrayList<>();
|
private final Collection<PaperCard> roAllCards = Collections.unmodifiableCollection(allCardsByName.values());
|
||||||
private final List<PaperCard> roAllCards = Collections.unmodifiableList(allCards);
|
|
||||||
private final CardEdition.Collection editions;
|
private final CardEdition.Collection editions;
|
||||||
|
|
||||||
public enum SetPreference {
|
public enum SetPreference {
|
||||||
@@ -153,9 +152,11 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
|
|
||||||
public void loadCard(String cardName, CardRules cr) {
|
public void loadCard(String cardName, CardRules cr) {
|
||||||
rulesByName.put(cardName, cr);
|
rulesByName.put(cardName, cr);
|
||||||
|
// This seems very unperformant. Does this get called often?
|
||||||
|
System.out.println("Inside loading card");
|
||||||
|
|
||||||
for (CardEdition e : editions) {
|
for (CardEdition e : editions) {
|
||||||
for (CardInSet cis : e.getCards()) {
|
for (CardInSet cis : e.getAllCardsInSet()) {
|
||||||
if (cis.name.equalsIgnoreCase(cardName)) {
|
if (cis.name.equalsIgnoreCase(cardName)) {
|
||||||
addSetCard(e, cis, cr);
|
addSetCard(e, cis, cr);
|
||||||
}
|
}
|
||||||
@@ -175,13 +176,13 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
boolean coreOrExpSet = e.getType() == CardEdition.Type.CORE || e.getType() == CardEdition.Type.EXPANSION;
|
boolean coreOrExpSet = e.getType() == CardEdition.Type.CORE || e.getType() == CardEdition.Type.EXPANSION;
|
||||||
boolean isCoreExpSet = coreOrExpSet || e.getType() == CardEdition.Type.REPRINT;
|
boolean isCoreExpSet = coreOrExpSet || e.getType() == CardEdition.Type.REPRINT;
|
||||||
if (logMissingPerEdition && isCoreExpSet) {
|
if (logMissingPerEdition && isCoreExpSet) {
|
||||||
System.out.print(e.getName() + " (" + e.getCards().length + " cards)");
|
System.out.print(e.getName() + " (" + e.getAllCardsInSet().size() + " cards)");
|
||||||
}
|
}
|
||||||
if (coreOrExpSet && e.getDate().after(today)) {
|
if (coreOrExpSet && e.getDate().after(today) && upcomingSet == null) {
|
||||||
upcomingSet = e;
|
upcomingSet = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (CardEdition.CardInSet cis : e.getCards()) {
|
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
|
||||||
CardRules cr = rulesByName.get(cis.name);
|
CardRules cr = rulesByName.get(cis.name);
|
||||||
if (cr != null) {
|
if (cr != null) {
|
||||||
addSetCard(e, cis, cr);
|
addSetCard(e, cis, cr);
|
||||||
@@ -195,7 +196,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
System.out.println(" ... 100% ");
|
System.out.println(" ... 100% ");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
int missing = (e.getCards().length - missingCards.size()) * 10000 / e.getCards().length;
|
int missing = (e.getAllCardsInSet().size() - missingCards.size()) * 10000 / e.getAllCardsInSet().size();
|
||||||
System.out.printf(" ... %.2f%% (%s missing: %s)%n", missing * 0.01f, Lang.nounWithAmount(missingCards.size(), "card"), StringUtils.join(missingCards, " | "));
|
System.out.printf(" ... %.2f%% (%s missing: %s)%n", missing * 0.01f, Lang.nounWithAmount(missingCards.size(), "card"), StringUtils.join(missingCards, " | "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,10 +246,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
|
|
||||||
private void reIndex() {
|
private void reIndex() {
|
||||||
uniqueCardsByName.clear();
|
uniqueCardsByName.clear();
|
||||||
allCards.clear();
|
|
||||||
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {
|
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {
|
||||||
uniqueCardsByName.put(kv.getKey(), getFirstWithImage(kv.getValue()));
|
uniqueCardsByName.put(kv.getKey(), getFirstWithImage(kv.getValue()));
|
||||||
allCards.addAll(kv.getValue());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +317,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
if (edition == null)
|
if (edition == null)
|
||||||
return null;
|
return null;
|
||||||
int numMatches = 0;
|
int numMatches = 0;
|
||||||
for (CardInSet card : edition.getCards()) {
|
for (CardInSet card : edition.getAllCardsInSet()) {
|
||||||
if (card.name.equalsIgnoreCase(cardName)) {
|
if (card.name.equalsIgnoreCase(cardName)) {
|
||||||
numMatches += 1;
|
numMatches += 1;
|
||||||
if (numMatches == artIndex) {
|
if (numMatches == artIndex) {
|
||||||
@@ -535,7 +534,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PaperCard> getAllCards() {
|
public Collection<PaperCard> getAllCards() {
|
||||||
return roAllCards;
|
return roAllCards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,7 +561,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
public List<PaperCard> getAllCardsFromEdition(CardEdition edition) {
|
public List<PaperCard> getAllCardsFromEdition(CardEdition edition) {
|
||||||
List<PaperCard> cards = Lists.newArrayList();
|
List<PaperCard> cards = Lists.newArrayList();
|
||||||
|
|
||||||
for(CardInSet cis : edition.getCards()) {
|
for(CardInSet cis : edition.getAllCardsInSet()) {
|
||||||
PaperCard card = this.getCard(cis.name, edition.getCode());
|
PaperCard card = this.getCard(cis.name, edition.getCode());
|
||||||
if (card == null) {
|
if (card == null) {
|
||||||
// Just in case the card is listed in the edition file but Forge doesn't support it
|
// Just in case the card is listed in the edition file but Forge doesn't support it
|
||||||
@@ -654,7 +653,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
// May iterate over editions and find out if there is any card named 'cardName' but not implemented with Forge script.
|
// May iterate over editions and find out if there is any card named 'cardName' but not implemented with Forge script.
|
||||||
if (StringUtils.isBlank(request.edition)) {
|
if (StringUtils.isBlank(request.edition)) {
|
||||||
for (CardEdition edition : editions) {
|
for (CardEdition edition : editions) {
|
||||||
for (CardInSet cardInSet : edition.getCards()) {
|
for (CardInSet cardInSet : edition.getAllCardsInSet()) {
|
||||||
if (cardInSet.name.equals(request.cardName)) {
|
if (cardInSet.name.equals(request.cardName)) {
|
||||||
cardEdition = edition;
|
cardEdition = edition;
|
||||||
cardRarity = cardInSet.rarity;
|
cardRarity = cardInSet.rarity;
|
||||||
@@ -668,7 +667,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
} else {
|
} else {
|
||||||
cardEdition = editions.get(request.edition);
|
cardEdition = editions.get(request.edition);
|
||||||
if (cardEdition != null) {
|
if (cardEdition != null) {
|
||||||
for (CardInSet cardInSet : cardEdition.getCards()) {
|
for (CardInSet cardInSet : cardEdition.getAllCardsInSet()) {
|
||||||
if (cardInSet.name.equals(request.cardName)) {
|
if (cardInSet.name.equals(request.cardName)) {
|
||||||
cardRarity = cardInSet.rarity;
|
cardRarity = cardInSet.rarity;
|
||||||
break;
|
break;
|
||||||
@@ -709,9 +708,10 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
// 1. generate all paper cards from edition data we have (either explicit, or found in res/editions, or add to unknown edition)
|
// 1. generate all paper cards from edition data we have (either explicit, or found in res/editions, or add to unknown edition)
|
||||||
List<PaperCard> paperCards = new ArrayList<>();
|
List<PaperCard> paperCards = new ArrayList<>();
|
||||||
if (null == whenItWasPrinted || whenItWasPrinted.isEmpty()) {
|
if (null == whenItWasPrinted || whenItWasPrinted.isEmpty()) {
|
||||||
|
// TODO Not performant Each time we "putCard" we loop through ALL CARDS IN ALL editions
|
||||||
for (CardEdition e : editions.getOrderedEditions()) {
|
for (CardEdition e : editions.getOrderedEditions()) {
|
||||||
int artIdx = 1;
|
int artIdx = 1;
|
||||||
for (CardInSet cis : e.getCards()) {
|
for (CardInSet cis : e.getAllCardsInSet()) {
|
||||||
if (!cis.name.equals(cardName)) {
|
if (!cis.name.equals(cardName)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ package forge.card;
|
|||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.*;
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
import forge.card.CardDb.SetPreference;
|
import forge.card.CardDb.SetPreference;
|
||||||
import forge.deck.CardPool;
|
import forge.deck.CardPool;
|
||||||
@@ -75,6 +74,38 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
MODERN // 8th Edition and newer
|
MODERN // 8th Edition and newer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reserved names of sections inside edition files, that are not parsed as cards
|
||||||
|
private static final List<String> reservedSectionNames = ImmutableList.of("metadata", "tokens");
|
||||||
|
|
||||||
|
// commonly used printsheets with collector number
|
||||||
|
public enum EditionSectionWithCollectorNumbers {
|
||||||
|
CARDS("cards"),
|
||||||
|
PRECON_PRODUCT("precon product"),
|
||||||
|
BORDERLESS("borderless"),
|
||||||
|
SHOWCASE("showcase"),
|
||||||
|
EXTENDED_ART("extended art"),
|
||||||
|
ALTERNATE_ART("alternate art"),
|
||||||
|
BUY_A_BOX("buy a box"),
|
||||||
|
PROMO("promo");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
EditionSectionWithCollectorNumbers(final String n) { this.name = n; }
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> getNames() {
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
for (EditionSectionWithCollectorNumbers s : EditionSectionWithCollectorNumbers.values()) {
|
||||||
|
String sName = s.getName();
|
||||||
|
list.add(sName);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class CardInSet {
|
public static class CardInSet {
|
||||||
public final CardRarity rarity;
|
public final CardRarity rarity;
|
||||||
public final String collectorNumber;
|
public final String collectorNumber;
|
||||||
@@ -123,20 +154,28 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
private String additionalUnlockSet = "";
|
private String additionalUnlockSet = "";
|
||||||
private boolean smallSetOverride = false;
|
private boolean smallSetOverride = false;
|
||||||
private String boosterMustContain = "";
|
private String boosterMustContain = "";
|
||||||
private final CardInSet[] cards;
|
private String boosterReplaceSlotFromPrintSheet = "";
|
||||||
|
private String[] chaosDraftThemes = new String[0];
|
||||||
|
private String doublePickDuringDraft = "";
|
||||||
|
private final ListMultimap<String, CardInSet> cardMap;
|
||||||
private final Map<String, Integer> tokenNormalized;
|
private final Map<String, Integer> tokenNormalized;
|
||||||
|
// custom print sheets that will be loaded lazily
|
||||||
|
private final Map<String, List<String>> customPrintSheetsToParse;
|
||||||
|
|
||||||
private int boosterArts = 1;
|
private int boosterArts = 1;
|
||||||
private SealedProduct.Template boosterTpl = null;
|
private SealedProduct.Template boosterTpl = null;
|
||||||
|
|
||||||
private CardEdition(CardInSet[] cards) {
|
private CardEdition(ListMultimap<String, CardInSet> cardMap, Map<String, Integer> tokens, Map<String, List<String>> customPrintSheetsToParse) {
|
||||||
this.cards = cards;
|
this.cardMap = cardMap;
|
||||||
tokenNormalized = new HashMap<>();
|
this.tokenNormalized = tokens;
|
||||||
|
this.customPrintSheetsToParse = customPrintSheetsToParse;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CardEdition(CardInSet[] cards, Map<String, Integer> tokens) {
|
private CardEdition(CardInSet[] cards, Map<String, Integer> tokens) {
|
||||||
this.cards = cards;
|
this.cardMap = ArrayListMultimap.create();
|
||||||
|
this.cardMap.replaceValues("cards", Arrays.asList(cards));
|
||||||
this.tokenNormalized = tokens;
|
this.tokenNormalized = tokens;
|
||||||
|
this.customPrintSheetsToParse = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,7 +193,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
* @param cards the cards in the set
|
* @param cards the cards in the set
|
||||||
*/
|
*/
|
||||||
private CardEdition(String date, String code, String code2, String mciCode, Type type, String name, FoilType foil, CardInSet[] cards) {
|
private CardEdition(String date, String code, String code2, String mciCode, Type type, String name, FoilType foil, CardInSet[] cards) {
|
||||||
this(cards);
|
this(cards, new HashMap<>());
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.code2 = code2;
|
this.code2 = code2;
|
||||||
this.mciCode = mciCode;
|
this.mciCode = mciCode;
|
||||||
@@ -190,8 +229,16 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
public String getAdditionalSheetForFoils() { return additionalSheetForFoils; }
|
public String getAdditionalSheetForFoils() { return additionalSheetForFoils; }
|
||||||
public String getAdditionalUnlockSet() { return additionalUnlockSet; }
|
public String getAdditionalUnlockSet() { return additionalUnlockSet; }
|
||||||
public boolean getSmallSetOverride() { return smallSetOverride; }
|
public boolean getSmallSetOverride() { return smallSetOverride; }
|
||||||
|
public String getDoublePickDuringDraft() { return doublePickDuringDraft; }
|
||||||
public String getBoosterMustContain() { return boosterMustContain; }
|
public String getBoosterMustContain() { return boosterMustContain; }
|
||||||
public CardInSet[] getCards() { return cards; }
|
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
|
||||||
|
public String[] getChaosDraftThemes() { return chaosDraftThemes; }
|
||||||
|
|
||||||
|
public List<CardInSet> getCards() { return cardMap.get("cards"); }
|
||||||
|
public List<CardInSet> getAllCardsInSet() {
|
||||||
|
return Lists.newArrayList(cardMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
|
public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
|
||||||
|
|
||||||
public Map<String, Integer> getTokens() { return tokenNormalized; }
|
public Map<String, Integer> getTokens() { return tokenNormalized; }
|
||||||
@@ -242,7 +289,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLargeSet() {
|
public boolean isLargeSet() {
|
||||||
return cards.length > 200 && !smallSetOverride;
|
return getAllCardsInSet().size() > 200 && !smallSetOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCntBoosterPictures() {
|
public int getCntBoosterPictures() {
|
||||||
@@ -257,6 +304,40 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
return boosterTpl != null;
|
return boosterTpl != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<PrintSheet> getPrintSheetsBySection() {
|
||||||
|
final CardDb cardDb = StaticData.instance().getCommonCards();
|
||||||
|
Map<String, Integer> cardToIndex = new HashMap<>();
|
||||||
|
|
||||||
|
List<PrintSheet> sheets = Lists.newArrayList();
|
||||||
|
for(String sectionName : cardMap.keySet()) {
|
||||||
|
PrintSheet sheet = new PrintSheet(String.format("%s %s", this.getCode(), sectionName));
|
||||||
|
|
||||||
|
List<CardInSet> cards = cardMap.get(sectionName);
|
||||||
|
for(CardInSet card : cards) {
|
||||||
|
int index = 1;
|
||||||
|
if (cardToIndex.containsKey(card.name)) {
|
||||||
|
index = cardToIndex.get(card.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
cardToIndex.put(card.name, index);
|
||||||
|
|
||||||
|
PaperCard pCard = cardDb.getCard(card.name, this.getCode(), index);
|
||||||
|
sheet.add(pCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
sheets.add(sheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(String sheetName : customPrintSheetsToParse.keySet()) {
|
||||||
|
List<String> sheetToParse = customPrintSheetsToParse.get(sheetName);
|
||||||
|
CardPool sheetPool = CardPool.fromCardList(sheetToParse);
|
||||||
|
PrintSheet sheet = new PrintSheet(String.format("%s %s", this.getCode(), sheetName), sheetPool);
|
||||||
|
sheets.add(sheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sheets;
|
||||||
|
}
|
||||||
|
|
||||||
public static class Reader extends StorageReaderFolder<CardEdition> {
|
public static class Reader extends StorageReaderFolder<CardEdition> {
|
||||||
public Reader(File path) {
|
public Reader(File path) {
|
||||||
super(path, CardEdition.FN_GET_CODE);
|
super(path, CardEdition.FN_GET_CODE);
|
||||||
@@ -266,9 +347,6 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
protected CardEdition read(File file) {
|
protected CardEdition read(File file) {
|
||||||
final Map<String, List<String>> contents = FileSection.parseSections(FileUtil.readFile(file));
|
final Map<String, List<String>> contents = FileSection.parseSections(FileUtil.readFile(file));
|
||||||
|
|
||||||
Map<String, Integer> tokenNormalized = new HashMap<>();
|
|
||||||
List<CardEdition.CardInSet> processedCards = new ArrayList<>();
|
|
||||||
if (contents.containsKey("cards")) {
|
|
||||||
final Pattern pattern = Pattern.compile(
|
final Pattern pattern = Pattern.compile(
|
||||||
/*
|
/*
|
||||||
The following pattern will match the WAR Japanese art entries,
|
The following pattern will match the WAR Japanese art entries,
|
||||||
@@ -287,18 +365,42 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
*/
|
*/
|
||||||
"(^([0-9]+.?) )?(([SCURML]) )?(.*)$"
|
"(^([0-9]+.?) )?(([SCURML]) )?(.*)$"
|
||||||
);
|
);
|
||||||
for(String line : contents.get("cards")) {
|
|
||||||
|
ListMultimap<String, CardInSet> cardMap = ArrayListMultimap.create();
|
||||||
|
Map<String, Integer> tokenNormalized = new HashMap<>();
|
||||||
|
Map<String, List<String>> customPrintSheetsToParse = new HashMap<>();
|
||||||
|
List<String> editionSectionsWithCollectorNumbers = EditionSectionWithCollectorNumbers.getNames();
|
||||||
|
|
||||||
|
for (String sectionName : contents.keySet()) {
|
||||||
|
// skip reserved section names like 'metadata' and 'tokens' that are handled separately
|
||||||
|
if (reservedSectionNames.contains(sectionName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// parse sections of the format "<collector number> <rarity> <name>"
|
||||||
|
if (editionSectionsWithCollectorNumbers.contains(sectionName)) {
|
||||||
|
for(String line : contents.get(sectionName)) {
|
||||||
Matcher matcher = pattern.matcher(line);
|
Matcher matcher = pattern.matcher(line);
|
||||||
if (matcher.matches()) {
|
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
String collectorNumber = matcher.group(2);
|
String collectorNumber = matcher.group(2);
|
||||||
CardRarity r = CardRarity.smartValueOf(matcher.group(4));
|
CardRarity r = CardRarity.smartValueOf(matcher.group(4));
|
||||||
String cardName = matcher.group(5);
|
String cardName = matcher.group(5);
|
||||||
CardInSet cis = new CardInSet(cardName, collectorNumber, r);
|
CardInSet cis = new CardInSet(cardName, collectorNumber, r);
|
||||||
processedCards.add(cis);
|
|
||||||
|
cardMap.put(sectionName, cis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// save custom print sheets of the format "<amount> <name>|<setcode>|<art index>"
|
||||||
|
// to parse later when printsheets are loaded lazily (and the cardpool is already initialized)
|
||||||
|
else {
|
||||||
|
customPrintSheetsToParse.put(sectionName, contents.get(sectionName));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parse tokens section
|
||||||
if (contents.containsKey("tokens")) {
|
if (contents.containsKey("tokens")) {
|
||||||
for(String line : contents.get("tokens")) {
|
for(String line : contents.get("tokens")) {
|
||||||
if (StringUtils.isBlank(line))
|
if (StringUtils.isBlank(line))
|
||||||
@@ -312,11 +414,9 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CardEdition res = new CardEdition(
|
CardEdition res = new CardEdition(cardMap, tokenNormalized, customPrintSheetsToParse);
|
||||||
processedCards.toArray(new CardInSet[processedCards.size()]),
|
|
||||||
tokenNormalized
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// parse metadata section
|
||||||
FileSection section = FileSection.parse(contents.get("metadata"), FileSection.EQUALS_KV_SEPARATOR);
|
FileSection section = FileSection.parse(contents.get("metadata"), FileSection.EQUALS_KV_SEPARATOR);
|
||||||
res.name = section.get("name");
|
res.name = section.get("name");
|
||||||
res.date = parseDate(section.get("date"));
|
res.date = parseDate(section.get("date"));
|
||||||
@@ -377,8 +477,13 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
res.additionalUnlockSet = section.get("AdditionalSetUnlockedInQuest", ""); // e.g. Time Spiral Timeshifted (TSB) for Time Spiral
|
res.additionalUnlockSet = section.get("AdditionalSetUnlockedInQuest", ""); // e.g. Time Spiral Timeshifted (TSB) for Time Spiral
|
||||||
|
|
||||||
res.smallSetOverride = section.getBoolean("TreatAsSmallSet", false); // for "small" sets with over 200 cards (e.g. Eldritch Moon)
|
res.smallSetOverride = section.getBoolean("TreatAsSmallSet", false); // for "small" sets with over 200 cards (e.g. Eldritch Moon)
|
||||||
|
res.doublePickDuringDraft = section.get("DoublePick", ""); // "FirstPick" or "Always"
|
||||||
|
|
||||||
res.boosterMustContain = section.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature
|
res.boosterMustContain = section.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature
|
||||||
|
res.boosterReplaceSlotFromPrintSheet = section.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card
|
||||||
|
|
||||||
|
res.chaosDraftThemes = section.get("ChaosDraftThemes", "").split(";"); // semicolon separated list of theme names
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -487,7 +487,7 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
if ("T".equals(key)) {
|
if ("T".equals(key)) {
|
||||||
this.faces[this.curFace].addTrigger(value);
|
this.faces[this.curFace].addTrigger(value);
|
||||||
} else if ("Types".equals(key)) {
|
} else if ("Types".equals(key)) {
|
||||||
this.faces[this.curFace].setType(CardType.parse(value));
|
this.faces[this.curFace].setType(CardType.parse(value, false));
|
||||||
} else if ("Text".equals(key) && !"no text".equals(value) && StringUtils.isNotBlank(value)) {
|
} else if ("Text".equals(key) && !"no text".equals(value) && StringUtils.isNotBlank(value)) {
|
||||||
this.faces[this.curFace].setNonAbilityText(value);
|
this.faces[this.curFace].setNonAbilityText(value);
|
||||||
}
|
}
|
||||||
@@ -557,7 +557,7 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
CardAiHints cah = new CardAiHints(true, true, true, null, null, null);
|
CardAiHints cah = new CardAiHints(true, true, true, null, null, null);
|
||||||
CardFace[] faces = { new CardFace(name), null};
|
CardFace[] faces = { new CardFace(name), null};
|
||||||
faces[0].setColor(ColorSet.fromMask(0));
|
faces[0].setColor(ColorSet.fromMask(0));
|
||||||
faces[0].setType(CardType.parse(""));
|
faces[0].setType(CardType.parse("", false));
|
||||||
faces[0].setOracleText("This card is not supported by Forge. Whenever you start a game with this card, it will be bugged.");
|
faces[0].setOracleText("This card is not supported by Forge. Whenever you start a game with this card, it will be bugged.");
|
||||||
faces[0].setNonAbilityText("This card is not supported by Forge.\nWhenever you start a game with this card, it will be bugged.");
|
faces[0].setNonAbilityText("This card is not supported by Forge.\nWhenever you start a game with this card, it will be bugged.");
|
||||||
faces[0].assignMissingFields();
|
faces[0].assignMissingFields();
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ public enum CardSplitType
|
|||||||
Meld(FaceSelectionMethod.USE_ACTIVE_FACE, CardStateName.Meld),
|
Meld(FaceSelectionMethod.USE_ACTIVE_FACE, CardStateName.Meld),
|
||||||
Split(FaceSelectionMethod.COMBINE, CardStateName.RightSplit),
|
Split(FaceSelectionMethod.COMBINE, CardStateName.RightSplit),
|
||||||
Flip(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Flipped),
|
Flip(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Flipped),
|
||||||
Adventure(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Adventure);
|
Adventure(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Adventure),
|
||||||
|
Modal(FaceSelectionMethod.USE_ACTIVE_FACE, CardStateName.Modal);
|
||||||
|
|
||||||
CardSplitType(FaceSelectionMethod calcMode, CardStateName stateName) {
|
CardSplitType(FaceSelectionMethod calcMode, CardStateName stateName) {
|
||||||
method = calcMode;
|
method = calcMode;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public enum CardStateName {
|
|||||||
LeftSplit,
|
LeftSplit,
|
||||||
RightSplit,
|
RightSplit,
|
||||||
Adventure,
|
Adventure,
|
||||||
|
Modal
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|||||||
@@ -50,25 +50,28 @@ import forge.util.Settable;
|
|||||||
public final class CardType implements Comparable<CardType>, CardTypeView {
|
public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||||
private static final long serialVersionUID = 4629853583167022151L;
|
private static final long serialVersionUID = 4629853583167022151L;
|
||||||
|
|
||||||
public static final CardTypeView EMPTY = new CardType();
|
public static final CardTypeView EMPTY = new CardType(false);
|
||||||
|
|
||||||
|
public static final String AllCreatureTypes = "AllCreatureTypes";
|
||||||
|
|
||||||
public enum CoreType {
|
public enum CoreType {
|
||||||
Artifact(true),
|
Artifact(true, "artifacts"),
|
||||||
Conspiracy(false),
|
Conspiracy(false, "conspiracies"),
|
||||||
Creature(true),
|
Creature(true, "creatures"),
|
||||||
Emblem(false),
|
Emblem(false, "emblems"),
|
||||||
Enchantment(true),
|
Enchantment(true, "enchantments"),
|
||||||
Instant(false),
|
Instant(false, "instants"),
|
||||||
Land(true),
|
Land(true, "lands"),
|
||||||
Phenomenon(false),
|
Phenomenon(false, "phenomenons"),
|
||||||
Plane(false),
|
Plane(false, "planes"),
|
||||||
Planeswalker(true),
|
Planeswalker(true, "planeswalkers"),
|
||||||
Scheme(false),
|
Scheme(false, "schemes"),
|
||||||
Sorcery(false),
|
Sorcery(false, "sorceries"),
|
||||||
Tribal(false),
|
Tribal(false, "tribals"),
|
||||||
Vanguard(false);
|
Vanguard(false, "vanguards");
|
||||||
|
|
||||||
public final boolean isPermanent;
|
public final boolean isPermanent;
|
||||||
|
public final String pluralName;
|
||||||
private static Map<String, CoreType> stringToCoreType = EnumUtils.getEnumMap(CoreType.class);
|
private static Map<String, CoreType> stringToCoreType = EnumUtils.getEnumMap(CoreType.class);
|
||||||
private static final Set<String> allCoreTypeNames = stringToCoreType.keySet();
|
private static final Set<String> allCoreTypeNames = stringToCoreType.keySet();
|
||||||
|
|
||||||
@@ -80,8 +83,9 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
return stringToCoreType.containsKey(name);
|
return stringToCoreType.containsKey(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreType(final boolean permanent) {
|
CoreType(final boolean permanent, final String plural) {
|
||||||
isPermanent = permanent;
|
isPermanent = permanent;
|
||||||
|
pluralName = plural;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,11 +113,14 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
private final Set<CoreType> coreTypes = EnumSet.noneOf(CoreType.class);
|
private final Set<CoreType> coreTypes = EnumSet.noneOf(CoreType.class);
|
||||||
private final Set<Supertype> supertypes = EnumSet.noneOf(Supertype.class);
|
private final Set<Supertype> supertypes = EnumSet.noneOf(Supertype.class);
|
||||||
private final Set<String> subtypes = Sets.newLinkedHashSet();
|
private final Set<String> subtypes = Sets.newLinkedHashSet();
|
||||||
|
private boolean incomplete = false;
|
||||||
private transient String calculatedType = null;
|
private transient String calculatedType = null;
|
||||||
|
|
||||||
public CardType() {
|
public CardType(boolean incomplete) {
|
||||||
|
this.incomplete = incomplete;
|
||||||
}
|
}
|
||||||
public CardType(final Iterable<String> from0) {
|
public CardType(final Iterable<String> from0, boolean incomplete) {
|
||||||
|
this.incomplete = incomplete;
|
||||||
addAll(from0);
|
addAll(from0);
|
||||||
}
|
}
|
||||||
public CardType(final CardType from0) {
|
public CardType(final CardType from0) {
|
||||||
@@ -152,6 +159,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sanisfySubtypes();
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
public boolean addAll(final CardType type) {
|
public boolean addAll(final CardType type) {
|
||||||
@@ -159,6 +167,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
if (coreTypes.addAll(type.coreTypes)) { changed = true; }
|
if (coreTypes.addAll(type.coreTypes)) { changed = true; }
|
||||||
if (supertypes.addAll(type.supertypes)) { changed = true; }
|
if (supertypes.addAll(type.supertypes)) { changed = true; }
|
||||||
if (subtypes.addAll(type.subtypes)) { changed = true; }
|
if (subtypes.addAll(type.subtypes)) { changed = true; }
|
||||||
|
sanisfySubtypes();
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
public boolean addAll(final CardTypeView type) {
|
public boolean addAll(final CardTypeView type) {
|
||||||
@@ -166,6 +175,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
if (Iterables.addAll(coreTypes, type.getCoreTypes())) { changed = true; }
|
if (Iterables.addAll(coreTypes, type.getCoreTypes())) { changed = true; }
|
||||||
if (Iterables.addAll(supertypes, type.getSupertypes())) { changed = true; }
|
if (Iterables.addAll(supertypes, type.getSupertypes())) { changed = true; }
|
||||||
if (Iterables.addAll(subtypes, type.getSubtypes())) { changed = true; }
|
if (Iterables.addAll(subtypes, type.getSubtypes())) { changed = true; }
|
||||||
|
sanisfySubtypes();
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +185,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
if (supertypes.removeAll(type.supertypes)) { changed = true; }
|
if (supertypes.removeAll(type.supertypes)) { changed = true; }
|
||||||
if (subtypes.removeAll(type.subtypes)) { changed = true; }
|
if (subtypes.removeAll(type.subtypes)) { changed = true; }
|
||||||
if (changed) {
|
if (changed) {
|
||||||
|
sanisfySubtypes();
|
||||||
calculatedType = null;
|
calculatedType = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -211,6 +222,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
|
sanisfySubtypes();
|
||||||
calculatedType = null;
|
calculatedType = null;
|
||||||
}
|
}
|
||||||
return changed;
|
return changed;
|
||||||
@@ -223,7 +235,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
}
|
}
|
||||||
boolean changed = Iterables.removeIf(subtypes, Predicates.IS_CREATURE_TYPE);
|
boolean changed = Iterables.removeIf(subtypes, Predicates.IS_CREATURE_TYPE);
|
||||||
// need to remove AllCreatureTypes too when setting Creature Type
|
// need to remove AllCreatureTypes too when setting Creature Type
|
||||||
if (subtypes.remove("AllCreatureTypes")) {
|
if (subtypes.remove(AllCreatureTypes)) {
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
subtypes.addAll(ctypes);
|
subtypes.addAll(ctypes);
|
||||||
@@ -252,7 +264,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
final Set<String> creatureTypes = Sets.newHashSet();
|
final Set<String> creatureTypes = Sets.newHashSet();
|
||||||
if (isCreature() || isTribal()) {
|
if (isCreature() || isTribal()) {
|
||||||
for (final String t : subtypes) {
|
for (final String t : subtypes) {
|
||||||
if (isACreatureType(t) || t.equals("AllCreatureTypes")) {
|
if (isACreatureType(t) || t.equals(AllCreatureTypes)) {
|
||||||
creatureTypes.add(t);
|
creatureTypes.add(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -302,7 +314,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public boolean hasSubtype(final String subtype) {
|
public boolean hasSubtype(final String subtype) {
|
||||||
if (isACreatureType(subtype) && subtypes.contains("AllCreatureTypes")) {
|
if (isACreatureType(subtype) && subtypes.contains(AllCreatureTypes)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return subtypes.contains(subtype);
|
return subtypes.contains(subtype);
|
||||||
@@ -315,7 +327,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
creatureType = toMixedCase(creatureType);
|
creatureType = toMixedCase(creatureType);
|
||||||
if (!isACreatureType(creatureType)) { return false; }
|
if (!isACreatureType(creatureType)) { return false; }
|
||||||
|
|
||||||
return subtypes.contains(creatureType) || subtypes.contains("AllCreatureTypes");
|
return subtypes.contains(creatureType) || subtypes.contains(AllCreatureTypes);
|
||||||
}
|
}
|
||||||
private static String toMixedCase(final String s) {
|
private static String toMixedCase(final String s) {
|
||||||
if (s.isEmpty()) {
|
if (s.isEmpty()) {
|
||||||
@@ -485,7 +497,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
if (ct.isRemoveCreatureTypes()) {
|
if (ct.isRemoveCreatureTypes()) {
|
||||||
Iterables.removeIf(newType.subtypes, Predicates.IS_CREATURE_TYPE);
|
Iterables.removeIf(newType.subtypes, Predicates.IS_CREATURE_TYPE);
|
||||||
// need to remove AllCreatureTypes too when removing creature Types
|
// need to remove AllCreatureTypes too when removing creature Types
|
||||||
newType.subtypes.remove("AllCreatureTypes");
|
newType.subtypes.remove(AllCreatureTypes);
|
||||||
}
|
}
|
||||||
if (ct.isRemoveArtifactTypes()) {
|
if (ct.isRemoveArtifactTypes()) {
|
||||||
Iterables.removeIf(newType.subtypes, Predicates.IS_ARTIFACT_TYPE);
|
Iterables.removeIf(newType.subtypes, Predicates.IS_ARTIFACT_TYPE);
|
||||||
@@ -503,29 +515,37 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
}
|
}
|
||||||
// sanisfy subtypes
|
// sanisfy subtypes
|
||||||
if (newType != null && !newType.subtypes.isEmpty()) {
|
if (newType != null && !newType.subtypes.isEmpty()) {
|
||||||
if (!newType.isCreature() && !newType.isTribal()) {
|
newType.sanisfySubtypes();
|
||||||
Iterables.removeIf(newType.subtypes, Predicates.IS_CREATURE_TYPE);
|
|
||||||
newType.subtypes.remove("AllCreatureTypes");
|
|
||||||
}
|
|
||||||
if (!newType.isLand()) {
|
|
||||||
Iterables.removeIf(newType.subtypes, Predicates.IS_LAND_TYPE);
|
|
||||||
}
|
|
||||||
if (!newType.isArtifact()) {
|
|
||||||
Iterables.removeIf(newType.subtypes, Predicates.IS_ARTIFACT_TYPE);
|
|
||||||
}
|
|
||||||
if (!newType.isEnchantment()) {
|
|
||||||
Iterables.removeIf(newType.subtypes, Predicates.IS_ENCHANTMENT_TYPE);
|
|
||||||
}
|
|
||||||
if (!newType.isInstant() && !newType.isSorcery()) {
|
|
||||||
Iterables.removeIf(newType.subtypes, Predicates.IS_SPELL_TYPE);
|
|
||||||
}
|
|
||||||
if (!newType.isPlaneswalker() && !newType.isEmblem()) {
|
|
||||||
Iterables.removeIf(newType.subtypes, Predicates.IS_WALKER_TYPE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return newType == null ? this : newType;
|
return newType == null ? this : newType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sanisfySubtypes() {
|
||||||
|
// incomplete types are used for changing effects
|
||||||
|
if (this.incomplete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isCreature() && !isTribal()) {
|
||||||
|
Iterables.removeIf(subtypes, Predicates.IS_CREATURE_TYPE);
|
||||||
|
subtypes.remove(AllCreatureTypes);
|
||||||
|
}
|
||||||
|
if (!isLand()) {
|
||||||
|
Iterables.removeIf(subtypes, Predicates.IS_LAND_TYPE);
|
||||||
|
}
|
||||||
|
if (!isArtifact()) {
|
||||||
|
Iterables.removeIf(subtypes, Predicates.IS_ARTIFACT_TYPE);
|
||||||
|
}
|
||||||
|
if (!isEnchantment()) {
|
||||||
|
Iterables.removeIf(subtypes, Predicates.IS_ENCHANTMENT_TYPE);
|
||||||
|
}
|
||||||
|
if (!isInstant() && !isSorcery()) {
|
||||||
|
Iterables.removeIf(subtypes, Predicates.IS_SPELL_TYPE);
|
||||||
|
}
|
||||||
|
if (!isPlaneswalker() && !isEmblem()) {
|
||||||
|
Iterables.removeIf(subtypes, Predicates.IS_WALKER_TYPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<String> iterator() {
|
public Iterator<String> iterator() {
|
||||||
final Iterator<CoreType> coreTypeIterator = coreTypes.iterator();
|
final Iterator<CoreType> coreTypeIterator = coreTypes.iterator();
|
||||||
@@ -560,7 +580,64 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
return toString().compareTo(o.toString());
|
return toString().compareTo(o.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean sharesSubtypeWith(final CardType ctOther) {
|
public boolean sharesCreaturetypeWith(final CardTypeView ctOther) {
|
||||||
|
if (ctOther == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.subtypes.contains(AllCreatureTypes) && ctOther.hasSubtype(AllCreatureTypes)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (final String type : getCreatureTypes()) {
|
||||||
|
if (ctOther.hasCreatureType(type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean sharesLandTypeWith(final CardTypeView ctOther) {
|
||||||
|
if (ctOther == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final String type : getLandTypes()) {
|
||||||
|
if (ctOther.hasSubtype(type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean sharesPermanentTypeWith(final CardTypeView ctOther) {
|
||||||
|
if (ctOther == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final CoreType type : getCoreTypes()) {
|
||||||
|
if (type.isPermanent && ctOther.hasType(type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean sharesCardTypeWith(final CardTypeView ctOther) {
|
||||||
|
if (ctOther == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final CoreType type : getCoreTypes()) {
|
||||||
|
if (ctOther.hasType(type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean sharesSubtypeWith(final CardTypeView ctOther) {
|
||||||
|
if (ctOther == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
for (final String t : ctOther.getSubtypes()) {
|
for (final String t : ctOther.getSubtypes()) {
|
||||||
if (hasSubtype(t)) {
|
if (hasSubtype(t)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -569,11 +646,11 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CardType parse(final String typeText) {
|
public static CardType parse(final String typeText, boolean incomplete) {
|
||||||
// Most types and subtypes, except "Serra's Realm" and
|
// Most types and subtypes, except "Serra's Realm" and
|
||||||
// "Bolas's Meditation Realm" consist of only one word
|
// "Bolas's Meditation Realm" consist of only one word
|
||||||
final char space = ' ';
|
final char space = ' ';
|
||||||
final CardType result = new CardType();
|
final CardType result = new CardType(incomplete);
|
||||||
|
|
||||||
int iTypeStart = 0;
|
int iTypeStart = 0;
|
||||||
int iSpace = typeText.indexOf(space);
|
int iSpace = typeText.indexOf(space);
|
||||||
@@ -593,7 +670,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static CardType combine(final CardType a, final CardType b) {
|
public static CardType combine(final CardType a, final CardType b) {
|
||||||
final CardType result = new CardType();
|
final CardType result = new CardType(false);
|
||||||
result.supertypes.addAll(a.supertypes);
|
result.supertypes.addAll(a.supertypes);
|
||||||
result.supertypes.addAll(b.supertypes);
|
result.supertypes.addAll(b.supertypes);
|
||||||
result.coreTypes.addAll(a.coreTypes);
|
result.coreTypes.addAll(a.coreTypes);
|
||||||
@@ -629,6 +706,12 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
public static final BiMap<String,String> pluralTypes = HashBiMap.create();
|
public static final BiMap<String,String> pluralTypes = HashBiMap.create();
|
||||||
// plural -> singular
|
// plural -> singular
|
||||||
public static final BiMap<String,String> singularTypes = pluralTypes.inverse();
|
public static final BiMap<String,String> singularTypes = pluralTypes.inverse();
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (CoreType c : CoreType.values()) {
|
||||||
|
pluralTypes.put(c.name(), c.pluralName);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public static class Predicates {
|
public static class Predicates {
|
||||||
public static Predicate<String> IS_LAND_TYPE = new Predicate<String>() {
|
public static Predicate<String> IS_LAND_TYPE = new Predicate<String>() {
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ public interface CardTypeView extends Iterable<String>, Serializable {
|
|||||||
boolean hasSupertype(Supertype supertype);
|
boolean hasSupertype(Supertype supertype);
|
||||||
boolean hasSubtype(String subtype);
|
boolean hasSubtype(String subtype);
|
||||||
boolean hasCreatureType(String creatureType);
|
boolean hasCreatureType(String creatureType);
|
||||||
|
|
||||||
|
public boolean sharesCreaturetypeWith(final CardTypeView ctOther);
|
||||||
|
public boolean sharesLandTypeWith(final CardTypeView ctOther);
|
||||||
|
public boolean sharesPermanentTypeWith(final CardTypeView ctOther);
|
||||||
|
public boolean sharesCardTypeWith(final CardTypeView ctOther);
|
||||||
|
|
||||||
boolean isPermanent();
|
boolean isPermanent();
|
||||||
boolean isCreature();
|
boolean isCreature();
|
||||||
boolean isPlaneswalker();
|
boolean isPlaneswalker();
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ public interface ICardDatabase extends Iterable<PaperCard> {
|
|||||||
int getArtCount(String cardName, String edition);
|
int getArtCount(String cardName, String edition);
|
||||||
|
|
||||||
Collection<PaperCard> getUniqueCards();
|
Collection<PaperCard> getUniqueCards();
|
||||||
List<PaperCard> getAllCards();
|
Collection<PaperCard> getAllCards();
|
||||||
List<PaperCard> getAllCards(String cardName);
|
Collection<PaperCard> getAllCards(String cardName);
|
||||||
List<PaperCard> getAllCards(Predicate<PaperCard> predicate);
|
Collection<PaperCard> getAllCards(Predicate<PaperCard> predicate);
|
||||||
|
|
||||||
List<PaperCard> getAllCardsFromEdition(CardEdition edition);
|
List<PaperCard> getAllCardsFromEdition(CardEdition edition);
|
||||||
|
|
||||||
|
|||||||
@@ -139,16 +139,22 @@ public final class MagicColor {
|
|||||||
public static final ImmutableList<String> SNOW_LANDS = ImmutableList.of("Snow-Covered Plains", "Snow-Covered Island", "Snow-Covered Swamp", "Snow-Covered Mountain", "Snow-Covered Forest");
|
public static final ImmutableList<String> SNOW_LANDS = ImmutableList.of("Snow-Covered Plains", "Snow-Covered Island", "Snow-Covered Swamp", "Snow-Covered Mountain", "Snow-Covered Forest");
|
||||||
public static final ImmutableMap<String, String> ANY_COLOR_CONVERSION = new ImmutableMap.Builder<String, String>()
|
public static final ImmutableMap<String, String> ANY_COLOR_CONVERSION = new ImmutableMap.Builder<String, String>()
|
||||||
.put("ManaColorConversion", "Additive")
|
.put("ManaColorConversion", "Additive")
|
||||||
.put("WhiteConversion", "All")
|
.put("WhiteConversion", "Color")
|
||||||
.put("BlueConversion", "All")
|
.put("BlueConversion", "Color")
|
||||||
.put("BlackConversion", "All")
|
.put("BlackConversion", "Color")
|
||||||
.put("RedConversion", "All")
|
.put("RedConversion", "Color")
|
||||||
.put("GreenConversion", "All")
|
.put("GreenConversion", "Color")
|
||||||
|
.put("ColorlessConversion", "Color")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static final ImmutableMap<String, String> ANY_TYPE_CONVERSION = new ImmutableMap.Builder<String, String>()
|
public static final ImmutableMap<String, String> ANY_TYPE_CONVERSION = new ImmutableMap.Builder<String, String>()
|
||||||
.putAll(ANY_COLOR_CONVERSION)
|
.put("ManaColorConversion", "Additive")
|
||||||
.put("ColorlessConversion", "All")
|
.put("WhiteConversion", "Type")
|
||||||
|
.put("BlueConversion", "Type")
|
||||||
|
.put("BlackConversion", "Type")
|
||||||
|
.put("RedConversion", "Type")
|
||||||
|
.put("GreenConversion", "Type")
|
||||||
|
.put("ColorlessConversion", "Type")
|
||||||
.build();
|
.build();
|
||||||
/**
|
/**
|
||||||
* Private constructor to prevent instantiation.
|
* Private constructor to prevent instantiation.
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import forge.deck.CardPool;
|
|||||||
import forge.item.PaperCard;
|
import forge.item.PaperCard;
|
||||||
import forge.util.ItemPool;
|
import forge.util.ItemPool;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
import forge.util.storage.IStorage;
|
||||||
|
import forge.util.storage.StorageExtendable;
|
||||||
import forge.util.storage.StorageReaderFileSections;
|
import forge.util.storage.StorageReaderFileSections;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -23,6 +25,18 @@ public class PrintSheet {
|
|||||||
@Override public final String apply(PrintSheet sheet) { return sheet.name; }
|
@Override public final String apply(PrintSheet sheet) { return sheet.name; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static final IStorage<PrintSheet> initializePrintSheets(File sheetsFile, CardEdition.Collection editions) {
|
||||||
|
IStorage<PrintSheet> sheets = new StorageExtendable<>("Special print runs", new PrintSheet.Reader(sheetsFile));
|
||||||
|
|
||||||
|
for(CardEdition edition : editions) {
|
||||||
|
for(PrintSheet ps : edition.getPrintSheetsBySection()) {
|
||||||
|
System.out.println(ps.name);
|
||||||
|
sheets.add(ps.name, ps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sheets;
|
||||||
|
}
|
||||||
|
|
||||||
private final ItemPool<PaperCard> cardsWithWeights;
|
private final ItemPool<PaperCard> cardsWithWeights;
|
||||||
|
|
||||||
@@ -78,6 +92,16 @@ public class PrintSheet {
|
|||||||
return fetchRoulette(sum + 1, roulette, toSkip); // start over from beginning, in case last cards were to skip
|
return fetchRoulette(sum + 1, roulette, toSkip); // start over from beginning, in case last cards were to skip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<PaperCard> all() {
|
||||||
|
List<PaperCard> result = new ArrayList<>();
|
||||||
|
for(Entry<PaperCard, Integer> kv : cardsWithWeights) {
|
||||||
|
for(int i = 0; i < kv.getValue(); i++) {
|
||||||
|
result.add(kv.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public List<PaperCard> random(int number, boolean wantUnique) {
|
public List<PaperCard> random(int number, boolean wantUnique) {
|
||||||
List<PaperCard> result = new ArrayList<>();
|
List<PaperCard> result = new ArrayList<>();
|
||||||
|
|
||||||
|
|||||||
@@ -229,6 +229,13 @@ public class BoosterGenerator {
|
|||||||
String sheetKey = StaticData.instance().getEditions().contains(setCode) ? slotType.trim() + " " + setCode
|
String sheetKey = StaticData.instance().getEditions().contains(setCode) ? slotType.trim() + " " + setCode
|
||||||
: slotType.trim();
|
: slotType.trim();
|
||||||
|
|
||||||
|
if (sheetKey.startsWith("wholeSheet")) {
|
||||||
|
PrintSheet ps = getPrintSheet(sheetKey);
|
||||||
|
result.addAll(ps.all());
|
||||||
|
sheetsUsed.add(ps);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
slotType = slotType.split("[ :!]")[0]; // add expansion symbol here?
|
slotType = slotType.split("[ :!]")[0]; // add expansion symbol here?
|
||||||
|
|
||||||
boolean foilInThisSlot = hasFoil && (slotType.equals(foilSlot));
|
boolean foilInThisSlot = hasFoil && (slotType.equals(foilSlot));
|
||||||
@@ -332,6 +339,11 @@ public class BoosterGenerator {
|
|||||||
if (!boosterMustContain.isEmpty()) {
|
if (!boosterMustContain.isEmpty()) {
|
||||||
ensureGuaranteedCardInBooster(result, template, boosterMustContain);
|
ensureGuaranteedCardInBooster(result, template, boosterMustContain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String boosterReplaceSlotFromPrintSheet = edition.getBoosterReplaceSlotFromPrintSheet();
|
||||||
|
if(!boosterReplaceSlotFromPrintSheet.isEmpty()) {
|
||||||
|
replaceCardFromExtraSheet(result, boosterReplaceSlotFromPrintSheet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -383,24 +395,66 @@ public class BoosterGenerator {
|
|||||||
|
|
||||||
if (!possibleCards.isEmpty()) {
|
if (!possibleCards.isEmpty()) {
|
||||||
PaperCard toAdd = Aggregates.random(possibleCards);
|
PaperCard toAdd = Aggregates.random(possibleCards);
|
||||||
PaperCard toRepl = null;
|
BoosterGenerator.replaceCard(result, toAdd);
|
||||||
CardRarity tgtRarity = toAdd.getRarity();
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// remove the first card of the same rarity, replace it with toAdd. Keep the foil state.
|
/**
|
||||||
for (PaperCard repl : result) {
|
* Replaces an already present card in the booster with a card from the supplied print sheet.
|
||||||
if (repl.getRarity() == tgtRarity) {
|
* Nothing is replaced if there is no matching rarity found.
|
||||||
toRepl = repl;
|
* @param booster in which a card gets replaced
|
||||||
|
* @param printSheetKey
|
||||||
|
*/
|
||||||
|
public static void replaceCardFromExtraSheet(List<PaperCard> booster, String printSheetKey) {
|
||||||
|
PrintSheet replacementSheet = StaticData.instance().getPrintSheets().get(printSheetKey);
|
||||||
|
PaperCard toAdd = replacementSheet.random(1, false).get(0);
|
||||||
|
BoosterGenerator.replaceCard(booster, toAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces an already present card with the supplied card of the same (or similar in case or rare/mythic)
|
||||||
|
* rarity in the supplied booster. Nothing is replaced if there is no matching rarity found.
|
||||||
|
* @param booster in which a card gets replaced
|
||||||
|
* @param toAdd new card which replaces a card in the booster
|
||||||
|
*/
|
||||||
|
public static void replaceCard(List<PaperCard> booster, PaperCard toAdd) {
|
||||||
|
Predicate<PaperCard> rarityPredicate = null;
|
||||||
|
switch(toAdd.getRarity()){
|
||||||
|
case BasicLand:
|
||||||
|
rarityPredicate = Presets.IS_BASIC_LAND;
|
||||||
|
break;
|
||||||
|
case Common:
|
||||||
|
rarityPredicate = Presets.IS_COMMON;
|
||||||
|
break;
|
||||||
|
case Uncommon:
|
||||||
|
rarityPredicate = Presets.IS_UNCOMMON;
|
||||||
|
break;
|
||||||
|
case Rare:
|
||||||
|
case MythicRare:
|
||||||
|
rarityPredicate = Presets.IS_RARE_OR_MYTHIC;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
rarityPredicate = Presets.IS_SPECIAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PaperCard toReplace = null;
|
||||||
|
// Find first card in booster that matches the rarity
|
||||||
|
for (PaperCard card : booster) {
|
||||||
|
if(rarityPredicate.apply(card)) {
|
||||||
|
toReplace = card;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (toRepl != null) {
|
|
||||||
if (toRepl.isFoil()) {
|
// Replace card if match is found
|
||||||
|
if (toReplace != null) {
|
||||||
|
// Keep the foil state
|
||||||
|
if (toReplace.isFoil()) {
|
||||||
toAdd = StaticData.instance().getCommonCards().getFoiled(toAdd);
|
toAdd = StaticData.instance().getCommonCards().getFoiled(toAdd);
|
||||||
}
|
}
|
||||||
result.remove(toRepl);
|
booster.remove(toReplace);
|
||||||
result.add(toAdd);
|
booster.add(toAdd);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,9 +492,10 @@ public class BoosterGenerator {
|
|||||||
|
|
||||||
String mainCode = itMod.next();
|
String mainCode = itMod.next();
|
||||||
|
|
||||||
if (mainCode.regionMatches(true, 0, "fromSheet", 0, 9)) { // custom print sheet
|
if (mainCode.regionMatches(true, 0, "fromSheet", 0, 9) ||
|
||||||
|
mainCode.regionMatches(true, 0, "wholeSheet", 0, 10)
|
||||||
String sheetName = StringUtils.strip(mainCode.substring(9), "()\" ");
|
) { // custom print sheet
|
||||||
|
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
|
||||||
src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
|
src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
|
||||||
setPred = Predicates.alwaysTrue();
|
setPred = Predicates.alwaysTrue();
|
||||||
|
|
||||||
@@ -524,7 +579,12 @@ public class BoosterGenerator {
|
|||||||
|
|
||||||
Predicate<PaperCard> toAdd = null;
|
Predicate<PaperCard> toAdd = null;
|
||||||
if (operator.equalsIgnoreCase(BoosterSlots.DUAL_FACED_CARD)) {
|
if (operator.equalsIgnoreCase(BoosterSlots.DUAL_FACED_CARD)) {
|
||||||
toAdd = Predicates.compose(Predicates.or(CardRulesPredicates.splitType(CardSplitType.Transform), CardRulesPredicates.splitType(CardSplitType.Meld)),
|
toAdd = Predicates.compose(
|
||||||
|
Predicates.or(
|
||||||
|
CardRulesPredicates.splitType(CardSplitType.Transform),
|
||||||
|
CardRulesPredicates.splitType(CardSplitType.Meld),
|
||||||
|
CardRulesPredicates.splitType(CardSplitType.Modal)
|
||||||
|
),
|
||||||
PaperCard.FN_GET_RULES);
|
PaperCard.FN_GET_RULES);
|
||||||
} else if (operator.equalsIgnoreCase(BoosterSlots.LAND)) { toAdd = Predicates.compose(CardRulesPredicates.Presets.IS_LAND, PaperCard.FN_GET_RULES);
|
} else if (operator.equalsIgnoreCase(BoosterSlots.LAND)) { toAdd = Predicates.compose(CardRulesPredicates.Presets.IS_LAND, PaperCard.FN_GET_RULES);
|
||||||
} else if (operator.equalsIgnoreCase(BoosterSlots.BASIC_LAND)) { toAdd = IPaperCard.Predicates.Presets.IS_BASIC_LAND;
|
} else if (operator.equalsIgnoreCase(BoosterSlots.BASIC_LAND)) { toAdd = IPaperCard.Predicates.Presets.IS_BASIC_LAND;
|
||||||
|
|||||||
@@ -3,23 +3,20 @@ package forge.item.generation;
|
|||||||
import forge.card.CardEdition;
|
import forge.card.CardEdition;
|
||||||
import forge.item.BoosterPack;
|
import forge.item.BoosterPack;
|
||||||
import forge.item.PaperCard;
|
import forge.item.PaperCard;
|
||||||
|
import forge.util.BagRandomizer;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ChaosBoosterSupplier implements IUnOpenedProduct {
|
public class ChaosBoosterSupplier implements IUnOpenedProduct {
|
||||||
private List<CardEdition> sets;
|
private BagRandomizer<CardEdition> randomizer;
|
||||||
|
|
||||||
public ChaosBoosterSupplier(List<CardEdition> sets) {
|
public ChaosBoosterSupplier(Iterable<CardEdition> sets) throws IllegalArgumentException {
|
||||||
this.sets = sets;
|
randomizer = new BagRandomizer<>(sets);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PaperCard> get() {
|
public List<PaperCard> get() {
|
||||||
if (sets.size() == 0) {
|
final CardEdition set = randomizer.getNextItem();
|
||||||
System.out.println("No chaos boosters left to supply.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final CardEdition set = sets.remove(0);
|
|
||||||
final BoosterPack pack = new BoosterPack(set.getCode(), set.getBoosterTemplate());
|
final BoosterPack pack = new BoosterPack(set.getCode(), set.getBoosterTemplate());
|
||||||
return pack.getCards();
|
return pack.getCards();
|
||||||
}
|
}
|
||||||
|
|||||||
76
forge-core/src/main/java/forge/util/BagRandomizer.java
Normal file
76
forge-core/src/main/java/forge/util/BagRandomizer.java
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package forge.util;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data structure that allows random draws from a set number of items,
|
||||||
|
* where all items are returned once before the first will be retrieved.
|
||||||
|
* The bag will be shuffled after each time all items have been returned.
|
||||||
|
* @param <T> an object
|
||||||
|
*/
|
||||||
|
public class BagRandomizer<T > implements Iterable<T>{
|
||||||
|
private static Random random = new SecureRandom();
|
||||||
|
|
||||||
|
private T[] bag;
|
||||||
|
private int currentPosition = 0;
|
||||||
|
|
||||||
|
public BagRandomizer(T[] items) throws IllegalArgumentException {
|
||||||
|
if (items.length == 0) {
|
||||||
|
throw new IllegalArgumentException("Must include at least one item!");
|
||||||
|
}
|
||||||
|
bag = items;
|
||||||
|
shuffleBag();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BagRandomizer(Iterable<T> items) throws IllegalArgumentException {
|
||||||
|
ArrayList<T> list = new ArrayList<>();
|
||||||
|
for (T item : items) {
|
||||||
|
list.add(item);
|
||||||
|
}
|
||||||
|
if (list.size() == 0) {
|
||||||
|
throw new IllegalArgumentException("Must include at least one item!");
|
||||||
|
}
|
||||||
|
bag = (T[]) list.toArray();
|
||||||
|
shuffleBag();
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getNextItem() {
|
||||||
|
// reset bag if last position is reached
|
||||||
|
if (currentPosition >= bag.length) {
|
||||||
|
shuffleBag();
|
||||||
|
currentPosition = 0;
|
||||||
|
}
|
||||||
|
return bag[currentPosition++];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shuffleBag() {
|
||||||
|
int n = bag.length;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
int r = (int) (random.nextDouble() * (i + 1));
|
||||||
|
T swap = bag[r];
|
||||||
|
bag[r] = bag[i];
|
||||||
|
bag[i] = swap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return new BagRandomizerIterator<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BagRandomizerIterator<T> implements Iterator<T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return bag.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
return (T) BagRandomizer.this.getNextItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,7 +81,7 @@ public class ImageUtil {
|
|||||||
|
|
||||||
public static boolean hasBackFacePicture(PaperCard cp) {
|
public static boolean hasBackFacePicture(PaperCard cp) {
|
||||||
CardSplitType cst = cp.getRules().getSplitType();
|
CardSplitType cst = cp.getRules().getSplitType();
|
||||||
return cst == CardSplitType.Transform || cst == CardSplitType.Flip || cst == CardSplitType.Meld;
|
return cst == CardSplitType.Transform || cst == CardSplitType.Flip || cst == CardSplitType.Meld || cst == CardSplitType.Modal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getNameToUse(PaperCard cp, boolean backFace) {
|
public static String getNameToUse(PaperCard cp, boolean backFace) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user