mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 09:48:02 +00:00
Compare commits
7 Commits
exiledWith
...
rememberSp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da886202c0 | ||
|
|
4ab45ae42b | ||
|
|
bae8819630 | ||
|
|
19fbe18235 | ||
|
|
5e7d542d36 | ||
|
|
5e32834fb8 | ||
|
|
738e68f468 |
7
.classpath
Normal file
7
.classpath
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
286
.gitignore
vendored
286
.gitignore
vendored
@@ -1,77 +1,233 @@
|
||||
# Ignore IDEA config files
|
||||
|
||||
*.idea
|
||||
*.iml
|
||||
*.tmp
|
||||
.metadata
|
||||
.recommenders
|
||||
|
||||
|
||||
# Ignore Eclipse config files
|
||||
|
||||
.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
|
||||
/*.idea
|
||||
/*.iml
|
||||
/*.tmp
|
||||
/.metadata
|
||||
/.recommenders
|
||||
forge-ai/forge-ai.iml
|
||||
forge-ai/target
|
||||
forge-core/forge-core.iml
|
||||
forge-core/target
|
||||
forge-game/*.iml
|
||||
forge-game/target
|
||||
forge-gui-android/*.iml
|
||||
forge-gui-android/*.keystore
|
||||
forge-gui-android/**/Thumbs.db
|
||||
forge-gui-mobile-dev/**/Thumbs.db
|
||||
forge-gui-android/assets/fallback_skin/Thumbs.db
|
||||
forge-gui-android/bin
|
||||
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/target
|
||||
forge-gui-mobile-dev/testAssets
|
||||
|
||||
forge-gui/res/cardsfolder/*.bat
|
||||
|
||||
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/decks
|
||||
forge-gui/res/layouts
|
||||
forge-gui/res/pics*
|
||||
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/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/EditionTrackingResults
|
||||
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
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
Summary
|
||||
|
||||
(Summarize the bug encountered concisely)
|
||||
|
||||
|
||||
Steps to reproduce
|
||||
|
||||
(How one can reproduce the issue - this is very important. Specific cards and specific actions especially)
|
||||
|
||||
|
||||
Which version of Forge are you on (Release, Snapshot? Desktop, Android?)
|
||||
|
||||
|
||||
What is the current bug behavior?
|
||||
|
||||
(What actually happens)
|
||||
|
||||
|
||||
What is the expected correct behavior?
|
||||
|
||||
(What you should see instead)
|
||||
|
||||
|
||||
Relevant logs and/or screenshots
|
||||
|
||||
(Paste/Attach your game.log from the crash - please use code blocks (```)) Also, provide screenshots of the current state.
|
||||
|
||||
|
||||
Possible fixes
|
||||
|
||||
(If you can, link to the line of code that might be responsible for the problem)
|
||||
|
||||
/label ~needs-investigation
|
||||
@@ -1,15 +0,0 @@
|
||||
Summary
|
||||
|
||||
(Summarize the feature you wish concisely)
|
||||
|
||||
|
||||
Example screenshots
|
||||
|
||||
(If this is a UI change, please provide an example screenshot of how this feature might work)
|
||||
|
||||
|
||||
Feature type
|
||||
|
||||
(Where in Forge does this belong? e.g. Quest Mode, Deck Editor, Limited, Constructed, etc.)
|
||||
|
||||
/label ~feature request
|
||||
12
.project
Normal file
12
.project
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>forge</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
7
.settings/net.sf.jautodoc.prefs
Normal file
7
.settings/net.sf.jautodoc.prefs
Normal file
@@ -0,0 +1,7 @@
|
||||
add_header=true
|
||||
add_todo=false
|
||||
eclipse.preferences.version=1
|
||||
header_text=/*\n * Forge\: Play Magic\: the Gathering.\n * Copyright (C) 2011 Forge Team\n *\n * This program is free software\: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n * \n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n * \n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http\://www.gnu.org/licenses/>.\n */
|
||||
project_specific_settings=true
|
||||
replacements=<?xml version\="1.0" standalone\="yes"?>\n\n<replacements>\n<replacement key\="get" scope\="1" mode\="0">Gets the</replacement>\n<replacement key\="set" scope\="1" mode\="0">Sets the</replacement>\n<replacement key\="add" scope\="1" mode\="0">Adds the</replacement>\n<replacement key\="edit" scope\="1" mode\="0">Edits the</replacement>\n<replacement key\="remove" scope\="1" mode\="0">Removes the</replacement>\n<replacement key\="init" scope\="1" mode\="0">Inits the</replacement>\n<replacement key\="parse" scope\="1" mode\="0">Parses the</replacement>\n<replacement key\="create" scope\="1" mode\="0">Creates the</replacement>\n<replacement key\="build" scope\="1" mode\="0">Builds the</replacement>\n<replacement key\="is" scope\="1" mode\="0">Checks if is</replacement>\n<replacement key\="print" scope\="1" mode\="0">Prints the</replacement>\n<replacement key\="has" scope\="1" mode\="0">Checks for</replacement>\n</replacements>\n\n
|
||||
visibility_private=false
|
||||
2
.settings/org.eclipse.core.resources.prefs
Normal file
2
.settings/org.eclipse.core.resources.prefs
Normal file
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
||||
284
.settings/org.eclipse.jdt.core.prefs
Normal file
284
.settings/org.eclipse.jdt.core.prefs
Normal file
@@ -0,0 +1,284 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
||||
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
|
||||
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
|
||||
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
|
||||
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
|
||||
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
|
||||
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
|
||||
org.eclipse.jdt.core.formatter.comment.format_header=false
|
||||
org.eclipse.jdt.core.formatter.comment.format_html=true
|
||||
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
|
||||
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
|
||||
org.eclipse.jdt.core.formatter.comment.format_source_code=true
|
||||
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
|
||||
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
|
||||
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
|
||||
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
|
||||
org.eclipse.jdt.core.formatter.comment.line_length=80
|
||||
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
|
||||
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
|
||||
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
|
||||
org.eclipse.jdt.core.formatter.compact_else_if=true
|
||||
org.eclipse.jdt.core.formatter.continuation_indentation=2
|
||||
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
|
||||
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
|
||||
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
|
||||
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
|
||||
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
|
||||
org.eclipse.jdt.core.formatter.indent_empty_lines=false
|
||||
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
|
||||
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
|
||||
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
|
||||
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
|
||||
org.eclipse.jdt.core.formatter.indentation.size=4
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
|
||||
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
|
||||
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
|
||||
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
|
||||
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
|
||||
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
|
||||
org.eclipse.jdt.core.formatter.lineSplit=120
|
||||
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
|
||||
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
|
||||
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
|
||||
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
|
||||
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
|
||||
org.eclipse.jdt.core.formatter.tabulation.char=space
|
||||
org.eclipse.jdt.core.formatter.tabulation.size=4
|
||||
org.eclipse.jdt.core.formatter.use_on_off_tags=false
|
||||
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
|
||||
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
|
||||
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
|
||||
6
.settings/org.eclipse.jdt.ui.prefs
Normal file
6
.settings/org.eclipse.jdt.ui.prefs
Normal file
File diff suppressed because one or more lines are too long
4
.settings/org.eclipse.m2e.core.prefs
Normal file
4
.settings/org.eclipse.m2e.core.prefs
Normal file
@@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
674
LICENSE
674
LICENSE
@@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
230
README.md
230
README.md
@@ -1,230 +0,0 @@
|
||||
# Forge
|
||||
|
||||
Gitlab repo is found [here](https://git.cardforge.org/core-developers/forge).
|
||||
|
||||
Dev instructions here: [Getting Started](https://www.slightlymagic.net/wiki/Forge:How_to_Get_Started_Developing_Forge) (Somewhat outdated)
|
||||
|
||||
Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968)
|
||||
|
||||
# Requirements / Tools
|
||||
|
||||
- Java IDE such as IntelliJ or Eclipse
|
||||
- Java JDK 8 or later
|
||||
- Git
|
||||
- Git client (optional)
|
||||
- Maven
|
||||
- Gitlab account
|
||||
- Libgdx (optional: familiarity with this library is helpful for mobile platform development)
|
||||
- Android SDK (optional: for Android releases)
|
||||
- RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx)
|
||||
|
||||
# Project Quick Setup
|
||||
|
||||
- Log in to gitlab with your user account and fork the project.
|
||||
|
||||
- Clone your forked project to your local machine
|
||||
|
||||
- Go to the project location on your machine. Run Maven to download all dependencies and build a snapshot. Example for Windows & Linux: `mvn -U -B clean -P windows-linux install`
|
||||
|
||||
# Eclipse
|
||||
|
||||
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
|
||||
|
||||
## Project Setup
|
||||
|
||||
- Follow the instructions for cloning from Gitlab. You'll need a Gitlab account setup and an SSH key defined.
|
||||
|
||||
If you are on a Windows machine you can use Putty with TortoiseGit for SSH keys. Run puttygen.exe to generate the key -- save the private key and export
|
||||
the OpenSSH public key. If you just leave the dialog open, you can copy and paste the key from it to your Gitlab profile under
|
||||
"SSH keys". Run pageant.exe and add the private key generated earlier. TortoiseGit will use this for accessing Gitlab.
|
||||
|
||||
- Fork the Forge git repo to your Gitlab account.
|
||||
|
||||
- Clone your forked repo to your local machine.
|
||||
|
||||
- Make sure the Java SDK is installed -- not just the JRE. Java 8 or newer required. If you execute `java -version` at the shell or command prompt, it should report version 1.8 or later.
|
||||
|
||||
- Install Eclipse 2018-12 or later for Java. Launch it.
|
||||
|
||||
- Create a workspace. Go to the workbench. Right-click inside of Package Explorer > Import... > Maven > Existing Maven Projects > Navigate to root path of the local forge repo and
|
||||
ensure everything is checked > Finish.
|
||||
|
||||
- Let Eclipse run through building the project. You may be prompted for resolving any missing Maven plugins -- accept the ones offered. You may see errors appear in the "Problems" tab. These should
|
||||
be automatically resolved as plug-ins are installed and Eclipse continues the build process. If this is the first time for some plug-in installs, Eclipse may prompt you to restart. Do so. Be patient
|
||||
for this first time through.
|
||||
|
||||
- Once everything builds, all errors should disappear. You can now advance to Project launch.
|
||||
|
||||
## Project Launch
|
||||
|
||||
### Desktop
|
||||
|
||||
This is the standard configuration used for releasing to Windows / Linux / MacOS.
|
||||
|
||||
- Right-click on forge-gui-desktop > Run As... > Java Application > "Main - forge.view" > Ok
|
||||
|
||||
- The familiar Forge splash screen, etc. should appear. Enjoy!
|
||||
|
||||
### Mobile (Desktop dev)
|
||||
|
||||
This is the configuration used for doing mobile development using the Windows / Linux / MacOS front-end. Knowledge of libgdx is helpful here.
|
||||
|
||||
- Right-click on forge-gui-mobile-dev > Run As... > Java Application > "Main - forge.app" > Ok.
|
||||
|
||||
- A view similar to a mobile phone should appear. Enjoy!
|
||||
|
||||
## Eclipse / Android SDK Integration
|
||||
|
||||
Google no longer supports Android SDK releases for Eclipse. That said, it is still possible to build and debug Android platforms.
|
||||
|
||||
### Android SDK
|
||||
|
||||
Reference SO for obtaining a specific release: https://stackoverflow.com/questions/27043522/where-can-i-download-an-older-version-of-the-android-sdk
|
||||
|
||||
#### Windows
|
||||
|
||||
Download the following archived version of the Android SDK: http://dl-ssl.google.com/android/repository/tools_r25.2.3-windows.zip. Install it somewhere on your machine. This is referenced
|
||||
in the following instructions as your 'Android SDK Install' path.
|
||||
|
||||
#### Linux / Mac OSX
|
||||
|
||||
TBD
|
||||
|
||||
### Android Plugin for Eclipse
|
||||
|
||||
Google's last plugin release does not work completely with target's running Android 7.0 or later. Download the ADT-24.2.0-20160729.zip plugin
|
||||
from: https://github.com/khaledev/ADT/releases
|
||||
|
||||
In Eclipse go to: Help > Install New Software... > Add > Name: ADT Update, Click on the "Archive:" button and navigate to the downloaded ADT-24.2.0-20160729.zip file > Add. Install all "Developer Tools". Eclipse
|
||||
should restart and prompt you to run the SDK Manager. Launch it and continue to the next steps below.
|
||||
|
||||
### Android Platform
|
||||
|
||||
In Eclipse, if the SDK Manager is not already running, go to Window > Android SDK Manager. Install the following options / versions:
|
||||
|
||||
- Android SDK Build-tools 26.0.1
|
||||
- Android 7.1.1 (API 25) SDK Platform
|
||||
- Google USB Driver 11
|
||||
|
||||
Note that this will populate additional tools in the Android SDK install path extracted above.
|
||||
|
||||
### Proguard update
|
||||
|
||||
The Proguard included with the Android SDK Build-tools is outdated and does not work with Java 1.8. Download Proguard 6.0.3 from https://sourceforge.net/projects/proguard/files/proguard/6.0/.
|
||||
|
||||
- Go to the Android SDK install path. Rename the tools/proguard/ path to tools/proguard4.7/.
|
||||
|
||||
- Extract Proguard 6.0.3 to the Android SDK install path under tools/. You will need to rename the dir proguard6.0.3/ to proguard/.
|
||||
|
||||
### Android Build
|
||||
|
||||
The Eclipse plug-ins do NOT support building things for Android. They do however allow you to use the debugger so you can still set breakpoints and trace
|
||||
things out. The steps below show how to generate a debug Android build.
|
||||
|
||||
1) Create a Maven build for the forge top-level project. Right-click on the forge project. Run as.. > Maven build...
|
||||
- On the Main tab, set Goals: clean install
|
||||
|
||||
2) Run forge Maven build. If everything built, you should see "BUILD SUCCESS" in the Console View.
|
||||
|
||||
3) Right-click on the forge-gui-android project. Run as.. > Maven build...
|
||||
|
||||
- On the Main tab, set Goals: install, Profiles: android-debug
|
||||
- On the Environment tab, you may need to define the variable ANDROID_HOME with the value containing the path to your Android SDK installation. For example, Variable: ANDROID_HOME, Value: Your Android SDK install path here.
|
||||
|
||||
4) Run the forge-gui-android Maven build. This may take a few minutes. If everything worked, you should see "BUILD SUCCESS" in the Console View.
|
||||
|
||||
Assuming you got this far, you should have an Android forge-android-[version].apk in the forge-gui-android/target path.
|
||||
|
||||
### Android Deploy
|
||||
|
||||
You'll need to have the Android SDK install path platform-tools/ path in your command search path to easily deploy builds.
|
||||
|
||||
- Open a command prompt. Navigate to the forge-gui-android/target/ path.
|
||||
|
||||
- Connect your Android device to your dev machine.
|
||||
|
||||
- Ensure the device is visible using `adb devices`
|
||||
|
||||
- Remove the old Forge install if present: `adb uninstall forge.app`
|
||||
|
||||
- Install the new apk: `adb install forge-android-[version].apk`
|
||||
|
||||
### Android Debugging
|
||||
|
||||
Assuming the apk is installed, launch it from the device.
|
||||
|
||||
In Eclipse, launch the DDMS. Window > Perspective > Open Perspective > Other... > DDMS. You should see the forge app in the list. Highlight the app, click on the green debug button and a
|
||||
green debug button should appear next to the app's name. You can now set breakpoints and step through the source code.
|
||||
|
||||
## Windows / Linux SNAPSHOT build
|
||||
|
||||
SNAPSHOT builds can be built via the Maven integration in Eclipse.
|
||||
|
||||
1) Create a Maven build for the forge top-level project. Right-click on the forge project. Run as.. > Maven build...
|
||||
- On the Main tab, set Goals: clean install, set Profiles: windows-linux
|
||||
|
||||
2) Run forge Maven build. If everything built, you should see "BUILD SUCCESS" in the Console View.
|
||||
|
||||
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
|
||||
|
||||
# IntelliJ
|
||||
|
||||
Quick start guide for [setting up the Forge project within IntelliJ](https://git.cardforge.org/core-developers/forge/wikis/intellij-setup).
|
||||
|
||||
# Card Scripting
|
||||
|
||||
Visit [this page](https://www.slightlymagic.net/wiki/Forge_API) for information on scripting.
|
||||
|
||||
Card scripting resources are found in the forge-gui/res/ path.
|
||||
|
||||
# General Notes
|
||||
|
||||
## Project Hierarchy
|
||||
|
||||
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
|
||||
|
||||
- forge-ai
|
||||
- forge-core
|
||||
- forge-game
|
||||
- forge-gui
|
||||
|
||||
The platform-specific projects are:
|
||||
|
||||
- forge-gui-android
|
||||
- forge-gui-desktop
|
||||
- forge-gui-ios
|
||||
- forge-gui-mobile
|
||||
- forge-gui-mobile-dev
|
||||
|
||||
### forge-ai
|
||||
|
||||
### forge-core
|
||||
|
||||
### forge-game
|
||||
|
||||
### forge-gui
|
||||
|
||||
The forge-gui project includes the scripting resource definitions in the res/ path.
|
||||
|
||||
### forge-gui-android
|
||||
|
||||
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
|
||||
|
||||
### forge-gui-desktop
|
||||
|
||||
Java Swing based GUI targeting desktop machines.
|
||||
|
||||
Screen layout and game logic revolving around the GUI is found here. For example, the overlay arrows (when enabled) that indicate attackers and blockers, or the targets of the stack are defined and drawn by this.
|
||||
|
||||
### forge-gui-ios
|
||||
|
||||
Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic.
|
||||
|
||||
### forge-gui-mobile
|
||||
|
||||
Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) library. Screen layout and game logic revolving around the GUI for the mobile platforms is found here.
|
||||
|
||||
### forge-gui-mobile-dev
|
||||
|
||||
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
|
||||
|
||||
10
forge-ai/.classpath
Normal file
10
forge-ai/.classpath
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/forge-core"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
24
forge-ai/.project
Normal file
24
forge-ai/.project
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>forge-ai</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
<project>forge-game</project>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
3
forge-ai/.settings/org.eclipse.core.resources.prefs
Normal file
3
forge-ai/.settings/org.eclipse.core.resources.prefs
Normal file
@@ -0,0 +1,3 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=ISO-8859-1
|
||||
encoding/<project>=UTF-8
|
||||
5
forge-ai/.settings/org.eclipse.jdt.core.prefs
Normal file
5
forge-ai/.settings/org.eclipse.jdt.core.prefs
Normal file
@@ -0,0 +1,5 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
||||
4
forge-ai/.settings/org.eclipse.m2e.core.prefs
Normal file
4
forge-ai/.settings/org.eclipse.m2e.core.prefs
Normal file
@@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.40-SNAPSHOT</version>
|
||||
<version>1.6.20-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package forge.ai;
|
||||
|
||||
public enum AIOption {
|
||||
USE_SIMULATION
|
||||
USE_SIMULATION;
|
||||
}
|
||||
|
||||
@@ -17,26 +17,16 @@
|
||||
*/
|
||||
package forge.ai;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ability.AnimateAi;
|
||||
import forge.card.CardTypeView;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.ProtectEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.combat.GlobalAttackRestrictions;
|
||||
@@ -47,12 +37,14 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
|
||||
/**
|
||||
@@ -76,7 +68,6 @@ public class AiAttackController {
|
||||
private Player defendingOpponent;
|
||||
|
||||
private int aiAggression = 0; // added by Masher, how aggressive the ai is attack will be depending on circumstances
|
||||
private final boolean nextTurn;
|
||||
|
||||
|
||||
/**
|
||||
@@ -86,40 +77,34 @@ public class AiAttackController {
|
||||
*
|
||||
*/
|
||||
public AiAttackController(final Player ai) {
|
||||
this(ai, false);
|
||||
} // constructor
|
||||
|
||||
public AiAttackController(final Player ai, boolean nextTurn) {
|
||||
this.ai = ai;
|
||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||
this.myList = ai.getCreaturesInPlay();
|
||||
this.attackers = new ArrayList<>();
|
||||
this.attackers = new ArrayList<Card>();
|
||||
for (Card c : myList) {
|
||||
if (nextTurn && CombatUtil.canAttackNextTurn(c, this.defendingOpponent) ||
|
||||
CombatUtil.canAttack(c, this.defendingOpponent)) {
|
||||
if (CombatUtil.canAttack(c, this.defendingOpponent)) {
|
||||
attackers.add(c);
|
||||
}
|
||||
}
|
||||
this.blockers = getPossibleBlockers(oppList, this.attackers);
|
||||
this.nextTurn = nextTurn;
|
||||
} // overloaded constructor to evaluate attackers that should attack next turn
|
||||
} // constructor
|
||||
|
||||
public AiAttackController(final Player ai, Card attacker) {
|
||||
this.ai = ai;
|
||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||
this.myList = ai.getCreaturesInPlay();
|
||||
this.attackers = new ArrayList<>();
|
||||
this.attackers = new ArrayList<Card>();
|
||||
if (CombatUtil.canAttack(attacker, this.defendingOpponent)) {
|
||||
attackers.add(attacker);
|
||||
}
|
||||
this.blockers = getPossibleBlockers(oppList, this.attackers);
|
||||
this.nextTurn = false;
|
||||
} // overloaded constructor to evaluate single specified attacker
|
||||
|
||||
public static List<Card> getOpponentCreatures(final Player defender) {
|
||||
List<Card> defenders = new ArrayList<>(defender.getCreaturesInPlay());
|
||||
List<Card> defenders = new ArrayList<Card>();
|
||||
defenders.addAll(defender.getCreaturesInPlay());
|
||||
Predicate<Card> canAnimate = new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card c) {
|
||||
@@ -147,14 +132,6 @@ public class AiAttackController {
|
||||
this.oppList.remove(blocker);
|
||||
}
|
||||
|
||||
private boolean canAttackWrapper(final Card attacker, final GameEntity defender) {
|
||||
if (nextTurn) {
|
||||
return CombatUtil.canAttackNextTurn(attacker, defender);
|
||||
} else {
|
||||
return CombatUtil.canAttack(attacker, defender);
|
||||
}
|
||||
}
|
||||
|
||||
/** Choose opponent for AI to attack here. Expand as necessary. */
|
||||
private Player choosePreferredDefenderPlayer() {
|
||||
Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life
|
||||
@@ -174,7 +151,7 @@ public class AiAttackController {
|
||||
*
|
||||
*/
|
||||
public final static List<Card> sortAttackers(final List<Card> in) {
|
||||
final List<Card> list = new ArrayList<>();
|
||||
final List<Card> list = new ArrayList<Card>();
|
||||
|
||||
// Cards with triggers should come first (for Battle Cry)
|
||||
for (final Card attacker : in) {
|
||||
@@ -214,48 +191,14 @@ public class AiAttackController {
|
||||
return false;
|
||||
}
|
||||
|
||||
// the attacker will die to a triggered ability (e.g. Sarkhan the Masterless)
|
||||
for (Card c : ai.getOpponents().getCardsIn(ZoneType.Battlefield)) {
|
||||
for (Trigger t : c.getTriggers()) {
|
||||
if (t.getMode() == TriggerType.Attacks) {
|
||||
SpellAbility sa = t.ensureAbility();
|
||||
if (sa == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sa.getApi() == ApiType.EachDamage && "TriggeredAttacker".equals(sa.getParam("DefinedPlayers"))) {
|
||||
List<Card> valid = CardLists.getValidCards(c.getController().getCreaturesInPlay(), sa.getParam("ValidCards"), c.getController(), c, sa);
|
||||
// TODO: this assumes that 1 damage is dealt per creature. Improve this to check the parameter/X to determine
|
||||
// how much damage is dealt by each of the creatures in the valid list.
|
||||
if (attacker.getNetToughness() <= valid.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ("TRUE".equals(attacker.getSVar("HasAttackEffect"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Player opp = this.defendingOpponent;
|
||||
|
||||
// Damage opponent if unblocked
|
||||
final int dmgIfUnblocked = ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat, true);
|
||||
if (dmgIfUnblocked > 0) {
|
||||
boolean onlyIfExalted = false;
|
||||
if (combat.getAttackers().isEmpty() && ai.countExaltedBonus() > 0
|
||||
&& dmgIfUnblocked - ai.countExaltedBonus() == 0) {
|
||||
// Make sure we're not counting on the Exalted bonus when the AI is planning to attack with more than one creature
|
||||
onlyIfExalted = true;
|
||||
}
|
||||
|
||||
if (!onlyIfExalted || this.attackers.size() == 1 || this.aiAggression == 6 /* 6 is Exalted attack */) {
|
||||
if (ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat, true) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Poison opponent if unblocked
|
||||
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -270,7 +213,7 @@ public class AiAttackController {
|
||||
final CardCollectionView controlledByCompy = ai.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES);
|
||||
for (final Card c : controlledByCompy) {
|
||||
for (final Trigger trigger : c.getTriggers()) {
|
||||
if (ComputerUtilCombat.combatTriggerWillTrigger(attacker, null, trigger, combat, this.attackers)) {
|
||||
if (ComputerUtilCombat.combatTriggerWillTrigger(attacker, null, trigger, combat)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -279,7 +222,7 @@ public class AiAttackController {
|
||||
}
|
||||
|
||||
public final static List<Card> getPossibleBlockers(final List<Card> blockers, final List<Card> attackers) {
|
||||
List<Card> possibleBlockers = new ArrayList<>(blockers);
|
||||
List<Card> possibleBlockers = new ArrayList<Card>(blockers);
|
||||
possibleBlockers = CardLists.filter(possibleBlockers, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
@@ -290,7 +233,7 @@ public class AiAttackController {
|
||||
}
|
||||
|
||||
public final static boolean canBlockAnAttacker(final Card c, final List<Card> attackers, final boolean nextTurn) {
|
||||
final List<Card> attackerList = new ArrayList<>(attackers);
|
||||
final List<Card> attackerList = new ArrayList<Card>(attackers);
|
||||
if (!c.isCreature()) {
|
||||
return false;
|
||||
}
|
||||
@@ -303,7 +246,7 @@ public class AiAttackController {
|
||||
}
|
||||
|
||||
public final static Card getCardCanBlockAnAttacker(final Card c, final List<Card> attackers, final boolean nextTurn) {
|
||||
final List<Card> attackerList = new ArrayList<>(attackers);
|
||||
final List<Card> attackerList = new ArrayList<Card>(attackers);
|
||||
if (!c.isCreature()) {
|
||||
return null;
|
||||
}
|
||||
@@ -318,9 +261,9 @@ public class AiAttackController {
|
||||
// this checks to make sure that the computer player doesn't lose when the human player attacks
|
||||
// this method is used by getAttackers()
|
||||
public final List<Card> notNeededAsBlockers(final Player ai, final List<Card> attackers) {
|
||||
final List<Card> notNeededAsBlockers = new ArrayList<>(attackers);
|
||||
final List<Card> notNeededAsBlockers = new ArrayList<Card>(attackers);
|
||||
int fixedBlockers = 0;
|
||||
final List<Card> vigilantes = new ArrayList<>();
|
||||
final List<Card> vigilantes = new ArrayList<Card>();
|
||||
//check for time walks
|
||||
if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) {
|
||||
return attackers;
|
||||
@@ -359,7 +302,7 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
|
||||
List<Card> opponentsAttackers = new ArrayList<>(oppList);
|
||||
List<Card> opponentsAttackers = new ArrayList<Card>(oppList);
|
||||
opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
@@ -488,7 +431,7 @@ public class AiAttackController {
|
||||
final CardCollectionView beastions = ai.getCardsIn(ZoneType.Battlefield, "Beastmaster Ascension");
|
||||
int minCreatures = 7;
|
||||
for (final Card beastion : beastions) {
|
||||
final int counters = beastion.getCounters(CounterEnumType.QUEST);
|
||||
final int counters = beastion.getCounters(CounterType.QUEST);
|
||||
minCreatures = Math.min(minCreatures, 7 - counters);
|
||||
}
|
||||
if (this.attackers.size() >= minCreatures) {
|
||||
@@ -571,7 +514,8 @@ public class AiAttackController {
|
||||
remainingAttackers.removeAll(unblockedAttackers);
|
||||
|
||||
for (Card blocker : this.blockers) {
|
||||
if (blocker.canBlockAny()) {
|
||||
if (blocker.hasKeyword("CARDNAME can block any number of creatures.")
|
||||
|| blocker.hasKeyword("CARDNAME can block an additional ninety-nine creatures each combat.")) {
|
||||
for (Card attacker : this.attackers) {
|
||||
if (CombatUtil.canBlock(attacker, blocker)) {
|
||||
remainingAttackers.remove(attacker);
|
||||
@@ -587,19 +531,14 @@ public class AiAttackController {
|
||||
if (remainingAttackers.isEmpty() || maxBlockersAfterCrew == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
int numExtraBlocks = blocker.canBlockAdditional();
|
||||
if (numExtraBlocks > 0) {
|
||||
while (numExtraBlocks-- > 0 && !remainingAttackers.isEmpty()) {
|
||||
if (blocker.hasKeyword("CARDNAME can block an additional creature each combat.")) {
|
||||
blockedAttackers.add(remainingAttackers.get(0));
|
||||
remainingAttackers.remove(0);
|
||||
maxBlockersAfterCrew--;
|
||||
}
|
||||
}
|
||||
|
||||
if (remainingAttackers.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
blockedAttackers.add(remainingAttackers.get(0));
|
||||
remainingAttackers.remove(0);
|
||||
maxBlockersAfterCrew--;
|
||||
@@ -705,12 +644,10 @@ public class AiAttackController {
|
||||
final boolean bAssault = this.doAssault(ai);
|
||||
// TODO: detect Lightmine Field by presence of a card with a specific trigger
|
||||
final boolean lightmineField = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Lightmine Field");
|
||||
// TODO: detect Season of the Witch by presence of a card with a specific trigger
|
||||
final boolean seasonOfTheWitch = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Season of the Witch");
|
||||
|
||||
// Determine who will be attacked
|
||||
GameEntity defender = this.chooseDefender(combat, bAssault);
|
||||
List<Card> attackersLeft = new ArrayList<>(this.attackers);
|
||||
List<Card> attackersLeft = new ArrayList<Card>(this.attackers);
|
||||
|
||||
// TODO probably use AttackConstraints instead of only GlobalAttackRestrictions?
|
||||
GlobalAttackRestrictions restrict = GlobalAttackRestrictions.getGlobalRestrictions(ai, combat.getDefenders());
|
||||
@@ -727,9 +664,6 @@ public class AiAttackController {
|
||||
|
||||
// Attackers that don't really have a choice
|
||||
int numForcedAttackers = 0;
|
||||
// nextTurn is now only used by effect from Oracle en-Vec, which can skip check must attack,
|
||||
// because creatures not chosen can't attack.
|
||||
if (!nextTurn) {
|
||||
for (final Card attacker : this.attackers) {
|
||||
if (!CombatUtil.canAttack(attacker, defender)) {
|
||||
attackersLeft.remove(attacker);
|
||||
@@ -743,9 +677,6 @@ public class AiAttackController {
|
||||
} else if (attacker.hasSVar("EndOfTurnLeavePlay")
|
||||
&& isEffectiveAttacker(ai, attacker, combat)) {
|
||||
mustAttack = true;
|
||||
} else if (seasonOfTheWitch) {
|
||||
// TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack
|
||||
mustAttack = true;
|
||||
} else {
|
||||
for (KeywordInterface inst : attacker.getKeywords()) {
|
||||
String s = inst.getOriginal();
|
||||
@@ -766,17 +697,11 @@ public class AiAttackController {
|
||||
if (attackersLeft.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Lightmine Field: make sure the AI doesn't wipe out its own creatures
|
||||
if (lightmineField) {
|
||||
doLightmineFieldAttackLogic(attackersLeft, numForcedAttackers, playAggro);
|
||||
}
|
||||
// Revenge of Ravens: make sure the AI doesn't kill itself and doesn't damage itself unnecessarily
|
||||
if (!doRevengeOfRavensAttackLogic(ai, defender, attackersLeft, numForcedAttackers, attackMax)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (bAssault) {
|
||||
if (LOG_AI_ATTACKS)
|
||||
@@ -787,7 +712,7 @@ public class AiAttackController {
|
||||
if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
|
||||
return;
|
||||
|
||||
if (canAttackWrapper(attacker, defender) && this.isEffectiveAttacker(ai, attacker, combat)) {
|
||||
if (CombatUtil.canAttack(attacker, defender) && this.isEffectiveAttacker(ai, attacker, combat)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
}
|
||||
}
|
||||
@@ -828,7 +753,7 @@ public class AiAttackController {
|
||||
System.out.println("Exalted");
|
||||
this.aiAggression = 6;
|
||||
for (Card attacker : this.attackers) {
|
||||
if (canAttackWrapper(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) {
|
||||
if (CombatUtil.canAttack(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
return;
|
||||
}
|
||||
@@ -844,7 +769,7 @@ public class AiAttackController {
|
||||
// reached max, breakup
|
||||
if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
|
||||
break;
|
||||
if (canAttackWrapper(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) {
|
||||
if (CombatUtil.canAttack(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
}
|
||||
}
|
||||
@@ -862,12 +787,12 @@ public class AiAttackController {
|
||||
int humanForcesForAttritionalAttack = 0;
|
||||
|
||||
// examine the potential forces
|
||||
final List<Card> nextTurnAttackers = new ArrayList<>();
|
||||
final List<Card> nextTurnAttackers = new ArrayList<Card>();
|
||||
int candidateCounterAttackDamage = 0;
|
||||
|
||||
final Player opp = this.defendingOpponent;
|
||||
// get the potential damage and strength of the AI forces
|
||||
final List<Card> candidateAttackers = new ArrayList<>();
|
||||
final List<Card> candidateAttackers = new ArrayList<Card>();
|
||||
int candidateUnblockedDamage = 0;
|
||||
for (final Card pCard : this.myList) {
|
||||
// if the creature can attack then it's a potential attacker this
|
||||
@@ -926,7 +851,7 @@ public class AiAttackController {
|
||||
final int outNumber = computerForces - humanForces;
|
||||
|
||||
for (Card blocker : this.blockers) {
|
||||
if (blocker.canBlockAny()) {
|
||||
if (blocker.hasKeyword("CARDNAME can block any number of creatures.")) {
|
||||
aiLifeToPlayerDamageRatio--;
|
||||
}
|
||||
}
|
||||
@@ -949,7 +874,7 @@ public class AiAttackController {
|
||||
// get player life total
|
||||
int humanLife = opp.getLife();
|
||||
// get the list of attackers up to the first blocked one
|
||||
final List<Card> attritionalAttackers = new ArrayList<>();
|
||||
final List<Card> attritionalAttackers = new ArrayList<Card>();
|
||||
for (int x = 0; x < (this.attackers.size() - humanForces); x++) {
|
||||
attritionalAttackers.add(this.attackers.get(x));
|
||||
}
|
||||
@@ -1062,7 +987,7 @@ public class AiAttackController {
|
||||
} // stay at home to block
|
||||
|
||||
if ( LOG_AI_ATTACKS )
|
||||
System.out.println(this.aiAggression + " = ai aggression");
|
||||
System.out.println(String.valueOf(this.aiAggression) + " = ai aggression");
|
||||
|
||||
// ****************
|
||||
// Evaluation the end
|
||||
@@ -1085,7 +1010,7 @@ public class AiAttackController {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.shouldAttack(ai, attacker, this.blockers, combat) && canAttackWrapper(attacker, defender)) {
|
||||
if (this.shouldAttack(ai, attacker, this.blockers, combat) && CombatUtil.canAttack(attacker, defender)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
// check if attackers are enough to finish the attacked planeswalker
|
||||
if (defender instanceof Card) {
|
||||
@@ -1103,7 +1028,7 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
// if enough damage: switch to next planeswalker or player
|
||||
if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) {
|
||||
if (damage >= pw.getCounters(CounterType.LOYALTY)) {
|
||||
List<Card> pwDefending = combat.getDefendingPlaneswalkers();
|
||||
boolean found = false;
|
||||
// look for next planeswalker
|
||||
@@ -1173,6 +1098,7 @@ public class AiAttackController {
|
||||
// TODO Somehow subtract expected damage of other attacking creatures from enemy life total (how? other attackers not yet declared? Can the AI guesstimate which of their creatures will not get blocked?)
|
||||
if (attacker.getCurrentPower() * Integer.parseInt(attacker.getSVar("NonCombatPriority")) < ai.getOpponentsSmallestLifeTotal()) {
|
||||
// Check if the card actually has an ability the AI can and wants to play, if not, attacking is fine!
|
||||
boolean wantability = false;
|
||||
for (SpellAbility sa : attacker.getSpellAbilities()) {
|
||||
// Do not attack if we can afford using the ability.
|
||||
if (sa.isAbility()) {
|
||||
@@ -1193,24 +1119,8 @@ public class AiAttackController {
|
||||
// is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...)
|
||||
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
|
||||
|| "Blocked".equals(attacker.getSVar("HasAttackEffect"));
|
||||
|
||||
// contains only the defender's blockers that can actually block the attacker
|
||||
CardCollection validBlockers = CardLists.filter(defenders, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card defender) {
|
||||
return CombatUtil.canBlock(attacker, defender);
|
||||
}
|
||||
});
|
||||
|
||||
boolean canTrampleOverDefenders = attacker.hasKeyword(Keyword.TRAMPLE) && attacker.getNetCombatDamage() > Aggregates.sum(validBlockers, CardPredicates.Accessors.fnGetNetToughness);
|
||||
|
||||
// used to check that CanKillAllDangerous check makes sense in context where creatures with dangerous abilities are present
|
||||
boolean dangerousBlockersPresent = !CardLists.filter(validBlockers, Predicates.or(
|
||||
CardPredicates.hasKeyword(Keyword.WITHER), CardPredicates.hasKeyword(Keyword.INFECT),
|
||||
CardPredicates.hasKeyword(Keyword.LIFELINK))).isEmpty();
|
||||
|
||||
// total power of the defending creatures, used in predicting whether a gang block can kill the attacker
|
||||
int defPower = CardLists.getTotalPower(validBlockers, true, false);
|
||||
int defPower = CardLists.getTotalPower(defenders, true);
|
||||
|
||||
if (!hasCombatEffect) {
|
||||
for (KeywordInterface inst : attacker.getKeywords()) {
|
||||
@@ -1227,12 +1137,13 @@ public class AiAttackController {
|
||||
// number of factors about the attacking
|
||||
// context that will be relevant to the attackers decision according to
|
||||
// the selected strategy
|
||||
for (final Card defender : validBlockers) {
|
||||
for (final Card defender : defenders) {
|
||||
// if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check
|
||||
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
|
||||
if ((isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2)
|
||||
&& CombatUtil.canBlock(attacker, defender)) {
|
||||
numberOfPossibleBlockers += 1;
|
||||
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
|
||||
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterEnumType.P1P1) == 0)) {
|
||||
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterType.P1P1) == 0)) {
|
||||
canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature
|
||||
// see if the defending creature is of higher or lower
|
||||
// value. We don't want to attack only to lose value
|
||||
@@ -1267,7 +1178,7 @@ public class AiAttackController {
|
||||
// - our creature will die for sure (chump attack)
|
||||
// - our attack will not do anything special (no attack/combat effect to proc)
|
||||
// - we can't deal damage to our opponent with sheer number of attackers and/or our attacker's power is 0 or less
|
||||
if (attackerWillDie || (avoidAttackingIntoBlock && uselessAttack && noContributionToAttack)) {
|
||||
if (attackerWillDie || (avoidAttackingIntoBlock && (uselessAttack || noContributionToAttack))) {
|
||||
canKillAllDangerous = false;
|
||||
}
|
||||
}
|
||||
@@ -1293,10 +1204,6 @@ public class AiAttackController {
|
||||
if (LOG_AI_ATTACKS)
|
||||
System.out.println(attacker.getName() + " = attacking because they can't block, expecting to kill or damage player");
|
||||
return true;
|
||||
} else if (!canBeKilled && !dangerousBlockersPresent && canTrampleOverDefenders) {
|
||||
if (LOG_AI_ATTACKS)
|
||||
System.out.println(attacker.getName() + " = expecting to survive and get some Trample damage through");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (numberOfPossibleBlockers > 2
|
||||
@@ -1320,9 +1227,8 @@ public class AiAttackController {
|
||||
if (LOG_AI_ATTACKS)
|
||||
System.out.println(attacker.getName() + " = all out attacking");
|
||||
return true;
|
||||
case 4: // expecting to at least trade with something, or can attack "for free", expecting no counterattack
|
||||
if (canKillAll || (dangerousBlockersPresent && canKillAllDangerous && !canBeKilledByOne) || !canBeBlocked
|
||||
|| (defPower == 0 && !ComputerUtilCombat.lifeInDanger(ai, combat))) {
|
||||
case 4: // expecting to at least trade with something
|
||||
if (canKillAll || (canKillAllDangerous && !canBeKilledByOne) || !canBeBlocked) {
|
||||
if (LOG_AI_ATTACKS)
|
||||
System.out.println(attacker.getName() + " = attacking expecting to at least trade with something");
|
||||
return true;
|
||||
@@ -1330,7 +1236,7 @@ public class AiAttackController {
|
||||
break;
|
||||
case 3: // expecting to at least kill a creature of equal value or not be blocked
|
||||
if ((canKillAll && isWorthLessThanAllKillers)
|
||||
|| (((dangerousBlockersPresent && canKillAllDangerous) || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne)
|
||||
|| ((canKillAllDangerous || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne)
|
||||
|| !canBeBlocked) {
|
||||
if (LOG_AI_ATTACKS)
|
||||
System.out.println(attacker.getName() + " = attacking expecting to kill creature or cause damage, or is unblockable");
|
||||
@@ -1339,7 +1245,7 @@ public class AiAttackController {
|
||||
break;
|
||||
case 2: // attack expecting to attract a group block or destroying a single blocker and surviving
|
||||
if (!canBeBlocked || ((canKillAll || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne &&
|
||||
((dangerousBlockersPresent && canKillAllDangerous) || !canBeKilled))) {
|
||||
(canKillAllDangerous || !canBeKilled))) {
|
||||
if (LOG_AI_ATTACKS)
|
||||
System.out.println(attacker.getName() + " = attacking expecting to survive or attract group block");
|
||||
return true;
|
||||
@@ -1380,9 +1286,9 @@ public class AiAttackController {
|
||||
if (!TriggerType.Exerted.equals(t.getMode())) {
|
||||
continue;
|
||||
}
|
||||
SpellAbility sa = t.ensureAbility();
|
||||
SpellAbility sa = t.getOverridingAbility();
|
||||
if (sa == null) {
|
||||
continue;
|
||||
sa = AbilityFactory.getAbility(c, t.getParam("Execute"));
|
||||
}
|
||||
if (sa.usesTargeting()) {
|
||||
sa.setActivatingPlayer(c.getController());
|
||||
@@ -1409,12 +1315,21 @@ public class AiAttackController {
|
||||
if (c.hasSVar("AIExertCondition")) {
|
||||
if (!c.getSVar("AIExertCondition").isEmpty()) {
|
||||
final String needsToExert = c.getSVar("AIExertCondition");
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
String sVar = needsToExert.split(" ")[0];
|
||||
String comparator = needsToExert.split(" ")[1];
|
||||
String compareTo = comparator.substring(2);
|
||||
|
||||
int x = AbilityUtils.calculateAmount(c, sVar, null);
|
||||
int y = AbilityUtils.calculateAmount(c, compareTo, null);
|
||||
try {
|
||||
x = Integer.parseInt(sVar);
|
||||
} catch (final NumberFormatException e) {
|
||||
x = CardFactoryUtil.xCount(c, c.getSVar(sVar));
|
||||
}
|
||||
try {
|
||||
y = Integer.parseInt(compareTo);
|
||||
} catch (final NumberFormatException e) {
|
||||
y = CardFactoryUtil.xCount(c, c.getSVar(compareTo));
|
||||
}
|
||||
if (Expressions.compare(x, comparator, y)) {
|
||||
shouldExert = true;
|
||||
}
|
||||
@@ -1495,7 +1410,7 @@ public class AiAttackController {
|
||||
if (artifact != null) {
|
||||
return artifact;
|
||||
}
|
||||
return null;//should never get here
|
||||
return null; //should never get here
|
||||
}
|
||||
|
||||
private void doLightmineFieldAttackLogic(List<Card> attackersLeft, int numForcedAttackers, boolean playAggro) {
|
||||
@@ -1528,39 +1443,4 @@ public class AiAttackController {
|
||||
attackersLeft.removeAll(attUnsafe);
|
||||
}
|
||||
|
||||
private boolean doRevengeOfRavensAttackLogic(Player ai, GameEntity defender, List<Card> attackersLeft, int numForcedAttackers, int maxAttack) {
|
||||
// TODO: detect Revenge of Ravens by the trigger instead of by name
|
||||
boolean revengeOfRavens = false;
|
||||
if (defender instanceof Player) {
|
||||
revengeOfRavens = !CardLists.filter(((Player)defender).getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Revenge of Ravens")).isEmpty();
|
||||
} else if (defender instanceof Card) {
|
||||
revengeOfRavens = !CardLists.filter(((Card)defender).getController().getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Revenge of Ravens")).isEmpty();
|
||||
}
|
||||
|
||||
if (!revengeOfRavens) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int life = ai.canLoseLife() && !ai.cantLoseForZeroOrLessLife() ? ai.getLife() : Integer.MAX_VALUE;
|
||||
maxAttack = maxAttack < 0 ? Integer.MAX_VALUE - 1 : maxAttack;
|
||||
if (Math.min(maxAttack, numForcedAttackers) >= life) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove all 1-power attackers since they usually only hurt the attacker
|
||||
// TODO: improve to account for possible combat effects coming from attackers like that
|
||||
CardCollection attUnsafe = new CardCollection();
|
||||
for (Card attacker : attackersLeft) {
|
||||
if (attacker.getNetCombatDamage() <= 1) {
|
||||
attUnsafe.add(attacker);
|
||||
}
|
||||
}
|
||||
attackersLeft.removeAll(attUnsafe);
|
||||
if (Math.min(maxAttack, attackersLeft.size()) >= life) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // end class ComputerUtil_Attack2
|
||||
|
||||
@@ -17,23 +17,13 @@
|
||||
*/
|
||||
package forge.ai;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.CardTraitBase;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.keyword.Keyword;
|
||||
@@ -44,6 +34,8 @@ import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -79,8 +71,7 @@ public class AiBlockController {
|
||||
for (final Card blocker : blockersLeft) {
|
||||
// if the blocker can block a creature with lure it can't block a creature without
|
||||
if (CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||
boolean cantBlockAlone = blocker.hasKeyword("CARDNAME can't attack or block alone.") || blocker.hasKeyword("CARDNAME can't block alone.");
|
||||
if (solo && cantBlockAlone) {
|
||||
if (solo && blocker.hasKeyword("CARDNAME can't attack or block alone.")) {
|
||||
continue;
|
||||
}
|
||||
blockers.add(blocker);
|
||||
@@ -157,7 +148,9 @@ public class AiBlockController {
|
||||
final CardCollection attackers = combat.getAttackersOf(defender);
|
||||
// Begin with the attackers that pose the biggest threat
|
||||
CardLists.sortByPowerDesc(attackers);
|
||||
sortedAttackers.addAll(attackers);
|
||||
for (final Card c : attackers) {
|
||||
sortedAttackers.add(c);
|
||||
}
|
||||
} else if (defender instanceof Player && defender.equals(ai)) {
|
||||
firstAttacker = combat.getAttackersOf(defender);
|
||||
}
|
||||
@@ -170,7 +163,9 @@ public class AiBlockController {
|
||||
}
|
||||
} else {
|
||||
// add creatures attacking the Player to the back of the list
|
||||
sortedAttackers.addAll(firstAttacker);
|
||||
for (final Card c : firstAttacker) {
|
||||
sortedAttackers.add(c);
|
||||
}
|
||||
}
|
||||
return sortedAttackers;
|
||||
}
|
||||
@@ -238,9 +233,9 @@ public class AiBlockController {
|
||||
// 3.Blockers that can destroy the attacker and have an upside when dying
|
||||
killingBlockers = getKillingBlockers(combat, attacker, blockers);
|
||||
for (Card b : killingBlockers) {
|
||||
if ((b.hasKeyword(Keyword.UNDYING) && b.getCounters(CounterEnumType.P1P1) == 0) || b.hasSVar("SacMe")
|
||||
|| (b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterEnumType.TIME) == 1)
|
||||
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterEnumType.FADE) == 0)
|
||||
if ((b.hasKeyword(Keyword.UNDYING) && b.getCounters(CounterType.P1P1) == 0) || b.hasSVar("SacMe")
|
||||
|| (b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
|
||||
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
|
||||
|| b.hasSVar("EndOfTurnLeavePlay")) {
|
||||
blocker = b;
|
||||
break;
|
||||
@@ -272,14 +267,14 @@ public class AiBlockController {
|
||||
|
||||
if (mode == TriggerType.DamageDone) {
|
||||
if ((!trigParams.containsKey("ValidSource")
|
||||
|| trigger.matchesValid(attacker, trigParams.get("ValidSource").split(",")))
|
||||
|| CardTraitBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), attacker))
|
||||
&& attacker.getNetCombatDamage() > 0
|
||||
&& (!trigParams.containsKey("ValidTarget")
|
||||
|| trigger.matchesValid(combat.getDefenderByAttacker(attacker), trigParams.get("ValidTarget").split(",")))) {
|
||||
|| CardTraitBase.matchesValid(combat.getDefenderByAttacker(attacker), trigParams.get("ValidTarget").split(","), attacker))) {
|
||||
value += 50;
|
||||
}
|
||||
} else if (mode == TriggerType.AttackerUnblocked) {
|
||||
if (trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))) {
|
||||
if (CardTraitBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), attacker)) {
|
||||
value += 50;
|
||||
}
|
||||
}
|
||||
@@ -309,8 +304,8 @@ public class AiBlockController {
|
||||
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||
|
||||
for (Card b : blockers) {
|
||||
if ((b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterEnumType.TIME) == 1)
|
||||
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterEnumType.FADE) == 0)
|
||||
if ((b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
|
||||
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
|
||||
|| b.hasSVar("EndOfTurnLeavePlay")) {
|
||||
blocker = b;
|
||||
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) {
|
||||
@@ -486,7 +481,8 @@ public class AiBlockController {
|
||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, secondBlocker, combat, false);
|
||||
|
||||
List<Card> usableBlockersAsThird = new ArrayList<>(usableBlockers);
|
||||
List<Card> usableBlockersAsThird = new ArrayList<>();
|
||||
usableBlockersAsThird.addAll(usableBlockers);
|
||||
usableBlockersAsThird.remove(secondBlocker);
|
||||
|
||||
// loop over the remaining blockers in search of a good third blocker candidate
|
||||
@@ -777,12 +773,14 @@ public class AiBlockController {
|
||||
blockers.removeAll(combat.getBlockers(attacker));
|
||||
|
||||
// Don't add any blockers that won't kill the attacker because the damage would be prevented by a static effect
|
||||
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) {
|
||||
blockers = CardLists.filter(blockers, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card blocker) {
|
||||
return !ComputerUtilCombat.isCombatDamagePrevented(blocker, attacker, blocker.getNetCombatDamage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Try to use safe blockers first
|
||||
if (blockers.size() > 0) {
|
||||
@@ -861,7 +859,7 @@ public class AiBlockController {
|
||||
damageToPW += ComputerUtilCombat.predictDamageTo((Card) def, pwatkr.getNetCombatDamage(), pwatkr, true);
|
||||
}
|
||||
}
|
||||
if ((!onlyIfLethal && damageToPW > 0) || damageToPW >= def.getCounters(CounterEnumType.LOYALTY)) {
|
||||
if ((!onlyIfLethal && damageToPW > 0) || damageToPW >= ((Card) def).getCounters(CounterType.LOYALTY)) {
|
||||
threatenedPWs.add((Card) def);
|
||||
}
|
||||
}
|
||||
@@ -881,7 +879,7 @@ public class AiBlockController {
|
||||
if (!chumpPWDefenders.isEmpty()) {
|
||||
for (final Card attacker : attackers) {
|
||||
GameEntity def = combat.getDefenderByAttacker(attacker);
|
||||
if (def instanceof Card && threatenedPWs.contains(def)) {
|
||||
if (def instanceof Card && threatenedPWs.contains((Card) def)) {
|
||||
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
|
||||
// don't bother trying to chump a trampling creature
|
||||
continue;
|
||||
@@ -916,10 +914,10 @@ public class AiBlockController {
|
||||
pwDefenders.addAll(combat.getBlockers(pwAtk));
|
||||
} else {
|
||||
isFullyBlocked = false;
|
||||
damageToPW += ComputerUtilCombat.predictDamageTo(pw, pwAtk.getNetCombatDamage(), pwAtk, true);
|
||||
damageToPW += ComputerUtilCombat.predictDamageTo((Card) pw, pwAtk.getNetCombatDamage(), pwAtk, true);
|
||||
}
|
||||
}
|
||||
if (!isFullyBlocked && damageToPW >= pw.getCounters(CounterEnumType.LOYALTY)) {
|
||||
if (!isFullyBlocked && damageToPW >= pw.getCounters(CounterType.LOYALTY)) {
|
||||
for (Card chump : pwDefenders) {
|
||||
if (chosenChumpBlockers.contains(chump)) {
|
||||
combat.removeFromCombat(chump);
|
||||
@@ -1291,7 +1289,6 @@ public class AiBlockController {
|
||||
oppCreatureCount = ComputerUtil.countUsefulCreatures(attackersLeft.get(0).getController());
|
||||
}
|
||||
|
||||
if (attacker != null && attacker.getOwner() != null)
|
||||
if (attacker.getOwner().equals(ai) && "6".equals(attacker.getSVar("SacMe"))) {
|
||||
// Temporarily controlled object - don't trade with it
|
||||
// TODO: find a more reliable way to figure out that control will be reestablished next turn
|
||||
@@ -1332,9 +1329,13 @@ public class AiBlockController {
|
||||
&& ((Card) combat.getDefenderByAttacker(attacker)).isPlaneswalker();
|
||||
boolean wantToTradeDownToSavePW = chanceToTradeDownToSaveWalker > 0;
|
||||
|
||||
return ((evalBlk <= evalAtk + 1) || (wantToSavePlaneswalker && wantToTradeDownToSavePW)) // "1" accounts for tapped.
|
||||
if (((evalBlk <= evalAtk + 1) || (wantToSavePlaneswalker && wantToTradeDownToSavePW)) // "1" accounts for tapped.
|
||||
&& powerParityOrHigher
|
||||
&& (creatureParityOrAllowedDiff || wantToTradeWithCreatInHand)
|
||||
&& (MyRandom.percentTrue(chance) || wantToSavePlaneswalker);
|
||||
&& (MyRandom.percentTrue(chance) || wantToSavePlaneswalker)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
|
||||
package forge.ai;
|
||||
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AiCardMemory class.
|
||||
@@ -137,7 +137,7 @@ public class AiCardMemory {
|
||||
|
||||
Set<Card> memorySet = getMemorySet(set);
|
||||
|
||||
return memorySet != null && memorySet.contains(c);
|
||||
return memorySet == null ? false : memorySet.contains(c);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,8 +150,6 @@ public class AiCardMemory {
|
||||
*/
|
||||
public boolean isRememberedCardByName(String cardName, MemorySet set) {
|
||||
Set<Card> memorySet = getMemorySet(set);
|
||||
|
||||
if (memorySet != null) {
|
||||
Iterator<Card> it = memorySet.iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
@@ -160,7 +158,6 @@ public class AiCardMemory {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -177,8 +174,6 @@ public class AiCardMemory {
|
||||
*/
|
||||
public boolean isRememberedCardByName(String cardName, MemorySet set, Player owner) {
|
||||
Set<Card> memorySet = getMemorySet(set);
|
||||
|
||||
if (memorySet != null) {
|
||||
Iterator<Card> it = memorySet.iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
@@ -187,7 +182,6 @@ public class AiCardMemory {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -203,12 +197,7 @@ public class AiCardMemory {
|
||||
if (c == null)
|
||||
return false;
|
||||
|
||||
Set<Card> memorySet = getMemorySet(set);
|
||||
|
||||
if (memorySet != null) {
|
||||
memorySet.add(c);
|
||||
}
|
||||
|
||||
getMemorySet(set).add(c);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -227,12 +216,7 @@ public class AiCardMemory {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<Card> memorySet = getMemorySet(set);
|
||||
|
||||
if (memorySet != null) {
|
||||
memorySet.remove(c);
|
||||
}
|
||||
|
||||
getMemorySet(set).remove(c);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -245,8 +229,6 @@ public class AiCardMemory {
|
||||
*/
|
||||
public boolean forgetAnyCardWithName(String cardName, MemorySet set) {
|
||||
Set<Card> memorySet = getMemorySet(set);
|
||||
|
||||
if (memorySet != null) {
|
||||
Iterator<Card> it = memorySet.iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
@@ -255,7 +237,6 @@ public class AiCardMemory {
|
||||
return forgetCard(c, set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -270,8 +251,6 @@ public class AiCardMemory {
|
||||
*/
|
||||
public boolean forgetAnyCardWithName(String cardName, MemorySet set, Player owner) {
|
||||
Set<Card> memorySet = getMemorySet(set);
|
||||
|
||||
if (memorySet != null) {
|
||||
Iterator<Card> it = memorySet.iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
@@ -280,7 +259,6 @@ public class AiCardMemory {
|
||||
return forgetCard(c, set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -291,17 +269,15 @@ public class AiCardMemory {
|
||||
* @return true, if the given memory set contains no remembered cards.
|
||||
*/
|
||||
public boolean isMemorySetEmpty(MemorySet set) {
|
||||
return set == null || getMemorySet(set).isEmpty();
|
||||
return getMemorySet(set).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the given memory set.
|
||||
*/
|
||||
public void clearMemorySet(MemorySet set) {
|
||||
if (set != null) {
|
||||
getMemorySet(set).clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all memory sets stored in this card memory for the given player.
|
||||
|
||||
@@ -17,19 +17,13 @@
|
||||
*/
|
||||
package forge.ai;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.esotericsoftware.minlog.Log;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.ai.ability.ChangeZoneAi;
|
||||
import forge.ai.ability.ExploreAi;
|
||||
import forge.ai.simulation.SpellAbilityPicker;
|
||||
@@ -38,38 +32,17 @@ import forge.card.mana.ManaCost;
|
||||
import forge.deck.CardPool;
|
||||
import forge.deck.Deck;
|
||||
import forge.deck.DeckSection;
|
||||
import forge.game.CardTraitBase;
|
||||
import forge.game.CardTraitPredicates;
|
||||
import forge.game.Direction;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.*;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.SpellApiBased;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPlayOption;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Accessors;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostAdjustment;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostPayEnergy;
|
||||
import forge.game.cost.CostPayLife;
|
||||
import forge.game.cost.CostPutCounter;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.cost.CostSacrifice;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -77,16 +50,7 @@ import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.replacement.ReplaceMoved;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.LandAbility;
|
||||
import forge.game.spellability.OptionalCost;
|
||||
import forge.game.spellability.OptionalCostValue;
|
||||
import forge.game.spellability.Spell;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityCondition;
|
||||
import forge.game.spellability.SpellAbilityPredicates;
|
||||
import forge.game.spellability.SpellPermanent;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -94,13 +58,16 @@ import forge.game.trigger.WrappedAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.PaperCard;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.ComparatorUtil;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import io.sentry.Sentry;
|
||||
import io.sentry.event.BreadcrumbBuilder;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AiController class.
|
||||
@@ -114,7 +81,6 @@ public class AiController {
|
||||
private final Game game;
|
||||
private final AiCardMemory memory;
|
||||
private Combat predictedCombat;
|
||||
private Combat predictedCombatNextTurn;
|
||||
private boolean cheatShuffle;
|
||||
private boolean useSimulation;
|
||||
private SpellAbilityPicker simPicker;
|
||||
@@ -159,15 +125,6 @@ public class AiController {
|
||||
return predictedCombat;
|
||||
}
|
||||
|
||||
public Combat getPredictedCombatNextTurn() {
|
||||
if (predictedCombatNextTurn == null) {
|
||||
AiAttackController aiAtk = new AiAttackController(player, true);
|
||||
predictedCombatNextTurn = new Combat(player);
|
||||
aiAtk.declareAttackers(predictedCombatNextTurn);
|
||||
}
|
||||
return predictedCombatNextTurn;
|
||||
}
|
||||
|
||||
public AiController(final Player computerPlayer, final Game game0) {
|
||||
player = computerPlayer;
|
||||
game = game0;
|
||||
@@ -220,7 +177,7 @@ public class AiController {
|
||||
&& CardFactoryUtil.isCounterable(host)) {
|
||||
return true;
|
||||
} else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)
|
||||
&& host.getCMC() == c.getCounters(CounterEnumType.CHARGE)) {
|
||||
&& host.getCMC() == c.getCounters(CounterType.CHARGE)) {
|
||||
return true;
|
||||
} else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) {
|
||||
String hostName = host.getName();
|
||||
@@ -247,22 +204,24 @@ public class AiController {
|
||||
return api == null;
|
||||
}
|
||||
boolean rightapi = false;
|
||||
String battlefield = ZoneType.Battlefield.toString();
|
||||
Player activatingPlayer = sa.getActivatingPlayer();
|
||||
|
||||
// Trigger play improvements
|
||||
for (final Trigger tr : card.getTriggers()) {
|
||||
// These triggers all care for ETB effects
|
||||
|
||||
final Map<String, String> params = tr.getMapParams();
|
||||
if (tr.getMode() != TriggerType.ChangesZone) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ZoneType.Battlefield.toString().equals(tr.getParam("Destination"))) {
|
||||
if (!params.get("Destination").equals(battlefield)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tr.hasParam("ValidCard")) {
|
||||
String validCard = tr.getParam("ValidCard");
|
||||
if (params.containsKey("ValidCard")) {
|
||||
String validCard = params.get("ValidCard");
|
||||
if (!validCard.contains("Self")) {
|
||||
continue;
|
||||
}
|
||||
@@ -286,11 +245,21 @@ public class AiController {
|
||||
}
|
||||
|
||||
// if trigger is not mandatory - no problem
|
||||
if (tr.hasParam("OptionalDecider") && api == null) {
|
||||
if (params.get("OptionalDecider") != null && api == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SpellAbility exSA = tr.ensureAbility().copy(activatingPlayer);
|
||||
SpellAbility exSA = tr.getOverridingAbility();
|
||||
|
||||
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 (exSA.getApi() != api) {
|
||||
@@ -304,7 +273,13 @@ public class AiController {
|
||||
}
|
||||
}
|
||||
|
||||
exSA.setTrigger(tr);
|
||||
if (sa != null) {
|
||||
exSA.setActivatingPlayer(activatingPlayer);
|
||||
}
|
||||
else {
|
||||
exSA.setActivatingPlayer(player);
|
||||
}
|
||||
exSA.setTrigger(true);
|
||||
|
||||
// for trigger test, need to ignore the conditions
|
||||
SpellAbilityCondition cons = exSA.getConditions();
|
||||
@@ -329,16 +304,18 @@ public class AiController {
|
||||
// Replacement effects
|
||||
for (final ReplacementEffect re : card.getReplacementEffects()) {
|
||||
// These Replacements all care for ETB effects
|
||||
|
||||
final Map<String, String> params = re.getMapParams();
|
||||
if (!(re instanceof ReplaceMoved)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ZoneType.Battlefield.toString().equals(re.getParam("Destination"))) {
|
||||
if (!params.get("Destination").equals(battlefield)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (re.hasParam("ValidCard")) {
|
||||
String validCard = re.getParam("ValidCard");
|
||||
if (params.containsKey("ValidCard")) {
|
||||
String validCard = params.get("ValidCard");
|
||||
if (!validCard.contains("Self")) {
|
||||
continue;
|
||||
}
|
||||
@@ -360,18 +337,27 @@ public class AiController {
|
||||
if (!re.requirementsCheck(game)) {
|
||||
continue;
|
||||
}
|
||||
SpellAbility exSA = re.getOverridingAbility();
|
||||
final SpellAbility exSA = re.getOverridingAbility();
|
||||
|
||||
if (exSA != null) {
|
||||
exSA = exSA.copy(activatingPlayer);
|
||||
if (sa != null) {
|
||||
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.
|
||||
// These checks only work if the Executing SpellAbility is an Ability_Sub.
|
||||
if ((exSA instanceof AbilitySub) && !doTrigger(exSA, false)) {
|
||||
if (exSA != null && (exSA instanceof AbilitySub) && !doTrigger(exSA, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -420,9 +406,7 @@ public class AiController {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
CardCollectionView battlefield = player.getCardsIn(ZoneType.Battlefield);
|
||||
if (canPlaySpellBasic(c, null) != AiPlayDecision.WillPlay) {
|
||||
return false;
|
||||
}
|
||||
canPlaySpellBasic(c, null);
|
||||
String name = c.getName();
|
||||
if (c.getType().isLegendary() && !name.equals("Flagstones of Trokair")) {
|
||||
if (Iterables.any(battlefield, CardPredicates.nameEquals(name))) {
|
||||
@@ -476,7 +460,8 @@ public class AiController {
|
||||
byte color = MagicColor.fromName(c);
|
||||
for (Card land : landList) {
|
||||
for (final SpellAbility m : ComputerUtilMana.getAIPlayableMana(land)) {
|
||||
if (m.canProduce(MagicColor.toShortString(color))) {
|
||||
AbilityManaPart mp = m.getManaPart();
|
||||
if (mp.canProduce(MagicColor.toShortString(color), m)) {
|
||||
return land;
|
||||
}
|
||||
}
|
||||
@@ -529,7 +514,8 @@ public class AiController {
|
||||
return land;
|
||||
}
|
||||
for (final SpellAbility m : ComputerUtilMana.getAIPlayableMana(land)) {
|
||||
if (m.canProduce(MagicColor.toShortString(color))) {
|
||||
AbilityManaPart mp = m.getManaPart();
|
||||
if (mp.canProduce(MagicColor.toShortString(color), m)) {
|
||||
return land;
|
||||
}
|
||||
}
|
||||
@@ -624,15 +610,7 @@ public class AiController {
|
||||
ComputerUtilAbility.getAvailableCards(game, player);
|
||||
|
||||
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player);
|
||||
|
||||
try {
|
||||
Collections.sort(all, saComparator); // put best spells first
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
System.err.println(ex.getMessage());
|
||||
String assertex = ComparatorUtil.verifyTransitivity(saComparator, all);
|
||||
Sentry.capture(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
|
||||
}
|
||||
|
||||
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
|
||||
ApiType saApi = sa.getApi();
|
||||
@@ -679,10 +657,10 @@ public class AiController {
|
||||
return false;
|
||||
}
|
||||
|
||||
AiCardMemory.MemorySet memSet = null;
|
||||
AiCardMemory.MemorySet memSet;
|
||||
if (phaseType == null && forNextSpell) {
|
||||
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL;
|
||||
} else if (phaseType != null) {
|
||||
} else {
|
||||
switch (phaseType) {
|
||||
case MAIN2:
|
||||
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2;
|
||||
@@ -742,8 +720,6 @@ public class AiController {
|
||||
|
||||
public AiPlayDecision canPlaySa(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final boolean isRightTiming = sa.canCastTiming(player);
|
||||
|
||||
if (!checkAiSpecificRestrictions(sa)) {
|
||||
return AiPlayDecision.CantPlayAi;
|
||||
}
|
||||
@@ -785,18 +761,17 @@ public class AiController {
|
||||
return AiPlayDecision.CantPlayAi;
|
||||
}
|
||||
}
|
||||
else {
|
||||
else if (sa.getPayCosts() != null){
|
||||
Cost payCosts = sa.getPayCosts();
|
||||
if(payCosts != null) {
|
||||
ManaCost mana = payCosts.getTotalMana();
|
||||
if (mana != null) {
|
||||
if(mana.countX() > 0) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilCost.getMaxXValue(sa, player);
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, player);
|
||||
if (xPay <= 0) {
|
||||
return AiPlayDecision.CantAffordX;
|
||||
}
|
||||
sa.setXManaCostPaid(xPay);
|
||||
card.setSVar("PayX", Integer.toString(xPay));
|
||||
} else if (mana.isZero()) {
|
||||
// if mana is zero, but card mana cost does have X, then something is wrong
|
||||
ManaCost cardCost = card.getManaCost();
|
||||
@@ -806,24 +781,10 @@ public class AiController {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (checkCurseEffects(sa)) {
|
||||
return AiPlayDecision.CurseEffects;
|
||||
}
|
||||
Card spellHost = card;
|
||||
if (sa.isSpell()) {
|
||||
spellHost = CardUtil.getLKICopy(spellHost);
|
||||
spellHost.setLKICMC(-1); // to reset the cmc
|
||||
spellHost.setLastKnownZone(game.getStackZone()); // need to add to stack to make check Restrictions respect stack cmc
|
||||
spellHost.setCastFrom(card.getZone().getZoneType());
|
||||
}
|
||||
if (!sa.checkRestrictions(spellHost, player)) {
|
||||
return AiPlayDecision.AnotherTime;
|
||||
}
|
||||
if (sa instanceof SpellPermanent) {
|
||||
if (!isRightTiming) {
|
||||
return AiPlayDecision.AnotherTime;
|
||||
}
|
||||
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
|
||||
}
|
||||
if (sa.usesTargeting() && !sa.isTargetNumberValid()) {
|
||||
@@ -836,14 +797,8 @@ public class AiController {
|
||||
&& !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) {
|
||||
return AiPlayDecision.CurseEffects;
|
||||
}
|
||||
if (!isRightTiming) {
|
||||
return AiPlayDecision.AnotherTime;
|
||||
}
|
||||
return canPlaySpellBasic(card, sa);
|
||||
}
|
||||
if (!isRightTiming) {
|
||||
return AiPlayDecision.AnotherTime;
|
||||
}
|
||||
return AiPlayDecision.WillPlay;
|
||||
}
|
||||
|
||||
@@ -895,7 +850,7 @@ public class AiController {
|
||||
int neededMana = 0;
|
||||
boolean dangerousRecurringCost = false;
|
||||
|
||||
Cost costWithBuyback = sa.getPayCosts().copy();
|
||||
Cost costWithBuyback = sa.getPayCosts() != null ? sa.getPayCosts().copy() : Cost.Zero;
|
||||
for (OptionalCostValue opt : GameActionUtil.getOptionalCostValues(sa)) {
|
||||
if (opt.getType() == OptionalCost.Buyback) {
|
||||
costWithBuyback.add(opt.getCost());
|
||||
@@ -944,8 +899,8 @@ public class AiController {
|
||||
public int compare(final SpellAbility a, final SpellAbility b) {
|
||||
// sort from highest cost to lowest
|
||||
// we want the highest costs first
|
||||
int a1 = a.getPayCosts().getTotalMana().getCMC();
|
||||
int b1 = b.getPayCosts().getTotalMana().getCMC();
|
||||
int a1 = a.getPayCosts() == null ? 0 : a.getPayCosts().getTotalMana().getCMC();
|
||||
int b1 = b.getPayCosts() == null ? 0 : b.getPayCosts().getTotalMana().getCMC();
|
||||
|
||||
// deprioritize SAs explicitly marked as preferred to be activated last compared to all other SAs
|
||||
if (a.hasParam("AIActivateLast") && !b.hasParam("AIActivateLast")) {
|
||||
@@ -964,12 +919,12 @@ public class AiController {
|
||||
// deprioritize pump spells with pure energy cost (can be activated last,
|
||||
// since energy is generally scarce, plus can benefit e.g. Electrostatic Pummeler)
|
||||
int a2 = 0, b2 = 0;
|
||||
if (a.getApi() == ApiType.Pump && a.getPayCosts().getCostEnergy() != null) {
|
||||
if (a.getApi() == ApiType.Pump && a.getPayCosts() != null && a.getPayCosts().getCostEnergy() != null) {
|
||||
if (a.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||
a2 = a.getPayCosts().getCostEnergy().convertAmount();
|
||||
}
|
||||
}
|
||||
if (b.getApi() == ApiType.Pump && b.getPayCosts().getCostEnergy() != null) {
|
||||
if (b.getApi() == ApiType.Pump && b.getPayCosts() != null && b.getPayCosts().getCostEnergy() != null) {
|
||||
if (b.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||
b2 = b.getPayCosts().getCostEnergy().convertAmount();
|
||||
}
|
||||
@@ -993,7 +948,8 @@ public class AiController {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a.getHostCard().equals(b.getHostCard()) && a.getApi() == b.getApi()) {
|
||||
if (a.getHostCard().equals(b.getHostCard()) && a.getApi() == b.getApi()
|
||||
&& a.getPayCosts() != null && b.getPayCosts() != null) {
|
||||
// Cheaper Spectacle costs should be preferred
|
||||
// FIXME: Any better way to identify that these are the same ability, one with Spectacle and one not?
|
||||
// (looks like it's not a full-fledged alternative cost as such, and is not processed with other alt costs)
|
||||
@@ -1034,10 +990,6 @@ public class AiController {
|
||||
if (source.isEquipment() && noCreatures) {
|
||||
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
|
||||
// 2. non-surged versions are usually inefficient
|
||||
if (source.getOracleText().contains("surge cost") && !sa.isSurged()) {
|
||||
@@ -1060,7 +1012,7 @@ public class AiController {
|
||||
p += 9;
|
||||
}
|
||||
// sort planeswalker abilities with most costly first
|
||||
if (sa.isPwAbility()) {
|
||||
if (sa.getRestrictions().isPwAbility()) {
|
||||
final CostPart cost = sa.getPayCosts().getCostParts().get(0);
|
||||
if (cost instanceof CostRemoveCounter) {
|
||||
p += cost.convertAmount() == null ? 1 : cost.convertAmount();
|
||||
@@ -1092,10 +1044,9 @@ public class AiController {
|
||||
}
|
||||
|
||||
public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa, final CardCollectionView exclude) {
|
||||
boolean noFiltering = (sa != null) && "DiscardCMCX".equals(sa.getParam("AILogic")); // list AI logic for which filtering is taken care of elsewhere
|
||||
CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
||||
hand.removeAll(exclude);
|
||||
if ((uTypes != null) && (sa != null) && !noFiltering) {
|
||||
if ((uTypes != null) && (sa != null)) {
|
||||
hand = CardLists.getValidCards(hand, uTypes, sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
}
|
||||
return getCardsToDiscard(numDiscard, numDiscard, hand, sa);
|
||||
@@ -1115,13 +1066,6 @@ public class AiController {
|
||||
min = 1;
|
||||
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) {
|
||||
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
||||
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
||||
final int cmc = sa.getXManaCostPaid();
|
||||
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
|
||||
if (discards.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new CardCollection(ComputerUtilCard.getWorstAI(discards));
|
||||
}
|
||||
|
||||
if (sa.hasParam("AnyNumber")) {
|
||||
@@ -1267,7 +1211,7 @@ public class AiController {
|
||||
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
ApiType api = sa.getApi();
|
||||
|
||||
// Abilities without api may also use this routine, However they should provide a unique mode value ?? How could this work?
|
||||
// Abilities without api may also use this routine, However they should provide a unique mode value
|
||||
if (api == null) {
|
||||
String exMsg = String.format("AI confirmAction does not know what to decide about %s mode (api is null).",
|
||||
mode);
|
||||
@@ -1408,8 +1352,6 @@ public class AiController {
|
||||
// Reset cached predicted combat, as it may be stale. It will be
|
||||
// re-created if needed and used for any AI logic that needs it.
|
||||
predictedCombat = null;
|
||||
// Also reset predicted combat for next turn here
|
||||
predictedCombatNextTurn = null;
|
||||
|
||||
// Reset priority mana reservation that's meant to work for one spell only
|
||||
AiCardMemory.clearMemorySet(player, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL);
|
||||
@@ -1444,7 +1386,6 @@ public class AiController {
|
||||
final List<SpellAbility> abilities = Lists.newArrayList();
|
||||
|
||||
LandAbility la = new LandAbility(land, player, null);
|
||||
la.setCardState(land.getCurrentState());
|
||||
if (la.canPlay()) {
|
||||
abilities.add(la);
|
||||
}
|
||||
@@ -1452,7 +1393,6 @@ public class AiController {
|
||||
// add mayPlay option
|
||||
for (CardPlayOption o : land.mayPlay(player)) {
|
||||
la = new LandAbility(land, player, o.getAbility());
|
||||
la.setCardState(land.getCurrentState());
|
||||
if (la.canPlay()) {
|
||||
abilities.add(la);
|
||||
}
|
||||
@@ -1505,11 +1445,9 @@ public class AiController {
|
||||
|
||||
int totalCMCInHand = Aggregates.sum(inHand, CardPredicates.Accessors.fnGetCmc);
|
||||
int minCMCInHand = Aggregates.min(inHand, CardPredicates.Accessors.fnGetCmc);
|
||||
if (minCMCInHand == Integer.MAX_VALUE)
|
||||
minCMCInHand = 0;
|
||||
int predictedMana = ComputerUtilMana.getAvailableManaEstimate(player, true);
|
||||
|
||||
boolean canCastWithLandDrop = (predictedMana + 1 >= minCMCInHand) && minCMCInHand > 0 && !isTapLand;
|
||||
boolean canCastWithLandDrop = (predictedMana + 1 >= minCMCInHand) && !isTapLand;
|
||||
boolean cantCastAnythingNow = predictedMana < minCMCInHand;
|
||||
|
||||
boolean hasRelevantAbsOTB = !CardLists.filter(otb, new Predicate<Card>() {
|
||||
@@ -1524,7 +1462,7 @@ public class AiController {
|
||||
}
|
||||
|
||||
for (SpellAbility sa : card.getSpellAbilities()) {
|
||||
if (sa.isAbility()
|
||||
if (sa.getPayCosts() != null && sa.isAbility()
|
||||
&& sa.getPayCosts().getCostMana() != null
|
||||
&& sa.getPayCosts().getCostMana().getMana().getCMC() > 0
|
||||
&& (!sa.getPayCosts().hasTapCost() || !isTapLand)
|
||||
@@ -1589,11 +1527,11 @@ public class AiController {
|
||||
top = game.getStack().peekAbility();
|
||||
}
|
||||
final boolean topOwnedByAI = top != null && top.getActivatingPlayer().equals(player);
|
||||
final boolean mustRespond = top != null && top.hasParam("AIRespondsToOwnAbility");
|
||||
|
||||
if (topOwnedByAI) {
|
||||
// AI's own spell: should probably let my stuff resolve first, but may want to copy the SA or respond to it
|
||||
// in a scripted timed fashion.
|
||||
final boolean mustRespond = top.hasParam("AIRespondsToOwnAbility");
|
||||
|
||||
if (!mustRespond) {
|
||||
saList = ComputerUtilAbility.getSpellAbilities(cards, player); // get the SA list early to check for copy SAs
|
||||
@@ -1617,19 +1555,8 @@ public class AiController {
|
||||
saList = ComputerUtilAbility.getSpellAbilities(cards, player);
|
||||
}
|
||||
|
||||
Iterables.removeIf(saList, new Predicate<SpellAbility>() {
|
||||
@Override
|
||||
public boolean apply(final SpellAbility spellAbility) {
|
||||
return spellAbility instanceof LandAbility;
|
||||
}
|
||||
});
|
||||
|
||||
SpellAbility chosenSa = chooseSpellAbilityToPlayFromList(saList, true);
|
||||
|
||||
if (topOwnedByAI && !mustRespond && chosenSa != ComputerUtilAbility.getFirstCopySASpell(saList)) {
|
||||
return null; // not planning to copy the spell and not marked as something the AI would respond to
|
||||
}
|
||||
|
||||
return chosenSa;
|
||||
}
|
||||
|
||||
@@ -1637,14 +1564,7 @@ public class AiController {
|
||||
if (all == null || all.isEmpty())
|
||||
return null;
|
||||
|
||||
try {
|
||||
Collections.sort(all, saComparator); // put best spells first
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
System.err.println(ex.getMessage());
|
||||
String assertex = ComparatorUtil.verifyTransitivity(saComparator, all);
|
||||
Sentry.capture(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
|
||||
}
|
||||
|
||||
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
|
||||
// Don't add Counterspells to the "normal" playcard lookups
|
||||
@@ -1662,18 +1582,10 @@ public class AiController {
|
||||
}
|
||||
|
||||
sa.setActivatingPlayer(player);
|
||||
SpellAbility root = sa.getRootAbility();
|
||||
|
||||
if (root.isSpell() || root.isTrigger() || root.isReplacementAbility()) {
|
||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||
}
|
||||
|
||||
AiPlayDecision opinion = canPlayAndPayFor(sa);
|
||||
|
||||
// reset LastStateBattlefield
|
||||
sa.setLastStateBattlefield(CardCollection.EMPTY);
|
||||
sa.setLastStateGraveyard(CardCollection.EMPTY);
|
||||
// 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());
|
||||
|
||||
@@ -1726,6 +1638,7 @@ public class AiController {
|
||||
// For non-converted triggers (such as Cumulative Upkeep) that don't have costs or targets to worry about
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1741,14 +1654,14 @@ public class AiController {
|
||||
hostCard = game.getCardState(hostCard);
|
||||
}
|
||||
|
||||
if (effect.hasParam("AICheckSVar")) {
|
||||
if (effect.getMapParams().containsKey("AICheckSVar")) {
|
||||
System.out.println("aiShouldRun?" + sa);
|
||||
final String svarToCheck = effect.getParam("AICheckSVar");
|
||||
final String svarToCheck = effect.getMapParams().get("AICheckSVar");
|
||||
String comparator = "GE";
|
||||
int compareTo = 1;
|
||||
|
||||
if (effect.hasParam("AISVarCompare")) {
|
||||
final String fullCmp = effect.getParam("AISVarCompare");
|
||||
if (effect.getMapParams().containsKey("AISVarCompare")) {
|
||||
final String fullCmp = effect.getMapParams().get("AISVarCompare");
|
||||
comparator = fullCmp.substring(0, 2);
|
||||
final String strCmpTo = fullCmp.substring(2);
|
||||
try {
|
||||
@@ -1770,11 +1683,16 @@ public class AiController {
|
||||
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
||||
}
|
||||
System.out.println("aiShouldRun?" + left + comparator + compareTo);
|
||||
return Expressions.compare(left, comparator, compareTo);
|
||||
} else if (effect.hasParam("AICheckDredge")) {
|
||||
if (Expressions.compare(left, comparator, compareTo)) {
|
||||
return true;
|
||||
}
|
||||
} else if (effect.getMapParams().containsKey("AICheckDredge")) {
|
||||
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
|
||||
} else return sa != null && doTrigger(sa, false);
|
||||
} else if (sa != null && doTrigger(sa, false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand) {
|
||||
@@ -1853,9 +1771,7 @@ public class AiController {
|
||||
+ MyRandom.getRandom().nextInt(3);
|
||||
return Math.max(remaining, min) / 2;
|
||||
} else if ("LowestLoseLife".equals(logic)) {
|
||||
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
||||
} else if ("HighestLoseLife".equals(logic)) {
|
||||
return MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
||||
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, ComputerUtil.getOpponentFor(player).getLife())) + 1;
|
||||
} else if ("HighestGetCounter".equals(logic)) {
|
||||
return MyRandom.getRandom().nextInt(3);
|
||||
} else if (source.hasSVar("EnergyToPay")) {
|
||||
@@ -1868,14 +1784,80 @@ public class AiController {
|
||||
throw new UnsupportedOperationException("AI is not supposed to reach this code at the moment");
|
||||
}
|
||||
|
||||
public CardCollection chooseCardsForEffect(CardCollectionView pool, SpellAbility sa, int min, int max, boolean isOptional, Map<String, Object> params) {
|
||||
public Map<GameEntity, CounterType> chooseProliferation(final SpellAbility sa) {
|
||||
final Map<GameEntity, CounterType> result = Maps.newHashMap();
|
||||
|
||||
final List<Player> allies = player.getAllies();
|
||||
allies.add(player);
|
||||
final List<Player> enemies = player.getOpponents();
|
||||
final Function<Card, CounterType> predProliferate = new Function<Card, CounterType>() {
|
||||
@Override
|
||||
public CounterType apply(Card crd) {
|
||||
//fast way out, no need to check other stuff
|
||||
if (!crd.hasCounters()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// cards controlled by ai or ally with Vanishing or Fading
|
||||
// and exaclty one counter of the specifice type gets high priority to keep the card
|
||||
if (allies.contains(crd.getController())) {
|
||||
// except if its a Chronozoa, because it WANTS to be removed to make more
|
||||
if (crd.hasKeyword(Keyword.VANISHING) && !"Chronozoa".equals(crd.getName())) {
|
||||
if (crd.getCounters(CounterType.TIME) == 1) {
|
||||
return CounterType.TIME;
|
||||
}
|
||||
} else if (crd.hasKeyword(Keyword.FADING)) {
|
||||
if (crd.getCounters(CounterType.FADE) == 1) {
|
||||
return CounterType.FADE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final Entry<CounterType, Integer> c1 : crd.getCounters().entrySet()) {
|
||||
// if card can not recive the given counter, try another one
|
||||
if (!crd.canReceiveCounters(c1.getKey())) {
|
||||
continue;
|
||||
}
|
||||
if (ComputerUtil.isNegativeCounter(c1.getKey(), crd) && enemies.contains(crd.getController())) {
|
||||
return c1.getKey();
|
||||
}
|
||||
if (!ComputerUtil.isNegativeCounter(c1.getKey(), crd) && allies.contains(crd.getController())) {
|
||||
return c1.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||
CounterType ct = predProliferate.apply(c);
|
||||
if (ct != null)
|
||||
result.put(c, ct);
|
||||
}
|
||||
|
||||
for (Player e : enemies) {
|
||||
// TODO In the future check of enemies can get poison counters and give them some other bad counter type
|
||||
if (e.getCounters(CounterType.POISON) > 0) {
|
||||
result.put(e, CounterType.POISON);
|
||||
}
|
||||
}
|
||||
|
||||
for (Player pl : allies) {
|
||||
if (pl.getCounters(CounterType.EXPERIENCE) > 0) {
|
||||
result.put(pl, CounterType.EXPERIENCE);
|
||||
} else if (pl.getCounters(CounterType.ENERGY) > 0) {
|
||||
result.put(pl, CounterType.ENERGY);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public CardCollection chooseCardsForEffect(CardCollectionView pool, SpellAbility sa, int min, int max, boolean isOptional) {
|
||||
if (sa == null || sa.getApi() == null) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
CardCollection result = new CardCollection();
|
||||
if (sa.hasParam("AIMaxAmount")) {
|
||||
max = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("AIMaxAmount"), sa);
|
||||
}
|
||||
switch(sa.getApi()) {
|
||||
case TwoPiles:
|
||||
// TODO: improve AI
|
||||
@@ -1904,7 +1886,7 @@ public class AiController {
|
||||
default:
|
||||
CardCollection editablePool = new CardCollection(pool);
|
||||
for (int i = 0; i < max; i++) {
|
||||
Card c = player.getController().chooseSingleEntityForEffect(editablePool, sa, null, isOptional, params);
|
||||
Card c = player.getController().chooseSingleEntityForEffect(editablePool, sa, null, isOptional);
|
||||
if (c != null) {
|
||||
result.add(c);
|
||||
editablePool.remove(c);
|
||||
@@ -1915,7 +1897,7 @@ public class AiController {
|
||||
// Special case for Bow to My Command which simulates a complex tap cost via ChooseCard
|
||||
// TODO: consider enhancing support for tapXType<Any/...> in UnlessCost to get rid of this hack
|
||||
if ("BowToMyCommand".equals(sa.getParam("AILogic"))) {
|
||||
if (!sa.getHostCard().isInZone(ZoneType.Command)) {
|
||||
if (!sa.getHostCard().getZone().is(ZoneType.Command)) {
|
||||
// Make sure that other opponents do not tap for an already abandoned scheme
|
||||
result.clear();
|
||||
break;
|
||||
@@ -2055,35 +2037,6 @@ public class AiController {
|
||||
return MyRandom.getRandom().nextBoolean();
|
||||
}
|
||||
|
||||
public boolean chooseEvenOdd(SpellAbility sa) {
|
||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (aiLogic.equals("AlwaysEven")) {
|
||||
return false; // false is Even
|
||||
} else if (aiLogic.equals("AlwaysOdd")) {
|
||||
return true; // true is Odd
|
||||
} else if (aiLogic.equals("Random")) {
|
||||
return MyRandom.getRandom().nextBoolean();
|
||||
} else if (aiLogic.equals("CMCInHand")) {
|
||||
CardCollectionView hand = sa.getActivatingPlayer().getCardsIn(ZoneType.Hand);
|
||||
int numEven = CardLists.filter(hand, CardPredicates.evenCMC()).size();
|
||||
int numOdd = CardLists.filter(hand, CardPredicates.oddCMC()).size();
|
||||
return numOdd > numEven;
|
||||
} else if (aiLogic.equals("CMCOppControls")) {
|
||||
CardCollectionView hand = sa.getActivatingPlayer().getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
int numEven = CardLists.filter(hand, CardPredicates.evenCMC()).size();
|
||||
int numOdd = CardLists.filter(hand, CardPredicates.oddCMC()).size();
|
||||
return numOdd > numEven;
|
||||
} else if (aiLogic.equals("CMCOppControlsByPower")) {
|
||||
// TODO: improve this to check for how dangerous those creatures actually are relative to host card
|
||||
CardCollectionView hand = sa.getActivatingPlayer().getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
int powerEven = Aggregates.sum(CardLists.filter(hand, CardPredicates.evenCMC()), Accessors.fnGetNetPower);
|
||||
int powerOdd = Aggregates.sum(CardLists.filter(hand, CardPredicates.oddCMC()), Accessors.fnGetNetPower);
|
||||
return powerOdd > powerEven;
|
||||
}
|
||||
return MyRandom.getRandom().nextBoolean(); // outside of any specific logic, choose randomly
|
||||
}
|
||||
|
||||
public Card chooseCardToHiddenOriginChangeZone(ZoneType destination, List<ZoneType> origin, SpellAbility sa,
|
||||
CardCollection fetchList, Player player2, Player decider) {
|
||||
if (useSimulation) {
|
||||
@@ -2169,9 +2122,9 @@ public class AiController {
|
||||
return filterList(list, CardTraitPredicates.hasParam("AiLogic", logic));
|
||||
}
|
||||
|
||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> possible, int min, int num, boolean allowRepeat) {
|
||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat) {
|
||||
if (simPicker != null) {
|
||||
return simPicker.chooseModeForAbility(sa, possible, min, num, allowRepeat);
|
||||
return simPicker.chooseModeForAbility(sa, min, num, allowRepeat);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -2187,22 +2140,24 @@ public class AiController {
|
||||
// AI-specific restrictions specified as activation parameters in spell abilities
|
||||
|
||||
if (sa.hasParam("AILifeThreshold")) {
|
||||
return player.getLife() > Integer.parseInt(sa.getParam("AILifeThreshold"));
|
||||
if (player.getLife() <= Integer.parseInt(sa.getParam("AILifeThreshold"))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ReplacementEffect chooseSingleReplacementEffect(List<ReplacementEffect> list) {
|
||||
public ReplacementEffect chooseSingleReplacementEffect(List<ReplacementEffect> list,
|
||||
Map<String, Object> runParams) {
|
||||
// no need to choose anything
|
||||
if (list.size() <= 1) {
|
||||
return Iterables.getFirst(list, null);
|
||||
}
|
||||
|
||||
ReplacementType mode = Iterables.getFirst(list, null).getMode();
|
||||
|
||||
if (runParams.containsKey("Event")) {
|
||||
// replace lifegain effects
|
||||
if (mode.equals(ReplacementType.GainLife)) {
|
||||
if ("GainLife".equals(runParams.get("Event"))) {
|
||||
List<ReplacementEffect> noGain = filterListByAiLogic(list, "NoLife");
|
||||
List<ReplacementEffect> loseLife = filterListByAiLogic(list, "LoseLife");
|
||||
List<ReplacementEffect> doubleLife = filterListByAiLogic(list, "DoubleLife");
|
||||
@@ -2221,7 +2176,7 @@ public class AiController {
|
||||
// other than that, do double life
|
||||
return Iterables.getFirst(doubleLife, null);
|
||||
}
|
||||
} else if (mode.equals(ReplacementType.DamageDone)) {
|
||||
} else if ("DamageDone".equals(runParams.get("Event"))) {
|
||||
List<ReplacementEffect> prevention = filterList(list, CardTraitPredicates.hasParam("Prevention"));
|
||||
|
||||
// TODO when Protection is done as ReplacementEffect do them
|
||||
@@ -2230,6 +2185,7 @@ public class AiController {
|
||||
return Iterables.getFirst(prevention, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AI logic for choosing which replacement effect to apply
|
||||
// happens here.
|
||||
|
||||
@@ -1,64 +1,27 @@
|
||||
package forge.ai;
|
||||
|
||||
import static forge.ai.ComputerUtilCard.getBestCreatureAI;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.card.CardType;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.CostAddMana;
|
||||
import forge.game.cost.CostChooseCreatureType;
|
||||
import forge.game.cost.CostDamage;
|
||||
import forge.game.cost.CostDecisionMakerBase;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostDraw;
|
||||
import forge.game.cost.CostExert;
|
||||
import forge.game.cost.CostExile;
|
||||
import forge.game.cost.CostExileFromStack;
|
||||
import forge.game.cost.CostExiledMoveToGrave;
|
||||
import forge.game.cost.CostFlipCoin;
|
||||
import forge.game.cost.CostGainControl;
|
||||
import forge.game.cost.CostGainLife;
|
||||
import forge.game.cost.CostMill;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.cost.CostPayEnergy;
|
||||
import forge.game.cost.CostPayLife;
|
||||
import forge.game.cost.CostPutCardToLib;
|
||||
import forge.game.cost.CostPutCounter;
|
||||
import forge.game.cost.CostRemoveAnyCounter;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.cost.CostReturn;
|
||||
import forge.game.cost.CostReveal;
|
||||
import forge.game.cost.CostSacrifice;
|
||||
import forge.game.cost.CostTap;
|
||||
import forge.game.cost.CostTapType;
|
||||
import forge.game.cost.CostUnattach;
|
||||
import forge.game.cost.CostUntap;
|
||||
import forge.game.cost.CostUntapType;
|
||||
import forge.game.cost.PaymentDecision;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
@@ -92,15 +55,9 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostChooseCreatureType cost) {
|
||||
Integer amount = cost.convertAmount();
|
||||
Iterable<String> choices = player.getController().chooseSomeType(
|
||||
Localizer.getInstance().getMessage("lblCreature"), ability, amount, amount, Lists.newArrayList(CardType.getAllCreatureTypes()));
|
||||
|
||||
if (choices == null || Iterables.isEmpty(choices)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return PaymentDecision.types(choices);
|
||||
String choice = player.getController().chooseSomeType("Creature", ability, CardType.getAllCreatureTypes(),
|
||||
Lists.<String>newArrayList());
|
||||
return PaymentDecision.type(choice);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,7 +80,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
}
|
||||
else if (type.equals("Hand")) {
|
||||
if (hand.size() > 1 && ability.getActivatingPlayer() != null) {
|
||||
hand = ability.getActivatingPlayer().getController().orderMoveToZoneList(hand, ZoneType.Graveyard, ability);
|
||||
hand = ability.getActivatingPlayer().getController().orderMoveToZoneList(hand, ZoneType.Graveyard);
|
||||
}
|
||||
return PaymentDecision.card(hand);
|
||||
}
|
||||
@@ -133,34 +90,20 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
}
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
if (type.equals("Random")) {
|
||||
CardCollectionView randomSubset = CardLists.getRandomSubList(new CardCollection(hand), c);
|
||||
if (randomSubset.size() > 1 && ability.getActivatingPlayer() != null) {
|
||||
randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability);
|
||||
randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard);
|
||||
}
|
||||
return PaymentDecision.card(randomSubset);
|
||||
}
|
||||
else if (type.equals("DifferentNames")) {
|
||||
CardCollection differentNames = new CardCollection();
|
||||
CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
|
||||
while (c > 0) {
|
||||
Card chosen;
|
||||
if (!discardMe.isEmpty()) {
|
||||
chosen = Aggregates.random(discardMe);
|
||||
discardMe = CardLists.filter(discardMe, Predicates.not(CardPredicates.sharesNameWith(chosen)));
|
||||
} else {
|
||||
final Card worst = ComputerUtilCard.getWorstAI(hand);
|
||||
chosen = worst != null ? worst : Aggregates.random(hand);
|
||||
}
|
||||
differentNames.add(chosen);
|
||||
hand = CardLists.filter(hand, Predicates.not(CardPredicates.sharesNameWith(chosen)));
|
||||
c--;
|
||||
}
|
||||
return PaymentDecision.card(differentNames);
|
||||
}
|
||||
else {
|
||||
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||
|
||||
@@ -177,8 +120,14 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
Integer c = cost.convertAmount();
|
||||
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null; // cannot pay
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
}
|
||||
|
||||
return PaymentDecision.number(c);
|
||||
}
|
||||
@@ -209,6 +158,11 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
@@ -220,7 +174,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
CardCollectionView chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c, ability);
|
||||
CardCollectionView chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c);
|
||||
return null == chosen ? null : PaymentDecision.card(chosen);
|
||||
}
|
||||
}
|
||||
@@ -230,6 +184,11 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
List<SpellAbility> chosen = Lists.newArrayList();
|
||||
@@ -275,6 +234,10 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
if (ability.getSVar(cost.getAmount()).equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
@@ -297,6 +260,11 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
public PaymentDecision visit(CostFlipCoin cost) {
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
return PaymentDecision.number(c);
|
||||
@@ -351,11 +319,17 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
public PaymentDecision visit(CostMill cost) {
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
CardCollectionView topLib = player.getCardsIn(ZoneType.Library, c);
|
||||
return topLib.size() < c ? null : PaymentDecision.number(c);
|
||||
return topLib.size() < c ? null : PaymentDecision.card(topLib);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -367,9 +341,20 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
public PaymentDecision visit(CostPayLife cost) {
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
if (source.getName().equals("Maralen of the Mornsong Avatar")) {
|
||||
return PaymentDecision.number(2);
|
||||
}
|
||||
if (source.getName().equals("Necrologia")) {
|
||||
return PaymentDecision.number(Integer.parseInt(ability.getSVar("ChosenX")));
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
}
|
||||
if (!player.canPayLife(c)) {
|
||||
return null;
|
||||
}
|
||||
@@ -381,8 +366,14 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
public PaymentDecision visit(CostPayEnergy cost) {
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
}
|
||||
if (!player.canPayEnergy(c)) {
|
||||
return null;
|
||||
}
|
||||
@@ -408,6 +399,11 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
}
|
||||
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
@@ -427,7 +423,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
chosen = chosen.subList(0, c);
|
||||
}
|
||||
else {
|
||||
chosen = ComputerUtil.choosePutToLibraryFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c, ability);
|
||||
chosen = ComputerUtil.choosePutToLibraryFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c);
|
||||
}
|
||||
return chosen.isEmpty() ? null : PaymentDecision.card(chosen);
|
||||
}
|
||||
@@ -467,9 +463,25 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
CardCollection exclude = new CardCollection();
|
||||
exclude.addAll(tapped);
|
||||
|
||||
if (c == null && !isVehicle) {
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(amount);
|
||||
if (sVar.equals("XChoice")) {
|
||||
CardCollectionView typeList =
|
||||
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"),
|
||||
ability.getActivatingPlayer(), ability.getHostCard(), ability);
|
||||
typeList = CardLists.filter(typeList, Presets.UNTAPPED);
|
||||
c = typeList.size();
|
||||
// account for the fact that the activated card may be tapped in the process
|
||||
if (ability.getPayCosts().hasTapCost() && typeList.contains(ability.getHostCard())) {
|
||||
c--;
|
||||
}
|
||||
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
|
||||
} else {
|
||||
if (!isVehicle) {
|
||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type.contains("sharesCreatureTypeWith")) {
|
||||
return null;
|
||||
}
|
||||
@@ -482,7 +494,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
for (final SpellAbility sa : card.getSpellAbilities()) {
|
||||
if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
|
||||
if (sa.isManaAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -499,7 +511,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
type = TextUtil.fastReplace(type, "+withTotalPowerGE", "");
|
||||
totap = ComputerUtil.chooseTapTypeAccumulatePower(player, type, ability, !cost.canTapSource, Integer.parseInt(totalP), exclude);
|
||||
} else {
|
||||
totap = ComputerUtil.chooseTapType(player, type, source, !cost.canTapSource, c, exclude, ability);
|
||||
totap = ComputerUtil.chooseTapType(player, type, source, !cost.canTapSource, c, exclude);
|
||||
}
|
||||
|
||||
if (totap == null) {
|
||||
@@ -516,19 +528,34 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
if (cost.payCostFromSource()) {
|
||||
return PaymentDecision.card(source);
|
||||
}
|
||||
if (cost.getType().equals("OriginalHost")) {
|
||||
return PaymentDecision.card(ability.getHostCard());
|
||||
}
|
||||
if (cost.getAmount().equals("All")) {
|
||||
// Does the AI want to use Sacrifice All?
|
||||
return null;
|
||||
}
|
||||
|
||||
final String amount = cost.getAmount();
|
||||
Integer c = cost.convertAmount();
|
||||
|
||||
if (c == null) {
|
||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||
if (ability.getSVar(cost.getAmount()).equals("XChoice")) {
|
||||
String logic = ability.getParamOrDefault("AILogic", "");
|
||||
if ("SacToReduceCost".equals(logic)) {
|
||||
// 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
|
||||
// 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
|
||||
// are currently conventionally flagged with AILogic$ DoSacrifice.
|
||||
c = AbilityUtils.calculateAmount(source, source.getSVar("ChosenX"), null);
|
||||
} else {
|
||||
// Other cards are assumed to be flagged AI:RemoveDeck:All for now
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
}
|
||||
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||
CardCollectionView list = aic.chooseSacrificeType(cost.getType(), ability, c);
|
||||
@@ -545,14 +572,14 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
CardCollectionView res = ComputerUtil.chooseReturnType(player, cost.getType(), source, ability.getTargetCard(), c, ability);
|
||||
CardCollectionView res = ComputerUtil.chooseReturnType(player, cost.getType(), source, ability.getTargetCard(), c);
|
||||
return res.isEmpty() ? null : PaymentDecision.card(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostReveal cost) {
|
||||
final String type = cost.getType();
|
||||
CardCollectionView hand = player.getCardsIn(cost.getRevealFrom());
|
||||
CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
|
||||
|
||||
if (cost.payCostFromSource()) {
|
||||
if (!hand.contains(source)) {
|
||||
@@ -569,176 +596,75 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cost.getRevealFrom().equals(ZoneType.Exile)) {
|
||||
hand = CardLists.getValidCards(hand, type.split(";"), player, source, ability);
|
||||
return PaymentDecision.card(getBestCreatureAI(hand));
|
||||
}
|
||||
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
if (sVar.equals("XChoice")) {
|
||||
c = hand.size();
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
}
|
||||
|
||||
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||
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
|
||||
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
||||
final String amount = cost.getAmount();
|
||||
final int c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||
final Card originalHost = ability.getOriginalOrHost();
|
||||
final String type = cost.getType();
|
||||
|
||||
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());
|
||||
CardCollectionView typeList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source, ability);
|
||||
|
||||
// no target
|
||||
if (typeList.isEmpty()) {
|
||||
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
|
||||
|
||||
// try to remove -1/-1 counter from persist creature
|
||||
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.M1M1))) {
|
||||
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.M1M1), CardPredicates.hasKeyword(Keyword.PERSIST));
|
||||
|
||||
toRemove += removeCounter(table, prefs, CounterEnumType.M1M1, c - toRemove);
|
||||
}
|
||||
|
||||
// try to remove +1/+1 counter from undying creature
|
||||
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.P1P1))) {
|
||||
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.P1P1), CardPredicates.hasKeyword(Keyword.UNDYING));
|
||||
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.P1P1, c),
|
||||
CardPredicates.hasKeyword("Undying"));
|
||||
|
||||
toRemove += removeCounter(table, prefs, CounterEnumType.P1P1, c - toRemove);
|
||||
if (!prefs.isEmpty()) {
|
||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterType.P1P1));
|
||||
PaymentDecision result = PaymentDecision.card(prefs);
|
||||
result.ct = CounterType.P1P1;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (c > toRemove && cost.counter == null && originalHost.hasSVar("AIRemoveCounterCostPriority") && !"ANY".equalsIgnoreCase(originalHost.getSVar("AIRemoveCounterCostPriority"))) {
|
||||
String[] counters = TextUtil.split(originalHost.getSVar("AIRemoveCounterCostPriority"), ',');
|
||||
// try to remove -1/-1 counter from persist creature
|
||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.M1M1, c),
|
||||
CardPredicates.hasKeyword("Persist"));
|
||||
|
||||
for (final String ctr : counters) {
|
||||
CounterType ctype = CounterType.getType(ctr);
|
||||
// ctype == null means any type
|
||||
// any type is just used to return null for this
|
||||
|
||||
for (Card card : CardLists.filter(typeList, CardPredicates.hasCounter(ctype))) {
|
||||
int thisRemove = Math.min(card.getCounters(ctype), c - toRemove);
|
||||
if (thisRemove > 0) {
|
||||
toRemove += thisRemove;
|
||||
table.put(card, ctype, thisRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!prefs.isEmpty()) {
|
||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterType.M1M1));
|
||||
PaymentDecision result = PaymentDecision.card(prefs);
|
||||
result.ct = CounterType.M1M1;
|
||||
return result;
|
||||
}
|
||||
|
||||
// filter for negative counters
|
||||
if (c > toRemove && cost.counter == null) {
|
||||
List<Card> negatives = CardLists.filter(typeList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (CounterType cType : table.filterToRemove(crd).keySet()) {
|
||||
if (ComputerUtil.isNegativeCounter(cType, crd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// try to remove Time counter from Chronozoa, it will generate more
|
||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.TIME, c),
|
||||
CardPredicates.nameEquals("Chronozoa"));
|
||||
|
||||
if (!negatives.isEmpty()) {
|
||||
// TODO sort negatives to remove from best Cards first?
|
||||
for (final Card crd : negatives) {
|
||||
for (Map.Entry<CounterType, Integer> e : table.filterToRemove(crd).entrySet()) {
|
||||
if (ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||
int over = Math.min(e.getValue(), c - toRemove);
|
||||
if (over > 0) {
|
||||
toRemove += over;
|
||||
table.put(crd, e.getKey(), over);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filter for useless counters
|
||||
// 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>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (CounterType ctype : table.filterToRemove(crd).keySet()) {
|
||||
if (ComputerUtil.isUselessCounter(ctype, crd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!useless.isEmpty()) {
|
||||
for (final Card crd : useless) {
|
||||
for (Map.Entry<CounterType, Integer> e : table.filterToRemove(crd).entrySet()) {
|
||||
if (ComputerUtil.isUselessCounter(e.getKey(), crd)) {
|
||||
int over = Math.min(e.getValue(), c - toRemove);
|
||||
if (over > 0) {
|
||||
toRemove += over;
|
||||
table.put(crd, e.getKey(), over);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to remove Time counter from Chronozoa, it will generate more token
|
||||
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.TIME))) {
|
||||
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.TIME), CardPredicates.nameEquals("Chronozoa"));
|
||||
|
||||
toRemove += removeCounter(table, prefs, CounterEnumType.TIME, c - toRemove);
|
||||
if (!prefs.isEmpty()) {
|
||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterType.TIME));
|
||||
PaymentDecision result = PaymentDecision.card(prefs);
|
||||
result.ct = CounterType.TIME;
|
||||
return result;
|
||||
}
|
||||
|
||||
// try to remove Quest counter on something with enough counters for the
|
||||
// effect to continue
|
||||
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.QUEST))) {
|
||||
List<Card> prefs = CardLists.filter(typeList, new Predicate<Card>() {
|
||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.QUEST, c));
|
||||
|
||||
if (!prefs.isEmpty()) {
|
||||
prefs = CardLists.filter(prefs, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
// a Card without MaxQuestEffect doesn't need any Quest
|
||||
@@ -747,65 +673,130 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
if (crd.hasSVar("MaxQuestEffect")) {
|
||||
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
|
||||
}
|
||||
return crd.getCounters(CounterEnumType.QUEST) > e;
|
||||
return crd.getCounters(CounterType.QUEST) >= e + c;
|
||||
}
|
||||
});
|
||||
Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterEnumType.QUEST)));
|
||||
|
||||
for (final Card crd : prefs) {
|
||||
int e = 0;
|
||||
if (crd.hasSVar("MaxQuestEffect")) {
|
||||
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
|
||||
}
|
||||
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
|
||||
if (over > 0) {
|
||||
toRemove += over;
|
||||
table.put(crd, CounterType.get(CounterEnumType.QUEST), over);
|
||||
}
|
||||
}
|
||||
Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterType.QUEST)));
|
||||
PaymentDecision result = PaymentDecision.card(prefs);
|
||||
result.ct = CounterType.QUEST;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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);
|
||||
// filter for only cards with enough counters
|
||||
typeList = CardLists.filter(typeList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (Integer i : crd.getCounters().values()) {
|
||||
if (i >= c) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// nothing with enough counters of any type
|
||||
if (typeList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// filter for negative counters
|
||||
List<Card> negatives = CardLists.filter(typeList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
if (!negatives.isEmpty()) {
|
||||
final Card card = ComputerUtilCard.getBestAI(negatives);
|
||||
PaymentDecision result = PaymentDecision.card(card);
|
||||
|
||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && ComputerUtil.isNegativeCounter(e.getKey(), card)) {
|
||||
result.ct = e.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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);
|
||||
// filter for useless counters
|
||||
// they have no effect on the card, if they are there or removed
|
||||
List<Card> useless = CardLists.filter(typeList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && ComputerUtil.isUselessCounter(e.getKey())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!useless.isEmpty()) {
|
||||
final Card card = useless.get(0);
|
||||
PaymentDecision result = PaymentDecision.card(card);
|
||||
|
||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && ComputerUtil.isUselessCounter(e.getKey())) {
|
||||
result.ct = e.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// if table is empty, than no counter was removed
|
||||
return table.isEmpty() ? null : PaymentDecision.counters(table);
|
||||
// try a way to pay unless cost
|
||||
if ("Chisei, Heart of Oceans".equals(ComputerUtilAbility.getAbilitySourceName(ability))) {
|
||||
final Card card = ComputerUtilCard.getWorstAI(typeList);
|
||||
PaymentDecision result = PaymentDecision.card(card);
|
||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c) {
|
||||
result.ct = e.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// check if the card defines its own priorities for counter removal as cost
|
||||
if (source.hasSVar("AIRemoveCounterCostPriority")) {
|
||||
String[] counters = TextUtil.split(source.getSVar("AIRemoveCounterCostPriority"), ',');
|
||||
|
||||
for (final String ctr : counters) {
|
||||
List<Card> withCtr = CardLists.filter(typeList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey() == CounterType.valueOf(ctr))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (!withCtr.isEmpty()) {
|
||||
final Card card = withCtr.get(0);
|
||||
PaymentDecision result = PaymentDecision.card(card);
|
||||
|
||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey() == CounterType.valueOf(ctr))) {
|
||||
result.ct = e.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -816,11 +807,14 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(amount);
|
||||
if (amount.equals("All")) {
|
||||
if (sVar.equals("XChoice")) {
|
||||
c = AbilityUtils.calculateAmount(source, "ChosenX", ability);
|
||||
source.setSVar("ChosenX", "Number$" + String.valueOf(c));
|
||||
} else if (amount.equals("All")) {
|
||||
c = source.getCounters(cost.counter);
|
||||
} else if (sVar.equals("Targeted$CardManaCost")) {
|
||||
c = 0;
|
||||
if (ability.getTargets().size() > 0) {
|
||||
if (ability.getTargets().getNumTargeted() > 0) {
|
||||
for (Card tgt : ability.getTargets().getTargetCards()) {
|
||||
if (tgt.getManaCost() != null) {
|
||||
c += tgt.getManaCost().getCMC();
|
||||
@@ -860,10 +854,22 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
final String amount = cost.getAmount();
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(amount);
|
||||
if (sVar.equals("XChoice")) {
|
||||
CardCollection typeList = CardLists.getValidCards(player.getGame().getCardsIn(ZoneType.Battlefield),
|
||||
cost.getType().split(";"), player, ability.getHostCard(), ability);
|
||||
if (!cost.canUntapSource) {
|
||||
typeList.remove(source);
|
||||
}
|
||||
typeList = CardLists.filter(typeList, Presets.TAPPED);
|
||||
c = typeList.size();
|
||||
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||
}
|
||||
}
|
||||
|
||||
CardCollectionView list = ComputerUtil.chooseUntapType(player, cost.getType(), source, cost.canUntapSource, c, ability);
|
||||
CardCollectionView list = ComputerUtil.chooseUntapType(player, cost.getType(), source, cost.canUntapSource, c);
|
||||
|
||||
if (list == null) {
|
||||
System.out.println("Couldn't find a valid card to untap for: " + source.getName());
|
||||
|
||||
@@ -17,5 +17,5 @@ public enum AiPlayDecision {
|
||||
WouldBecomeZeroToughnessCreature,
|
||||
WouldDestroyWorldEnchantment,
|
||||
BadEtbEffects,
|
||||
CurseEffects
|
||||
CurseEffects;
|
||||
}
|
||||
@@ -17,19 +17,19 @@
|
||||
*/
|
||||
package forge.ai;
|
||||
|
||||
import forge.LobbyPlayer;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.FileUtil;
|
||||
|
||||
import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import forge.LobbyPlayer;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.FileUtil;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
/**
|
||||
* Holds default AI personality profile values in an enum.
|
||||
* Loads profile from the given text file when setProfile is called.
|
||||
@@ -39,7 +39,7 @@ import forge.util.TextUtil;
|
||||
* @version $Id: AIProfile.java 20169 2013-03-08 08:24:17Z Agetian $
|
||||
*/
|
||||
public class AiProfileUtil {
|
||||
private static Map<String, Map<AiProps, String>> loadedProfiles = new HashMap<>();
|
||||
private static Map<String, Map<AiProps, String>> loadedProfiles = new HashMap<String, Map<AiProps, String>>();
|
||||
|
||||
private static String AI_PROFILE_DIR;
|
||||
private static final String AI_PROFILE_EXT = ".ai";
|
||||
@@ -74,7 +74,7 @@ public class AiProfileUtil {
|
||||
* @param profileName a profile to load.
|
||||
*/
|
||||
private static final Map<AiProps, String> loadProfile(final String profileName) {
|
||||
Map<AiProps, String> profileMap = new HashMap<>();
|
||||
Map<AiProps, String> profileMap = new HashMap<AiProps, String>();
|
||||
|
||||
List<String> lines = FileUtil.readFile(buildFileName(profileName));
|
||||
for (String line : lines) {
|
||||
@@ -122,7 +122,7 @@ public class AiProfileUtil {
|
||||
*/
|
||||
public static List<String> getAvailableProfiles()
|
||||
{
|
||||
final List<String> availableProfiles = new ArrayList<>();
|
||||
final List<String> availableProfiles = new ArrayList<String>();
|
||||
|
||||
final File dir = new File(AI_PROFILE_DIR);
|
||||
final String[] children = dir.list();
|
||||
@@ -146,7 +146,7 @@ public class AiProfileUtil {
|
||||
* available profiles including special random profile tags.
|
||||
*/
|
||||
public static List<String> getProfilesDisplayList() {
|
||||
final List<String> availableProfiles = new ArrayList<>();
|
||||
final List<String> availableProfiles = new ArrayList<String>();
|
||||
availableProfiles.add(AI_PROFILE_RANDOM_MATCH);
|
||||
availableProfiles.add(AI_PROFILE_RANDOM_DUEL);
|
||||
availableProfiles.addAll(getAvailableProfiles());
|
||||
|
||||
@@ -35,7 +35,6 @@ public enum AiProps { /** */
|
||||
MOVE_EQUIPMENT_TO_BETTER_CREATURES ("from_useless_only"),
|
||||
MOVE_EQUIPMENT_CREATURE_EVAL_THRESHOLD ("60"),
|
||||
PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS ("true"),
|
||||
SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD ("400"),
|
||||
PREDICT_SPELLS_FOR_MAIN2 ("true"), /** */
|
||||
RESERVE_MANA_FOR_MAIN2_CHANCE ("0"), /** */
|
||||
PLAY_AGGRO ("false"),
|
||||
@@ -75,7 +74,6 @@ public enum AiProps { /** */
|
||||
ALWAYS_COPY_SPELL_IF_CMC_DIFF ("2"), /** */
|
||||
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */
|
||||
ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */
|
||||
ACTIVELY_PROTECT_VS_CURSE_AURAS("false"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */
|
||||
@@ -131,10 +129,7 @@ public enum AiProps { /** */
|
||||
FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS("true"),
|
||||
FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY("1"),
|
||||
FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT("5"),
|
||||
FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("100"),
|
||||
BLINK_RELOAD_PLANESWALKER_CHANCE("30"), /** */
|
||||
BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY("2"), /** */
|
||||
BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF("2"); /** */
|
||||
FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("100"); /** */
|
||||
// Experimental features, must be promoted or removed after extensive testing and, ideally, defaulting
|
||||
// <-- There are no experimental options here -->
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,7 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameActionUtil;
|
||||
@@ -21,6 +17,9 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class ComputerUtilAbility {
|
||||
public static CardCollection getAvailableLandsToPlay(final Game game, final Player player) {
|
||||
if (!game.getStack().isEmpty() || !game.getPhaseHandler().getPhase().isMain()) {
|
||||
@@ -82,28 +81,31 @@ public class ComputerUtilAbility {
|
||||
public static List<SpellAbility> getSpellAbilities(final CardCollectionView l, final Player player) {
|
||||
final List<SpellAbility> spellAbilities = Lists.newArrayList();
|
||||
for (final Card c : l) {
|
||||
spellAbilities.addAll(c.getAllPossibleAbilities(player, false));
|
||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||
spellAbilities.add(sa);
|
||||
}
|
||||
if (c.isFaceDown() && c.isInZone(ZoneType.Exile) && !c.mayPlay(player).isEmpty()) {
|
||||
for (final SpellAbility sa : c.getState(CardStateName.Original).getSpellAbilities()) {
|
||||
spellAbilities.add(sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
return spellAbilities;
|
||||
}
|
||||
|
||||
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
|
||||
final List<SpellAbility> newAbilities = Lists.newArrayList();
|
||||
|
||||
List<SpellAbility> originListWithAddCosts = Lists.newArrayList();
|
||||
for (SpellAbility sa : originList) {
|
||||
// If this spell has alternative additional costs, add them instead of the unmodified SA itself
|
||||
sa.setActivatingPlayer(player);
|
||||
originListWithAddCosts.addAll(GameActionUtil.getAdditionalCostSpell(sa));
|
||||
}
|
||||
|
||||
for (SpellAbility sa : originListWithAddCosts) {
|
||||
// determine which alternative costs are cheaper than the original and prioritize them
|
||||
List<SpellAbility> saAltCosts = GameActionUtil.getAlternativeCosts(sa, player);
|
||||
List<SpellAbility> priorityAltSa = Lists.newArrayList();
|
||||
List<SpellAbility> otherAltSa = Lists.newArrayList();
|
||||
for (SpellAbility altSa : saAltCosts) {
|
||||
if (sa.getPayCosts().isOnlyManaCost()
|
||||
if (altSa.getPayCosts() == null || sa.getPayCosts() == null) {
|
||||
otherAltSa.add(altSa);
|
||||
} else if (sa.getPayCosts().isOnlyManaCost()
|
||||
&& altSa.getPayCosts().isOnlyManaCost() && sa.getPayCosts().getTotalMana().compareTo(altSa.getPayCosts().getTotalMana()) == 1) {
|
||||
// the alternative cost is strictly cheaper, so why not? (e.g. Omniscience etc.)
|
||||
priorityAltSa.add(altSa);
|
||||
@@ -171,8 +173,12 @@ public class ComputerUtilAbility {
|
||||
return sa;
|
||||
}
|
||||
|
||||
public static Card getAbilitySource(SpellAbility sa) {
|
||||
return sa.getOriginalHost() != null ? sa.getOriginalHost() : sa.getHostCard();
|
||||
}
|
||||
|
||||
public static String getAbilitySourceName(SpellAbility sa) {
|
||||
final Card c = sa.getOriginalOrHost();
|
||||
final Card c = getAbilitySource(sa);
|
||||
return c != null ? c.getName() : "";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,11 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.MutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardType;
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
@@ -31,18 +15,10 @@ import forge.deck.Deck;
|
||||
import forge.deck.DeckSection;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
@@ -64,6 +40,12 @@ import forge.item.PaperCard;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.MyRandom;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.MutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class ComputerUtilCard {
|
||||
public static Card getMostExpensivePermanentAI(final CardCollectionView list, final SpellAbility spell, final boolean targeted) {
|
||||
@@ -190,22 +172,21 @@ public class ComputerUtilCard {
|
||||
|
||||
// if no non-basic lands, target the least represented basic land type
|
||||
String sminBL = "";
|
||||
int iminBL = Integer.MAX_VALUE;
|
||||
int iminBL = 20000; // hopefully no one will ever have more than 20000
|
||||
// lands of one type....
|
||||
int n = 0;
|
||||
for (String name : MagicColor.Constant.BASIC_LANDS) {
|
||||
n = CardLists.getType(land, name).size();
|
||||
if (n < iminBL && n > 0) {
|
||||
if ((n < iminBL) && (n > 0)) {
|
||||
// if two or more are tied, only the
|
||||
// first
|
||||
// one checked will be used
|
||||
iminBL = n;
|
||||
sminBL = name;
|
||||
}
|
||||
}
|
||||
if (iminBL == Integer.MAX_VALUE) {
|
||||
// All basic lands have no basic land type. Just return something
|
||||
Iterator<Card> untapped = Iterables.filter(land, CardPredicates.Presets.UNTAPPED).iterator();
|
||||
if (untapped.hasNext()) {
|
||||
return untapped.next();
|
||||
}
|
||||
return land.get(0);
|
||||
if (iminBL == 20000) {
|
||||
return null; // no basic land was a minimum
|
||||
}
|
||||
|
||||
final List<Card> bLand = CardLists.getType(land, sminBL);
|
||||
@@ -214,6 +195,7 @@ public class ComputerUtilCard {
|
||||
return ut;
|
||||
}
|
||||
|
||||
|
||||
return Aggregates.random(bLand); // random tapped land of least represented type
|
||||
}
|
||||
|
||||
@@ -246,7 +228,7 @@ public class ComputerUtilCard {
|
||||
Card cheapest = null;
|
||||
|
||||
for (Card c : all) {
|
||||
if (cheapest == null || c.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
|
||||
if (cheapest == null || cheapest.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
|
||||
cheapest = c;
|
||||
}
|
||||
}
|
||||
@@ -327,17 +309,6 @@ public class ComputerUtilCard {
|
||||
return biggest;
|
||||
}
|
||||
|
||||
// For ability of Oracle en-Vec, return the first card that are going to attack next turn
|
||||
public static Card getBestCreatureToAttackNextTurnAI(final Player aiPlayer, final Iterable<Card> list) {
|
||||
AiController aic = ((PlayerControllerAi)aiPlayer.getController()).getAi();
|
||||
for(final Card card : list) {
|
||||
if (aic.getPredictedCombatNextTurn().isAttacking(card)) {
|
||||
return card;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getWorstAI.
|
||||
@@ -397,7 +368,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
|
||||
if (hasEnchantmants || hasArtifacts) {
|
||||
final List<Card> ae = CardLists.filter(list, Predicates.and(Predicates.or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.ENCHANTMENTS), new Predicate<Card>() {
|
||||
final List<Card> ae = CardLists.filter(list, Predicates.and(Predicates.<Card>or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.ENCHANTMENTS), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return !card.hasSVar("DoNotDiscardIfAble");
|
||||
@@ -550,7 +521,7 @@ public class ComputerUtilCard {
|
||||
*/
|
||||
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
|
||||
AiBlockController aiBlk = new AiBlockController(ai);
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
Combat combat = new Combat(opp);
|
||||
//Use actual attackers if available, else consider all possible attackers
|
||||
Combat currentCombat = ai.getGame().getCombat();
|
||||
@@ -593,7 +564,7 @@ public class ComputerUtilCard {
|
||||
AiBlockController aiBlk = new AiBlockController(ai);
|
||||
Combat combat = new Combat(ai);
|
||||
combat.addAttacker(attacker, ai);
|
||||
final List<Card> attackers = new ArrayList<>();
|
||||
final List<Card> attackers = new ArrayList<Card>();
|
||||
attackers.add(attacker);
|
||||
aiBlk.assignBlockersGivenAttackers(combat, attackers);
|
||||
return ComputerUtilCombat.attackerWouldBeDestroyed(ai, attacker, combat);
|
||||
@@ -672,7 +643,7 @@ public class ComputerUtilCard {
|
||||
return getMostProminentType(list, CardType.getAllCreatureTypes());
|
||||
}
|
||||
|
||||
public static String getMostProminentType(final CardCollectionView list, final Collection<String> valid) {
|
||||
public static String getMostProminentType(final CardCollectionView list, final List<String> valid) {
|
||||
if (list.size() == 0) {
|
||||
return "";
|
||||
}
|
||||
@@ -727,12 +698,31 @@ public class ComputerUtilCard {
|
||||
}
|
||||
// same for Trigger that does make Tokens
|
||||
for(Trigger t:c.getTriggers()){
|
||||
SpellAbility sa = t.ensureAbility();
|
||||
SpellAbility sa = t.getOverridingAbility();
|
||||
String sTokenTypes = null;
|
||||
if (sa != null) {
|
||||
if (sa.getApi() != ApiType.Token || !sa.hasParam("TokenTypes")) {
|
||||
continue;
|
||||
}
|
||||
for (String var : sa.getParam("TokenTypes").split(",")) {
|
||||
sTokenTypes = sa.getParam("TokenTypes");
|
||||
} else if (t.hasParam("Execute")) {
|
||||
String name = t.getParam("Execute");
|
||||
if (!c.hasSVar(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, String> params = AbilityFactory.getMapParams(c.getSVar(name));
|
||||
if (!params.containsKey("TokenTypes")) {
|
||||
continue;
|
||||
}
|
||||
sTokenTypes = params.get("TokenTypes");
|
||||
}
|
||||
|
||||
if (sTokenTypes == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String var : sTokenTypes.split(",")) {
|
||||
if (!CardType.isACreatureType(var)) {
|
||||
continue;
|
||||
}
|
||||
@@ -743,7 +733,6 @@ public class ComputerUtilCard {
|
||||
typesInDeck.put(var, count + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// special rule for Fabricate and Servo
|
||||
if(c.hasStartOfKeyword(Keyword.FABRICATE.toString())){
|
||||
Integer count = typesInDeck.get("Servo");
|
||||
@@ -799,7 +788,7 @@ public class ComputerUtilCard {
|
||||
|
||||
public static List<String> getColorByProminence(final List<Card> list) {
|
||||
int cntColors = MagicColor.WUBRG.length;
|
||||
final List<Pair<Byte,Integer>> map = new ArrayList<>();
|
||||
final List<Pair<Byte,Integer>> map = new ArrayList<Pair<Byte,Integer>>();
|
||||
for(int i = 0; i < cntColors; i++) {
|
||||
map.add(MutablePair.of(MagicColor.WUBRG[i], 0));
|
||||
}
|
||||
@@ -820,7 +809,7 @@ public class ComputerUtilCard {
|
||||
});
|
||||
|
||||
// will this part be once dropped?
|
||||
List<String> result = new ArrayList<>(cntColors);
|
||||
List<String> result = new ArrayList<String>(cntColors);
|
||||
for(Pair<Byte, Integer> idx : map) { // fetch color names in the same order
|
||||
result.add(MagicColor.toLongString(idx.getKey()));
|
||||
}
|
||||
@@ -892,10 +881,10 @@ public class ComputerUtilCard {
|
||||
}
|
||||
};
|
||||
public static List<String> chooseColor(SpellAbility sa, int min, int max, List<String> colorChoices) {
|
||||
List<String> chosen = new ArrayList<>();
|
||||
List<String> chosen = new ArrayList<String>();
|
||||
Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (sa.hasParam("AILogic")) {
|
||||
final String logic = sa.getParam("AILogic");
|
||||
|
||||
@@ -937,7 +926,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
else if (logic.equals("MostProminentInComputerDeckButGreen")) {
|
||||
List<String> prominence = ComputerUtilCard.getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai));
|
||||
if (prominence.get(0).equals(MagicColor.Constant.GREEN)) {
|
||||
if (prominence.get(0) == MagicColor.Constant.GREEN) {
|
||||
chosen.add(prominence.get(1));
|
||||
} else {
|
||||
chosen.add(prominence.get(0));
|
||||
@@ -975,22 +964,6 @@ public class ComputerUtilCard {
|
||||
}
|
||||
chosen.add(chosenColor);
|
||||
}
|
||||
else if (logic.equals("HighestDevotionToColor")) {
|
||||
int curDevotion = 0;
|
||||
String chosenColor = MagicColor.Constant.WHITE;
|
||||
CardCollectionView hand = ai.getCardsIn(ZoneType.Hand);
|
||||
for(byte c : MagicColor.WUBRG) {
|
||||
String devotionCode = "Count$Devotion." + MagicColor.toLongString(c);
|
||||
|
||||
int devotion = AbilityUtils.calculateAmount(sa.getHostCard(), devotionCode, sa);
|
||||
if (devotion > curDevotion && !CardLists.filter(hand, CardPredicates.isColor(c)).isEmpty()) {
|
||||
curDevotion = devotion;
|
||||
chosenColor = MagicColor.toLongString(c);
|
||||
}
|
||||
}
|
||||
chosen.add(chosenColor);
|
||||
}
|
||||
|
||||
}
|
||||
if (chosen.isEmpty()) {
|
||||
chosen.add(MagicColor.Constant.GREEN);
|
||||
@@ -1001,7 +974,7 @@ public class ComputerUtilCard {
|
||||
public static boolean useRemovalNow(final SpellAbility sa, final Card c, final int dmg, ZoneType destination) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final Game game = ai.getGame();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
final PhaseType phaseType = ph.getPhase();
|
||||
@@ -1155,14 +1128,14 @@ public class ComputerUtilCard {
|
||||
// assume it either benefits the player or disrupts the opponent
|
||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||
final Map<String, String> params = stAb.getMapParams();
|
||||
if (params.get("Mode").equals("Continuous") && stAb.isIntrinsic()) {
|
||||
if (params.get("Mode").equals("Continuous") && stAb.isIntrinsic() && !stAb.isTemporary()) {
|
||||
priority = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!priority) {
|
||||
for (final Trigger t : c.getTriggers()) {
|
||||
if (t.isIntrinsic()) {
|
||||
if (t.isIntrinsic() && !t.isTemporary()) {
|
||||
// has a triggered ability, could be benefitting the opponent or disrupting the AI
|
||||
priority = true;
|
||||
break;
|
||||
@@ -1171,8 +1144,8 @@ public class ComputerUtilCard {
|
||||
}
|
||||
// if this thing has AILogic set to "Curse", it's probably meant as some form of disruption
|
||||
if (!priority) {
|
||||
for (final String value : c.getSVars().values()) {
|
||||
if (value.contains("AILogic$ Curse")) {
|
||||
for (final String sVar : c.getSVars().keySet()) {
|
||||
if (c.getSVars().get(sVar).contains("AILogic$ Curse")) {
|
||||
// this is a curse ability, so prioritize its removal
|
||||
priority = true;
|
||||
break;
|
||||
@@ -1214,10 +1187,11 @@ public class ComputerUtilCard {
|
||||
final float valueNow = Math.max(valueTempo, threat);
|
||||
if (valueNow < 0.2) { //hard floor to reduce ridiculous odds for instants over time
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
final float chance = MyRandom.getRandom().nextFloat();
|
||||
return chance < valueNow;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides if the "pump" is worthwhile
|
||||
@@ -1239,7 +1213,6 @@ public class ComputerUtilCard {
|
||||
final Game game = ai.getGame();
|
||||
final PhaseHandler phase = game.getPhaseHandler();
|
||||
final Combat combat = phase.getCombat();
|
||||
final boolean main1Preferred = "Main1IfAble".equals(sa.getParam("AILogic")) && phase.is(PhaseType.MAIN1, ai);
|
||||
final boolean isBerserk = "Berserk".equals(sa.getParam("AILogic"));
|
||||
final boolean loseCardAtEOT = "Sacrifice".equals(sa.getParam("AtEOT")) || "Exile".equals(sa.getParam("AtEOT"))
|
||||
|| "Destroy".equals(sa.getParam("AtEOT")) || "ExileCombat".equals(sa.getParam("AtEOT"));
|
||||
@@ -1277,7 +1250,7 @@ public class ComputerUtilCard {
|
||||
// will the creature attack (only relevant for sorcery speed)?
|
||||
if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& phase.isPlayerTurn(ai)
|
||||
&& SpellAbilityAi.isSorcerySpeed(sa) || main1Preferred
|
||||
&& SpellAbilityAi.isSorcerySpeed(sa)
|
||||
&& power > 0
|
||||
&& ComputerUtilCard.doesCreatureAttackAI(ai, c)) {
|
||||
return true;
|
||||
@@ -1296,7 +1269,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
}
|
||||
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
Card pumped = getPumpedCreature(ai, sa, c, toughness, power, keywords);
|
||||
List<Card> oppCreatures = opp.getCreaturesInPlay();
|
||||
float chance = 0;
|
||||
@@ -1320,14 +1293,14 @@ public class ComputerUtilCard {
|
||||
// cast it during Declare Blockers, thus ruining its attacker
|
||||
if (holdCombatTricks && sa.getApi() == ApiType.Pump
|
||||
&& sa.hasParam("NumAtt") && sa.getHostCard() != null
|
||||
&& sa.getHostCard().isInZone(ZoneType.Hand)
|
||||
&& sa.getHostCard().getZone() != null && sa.getHostCard().getZone().is(ZoneType.Hand)
|
||||
&& c.getNetPower() > 0 // too obvious if attacking with a 0-power creature
|
||||
&& sa.getHostCard().isInstant() // only do it for instant speed spells in hand
|
||||
&& ComputerUtilMana.hasEnoughManaSourcesToCast(sa, ai)) {
|
||||
combatTrick = true;
|
||||
|
||||
final List<String> kws = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & "))
|
||||
: Lists.newArrayList();
|
||||
: Lists.<String>newArrayList();
|
||||
for (String kw : kws) {
|
||||
if (!kw.equals("Trample") && !kw.equals("First Strike") && !kw.equals("Double Strike")) {
|
||||
combatTrick = false;
|
||||
@@ -1433,8 +1406,8 @@ public class ComputerUtilCard {
|
||||
if (combat.isAttacking(c) && opp.getLife() > 0) {
|
||||
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opp, combat, true);
|
||||
int pumpedDmg = ComputerUtilCombat.damageIfUnblocked(pumped, opp, pumpedCombat, true);
|
||||
int poisonOrig = opp.canReceiveCounters(CounterEnumType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(c, ai) : 0;
|
||||
int poisonPumped = opp.canReceiveCounters(CounterEnumType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(pumped, ai) : 0;
|
||||
int poisonOrig = opp.canReceiveCounters(CounterType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(c, ai) : 0;
|
||||
int poisonPumped = opp.canReceiveCounters(CounterType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(pumped, ai) : 0;
|
||||
|
||||
// predict Infect
|
||||
if (pumpedDmg == 0 && c.hasKeyword(Keyword.INFECT)) {
|
||||
@@ -1457,8 +1430,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
if (pumpedDmg > dmg) {
|
||||
if ((!c.hasKeyword(Keyword.INFECT) && pumpedDmg >= opp.getLife())
|
||||
|| (c.hasKeyword(Keyword.INFECT) && opp.canReceiveCounters(CounterEnumType.POISON) && pumpedDmg >= opp.getPoisonCounters())
|
||||
|| ("PumpForTrample".equals(sa.getParam("AILogic")))) {
|
||||
|| (c.hasKeyword(Keyword.INFECT) && opp.canReceiveCounters(CounterType.POISON) && pumpedDmg >= opp.getPoisonCounters())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1484,10 +1456,6 @@ public class ComputerUtilCard {
|
||||
}
|
||||
if (totalPowerUnblocked >= opp.getLife()) {
|
||||
return true;
|
||||
} else if (totalPowerUnblocked > dmg && sa.getHostCard() != null && sa.getHostCard().isInPlay()) {
|
||||
if (sa.getPayCosts().hasNoManaCost()) {
|
||||
return true; // always activate abilities which cost no mana and which can increase unblocked damage
|
||||
}
|
||||
}
|
||||
}
|
||||
float value = 1.0f * (pumpedDmg - dmg);
|
||||
@@ -1598,7 +1566,7 @@ public class ComputerUtilCard {
|
||||
Card pumped = CardFactory.copyCard(c, true);
|
||||
pumped.setSickness(c.hasSickness());
|
||||
final long timestamp = c.getGame().getNextTimestamp();
|
||||
final List<String> kws = new ArrayList<>();
|
||||
final List<String> kws = new ArrayList<String>();
|
||||
for (String kw : keywords) {
|
||||
if (kw.startsWith("HIDDEN")) {
|
||||
pumped.addHiddenExtrinsicKeyword(kw);
|
||||
@@ -1628,12 +1596,12 @@ public class ComputerUtilCard {
|
||||
}
|
||||
|
||||
pumped.addNewPT(c.getCurrentPower(), c.getCurrentToughness(), timestamp);
|
||||
pumped.setPTBoost(c.getPTBoostTable());
|
||||
pumped.addPTBoost(power + berserkPower, toughness, timestamp, null);
|
||||
pumped.addTempPowerBoost(c.getTempPowerBoost() + power + berserkPower);
|
||||
pumped.addTempToughnessBoost(c.getTempToughnessBoost() + toughness);
|
||||
pumped.addChangedCardKeywords(kws, null, false, false, timestamp);
|
||||
Set<CounterType> types = c.getCounters().keySet();
|
||||
for(CounterType ct : types) {
|
||||
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, true, null);
|
||||
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, true);
|
||||
}
|
||||
//Copies tap-state and extra keywords (auras, equipment, etc.)
|
||||
if (c.isTapped()) {
|
||||
@@ -1675,9 +1643,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
list.add(vCard); // account for the static abilities that may be present on the card itself
|
||||
for (final Card c : list) {
|
||||
// remove old boost that might be copied
|
||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||
vCard.removePTBoost(c.getTimestamp(), stAb.getId());
|
||||
final Map<String, String> params = stAb.getMapParams();
|
||||
if (!params.get("Mode").equals("Continuous")) {
|
||||
continue;
|
||||
@@ -1692,25 +1658,26 @@ public class ComputerUtilCard {
|
||||
if (!vCard.isValid(valid, c.getController(), c, null)) {
|
||||
continue;
|
||||
}
|
||||
int att = 0;
|
||||
if (params.containsKey("AddPower")) {
|
||||
String addP = params.get("AddPower");
|
||||
int att = 0;
|
||||
if (addP.equals("AffectedX")) {
|
||||
att = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addP));
|
||||
} else {
|
||||
att = AbilityUtils.calculateAmount(c, addP, stAb);
|
||||
}
|
||||
vCard.addTempPowerBoost(att);
|
||||
}
|
||||
int def = 0;
|
||||
if (params.containsKey("AddToughness")) {
|
||||
String addT = params.get("AddToughness");
|
||||
int def = 0;
|
||||
if (addT.equals("AffectedY")) {
|
||||
def = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addT));
|
||||
} else {
|
||||
def = AbilityUtils.calculateAmount(c, addT, stAb);
|
||||
}
|
||||
vCard.addTempToughnessBoost(def);
|
||||
}
|
||||
vCard.addPTBoost(att, def, c.getTimestamp(), stAb.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1776,10 +1743,10 @@ public class ComputerUtilCard {
|
||||
}
|
||||
|
||||
public static boolean hasActiveUndyingOrPersist(final Card c) {
|
||||
if (c.hasKeyword(Keyword.UNDYING) && c.getCounters(CounterEnumType.P1P1) == 0) {
|
||||
if (c.hasKeyword(Keyword.UNDYING) && c.getCounters(CounterType.P1P1) == 0) {
|
||||
return true;
|
||||
}
|
||||
if (c.hasKeyword(Keyword.PERSIST) && c.getCounters(CounterEnumType.M1M1) == 0) {
|
||||
if (c.hasKeyword(Keyword.PERSIST) && c.getCounters(CounterType.M1M1) == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1796,6 +1763,10 @@ public class ComputerUtilCard {
|
||||
|
||||
for (Card c : otb) {
|
||||
for (SpellAbility sa : c.getSpellAbilities()) {
|
||||
if (sa.getPayCosts() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CostPayEnergy energyCost = sa.getPayCosts().getCostEnergy();
|
||||
if (energyCost != null) {
|
||||
int amount = energyCost.convertAmount();
|
||||
@@ -1867,64 +1838,36 @@ public class ComputerUtilCard {
|
||||
|
||||
public static AiPlayDecision checkNeedsToPlayReqs(final Card card, final SpellAbility sa) {
|
||||
Game game = card.getGame();
|
||||
boolean isRightSplit = sa != null && sa.getCardState().getStateName() == CardStateName.RightSplit;
|
||||
boolean isRightSplit = sa != null && sa.isRightSplit();
|
||||
String needsToPlayName = isRightSplit ? "SplitNeedsToPlay" : "NeedsToPlay";
|
||||
String needsToPlayVarName = isRightSplit ? "SplitNeedsToPlayVar" : "NeedsToPlayVar";
|
||||
|
||||
// TODO: if there are ever split cards with Evoke or Kicker, factor in the right split option above
|
||||
if (sa != null) {
|
||||
if (sa.isEvoke()) {
|
||||
// if the spell is evoked, will use NeedsToPlayEvoked if available (otherwise falls back to NeedsToPlay)
|
||||
if (card.hasSVar("NeedsToPlayEvoked")) {
|
||||
needsToPlayName = "NeedsToPlayEvoked";
|
||||
}
|
||||
if (card.hasSVar("NeedsToPlayEvokedVar")) {
|
||||
needsToPlayVarName = "NeedsToPlayEvokedVar";
|
||||
}
|
||||
} else if (sa.isKicked()) {
|
||||
// if the spell is kicked, uses NeedsToPlayKicked if able and locks out the regular NeedsToPlay check
|
||||
// for unkicked spells, uses NeedsToPlay
|
||||
if (card.hasSVar("NeedsToPlayKicked")) {
|
||||
needsToPlayName = "NeedsToPlayKicked";
|
||||
} else {
|
||||
needsToPlayName = "UNUSED";
|
||||
}
|
||||
if (card.hasSVar("NeedsToPlayKickedVar")) {
|
||||
needsToPlayVarName = "NeedsToPlayKickedVar";
|
||||
} else {
|
||||
needsToPlayVarName = "UNUSED";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (card.hasSVar(needsToPlayName)) {
|
||||
final String needsToPlay = card.getSVar(needsToPlayName);
|
||||
|
||||
// A special case which checks that this creature will attack if it's the AI's turn
|
||||
if (needsToPlay.equalsIgnoreCase("WillAttack")) {
|
||||
if (sa != null && game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
return ComputerUtilCard.doesSpecifiedCreatureAttackAI(sa.getActivatingPlayer(), card) ?
|
||||
AiPlayDecision.WillPlay : AiPlayDecision.BadEtbEffects;
|
||||
} else {
|
||||
return AiPlayDecision.WillPlay; // not our turn, skip this check for the possible Flash use etc.
|
||||
}
|
||||
}
|
||||
|
||||
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card, sa);
|
||||
list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card, null);
|
||||
if (list.isEmpty()) {
|
||||
return AiPlayDecision.MissingNeededCards;
|
||||
}
|
||||
}
|
||||
if (card.getSVar(needsToPlayVarName).length() > 0) {
|
||||
final String needsToPlay = card.getSVar(needsToPlayVarName);
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
String sVar = needsToPlay.split(" ")[0];
|
||||
String comparator = needsToPlay.split(" ")[1];
|
||||
String compareTo = comparator.substring(2);
|
||||
int x = AbilityUtils.calculateAmount(card, sVar, sa);
|
||||
int y = AbilityUtils.calculateAmount(card, compareTo, sa);
|
||||
|
||||
try {
|
||||
x = Integer.parseInt(sVar);
|
||||
} catch (final NumberFormatException e) {
|
||||
x = CardFactoryUtil.xCount(card, card.getSVar(sVar));
|
||||
}
|
||||
try {
|
||||
y = Integer.parseInt(compareTo);
|
||||
} catch (final NumberFormatException e) {
|
||||
y = CardFactoryUtil.xCount(card, card.getSVar(compareTo));
|
||||
}
|
||||
if (!Expressions.compare(x, comparator, y)) {
|
||||
return AiPlayDecision.NeedsToPlayCriteriaNotMet;
|
||||
}
|
||||
@@ -1941,7 +1884,4 @@ public class ComputerUtilCard {
|
||||
public static boolean isCardRemRandomDeck(final Card card) {
|
||||
return card.getRules() != null && card.getRules().getAiHints().getRemRandomDecks();
|
||||
}
|
||||
public static boolean isCardRemNonCommanderDeck(final Card card) {
|
||||
return card.getRules() != null && card.getRules().getAiHints().getRemNonCommanderDecks();
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,41 +1,16 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import forge.ai.ability.AnimateAi;
|
||||
import forge.card.ColorSet;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostDamage;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostPayLife;
|
||||
import forge.game.cost.CostPayment;
|
||||
import forge.game.cost.CostPutCounter;
|
||||
import forge.game.cost.CostRemoveAnyCounter;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.cost.CostSacrifice;
|
||||
import forge.game.cost.CostTapType;
|
||||
import forge.game.cost.PaymentDecision;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.Spell;
|
||||
@@ -44,6 +19,10 @@ import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public class ComputerUtilCost {
|
||||
@@ -66,7 +45,7 @@ public class ComputerUtilCost {
|
||||
final CostPutCounter addCounter = (CostPutCounter) part;
|
||||
final CounterType type = addCounter.getCounter();
|
||||
|
||||
if (type.is(CounterEnumType.M1M1)) {
|
||||
if (type.equals(CounterType.M1M1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -74,6 +53,9 @@ public class ComputerUtilCost {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean checkRemoveCounterCost(final Cost cost, final Card source) {
|
||||
return checkRemoveCounterCost(cost, source, null);
|
||||
}
|
||||
/**
|
||||
* Check remove counter cost.
|
||||
*
|
||||
@@ -87,14 +69,13 @@ public class ComputerUtilCost {
|
||||
if (cost == null) {
|
||||
return true;
|
||||
}
|
||||
final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa);
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
|
||||
|
||||
final CounterType type = remCounter.counter;
|
||||
if (!part.payCostFromSource()) {
|
||||
if (type.is(CounterEnumType.P1P1)) {
|
||||
if (CounterType.P1P1.equals(type)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
@@ -105,8 +86,19 @@ public class ComputerUtilCost {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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)
|
||||
if (sa != null && sa.hasSVar(remCounter.getAmount())) {
|
||||
final String sVar = sa.getSVar(remCounter.getAmount());
|
||||
if (sVar.equals("XChoice") && !sa.hasSVar("ChosenX")) {
|
||||
sa.setSVar("ChosenX", String.valueOf(source.getCounters(type)));
|
||||
}
|
||||
}
|
||||
|
||||
// check the sa what the PaymentDecision is.
|
||||
// ignore Loyality abilities with Zero as Cost
|
||||
if (!type.is(CounterEnumType.LOYALTY)) {
|
||||
if (sa != null && !CounterType.LOYALTY.equals(type)) {
|
||||
final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa);
|
||||
PaymentDecision pay = decision.visit(remCounter);
|
||||
if (pay == null || pay.c <= 0) {
|
||||
return false;
|
||||
@@ -114,15 +106,10 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
//don't kill the creature
|
||||
if (type.is(CounterEnumType.P1P1) && source.getLethalDamage() <= 1
|
||||
if (CounterType.P1P1.equals(type) && source.getLethalDamage() <= 1
|
||||
&& !source.hasKeyword(Keyword.UNDYING)) {
|
||||
return false;
|
||||
}
|
||||
} else if (part instanceof CostRemoveAnyCounter) {
|
||||
final CostRemoveAnyCounter remCounter = (CostRemoveAnyCounter) part;
|
||||
|
||||
PaymentDecision pay = decision.visit(remCounter);
|
||||
return pay != null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -137,9 +124,8 @@ public class ComputerUtilCost {
|
||||
* the source
|
||||
* @return true, if successful
|
||||
*/
|
||||
|
||||
public static boolean checkDiscardCost(final Player ai, final Cost cost, final Card source, SpellAbility sa) {
|
||||
if (cost == null || source.hasSVar("AISkipDiscardCostCheck") /* FIXME: should not be needed! */ ) {
|
||||
public static boolean checkDiscardCost(final Player ai, final Cost cost, final Card source) {
|
||||
if (cost == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -153,7 +139,7 @@ public class ComputerUtilCost {
|
||||
if (type.equals("CARDNAME") && source.getAbilityText().contains("Bloodrush")) {
|
||||
continue;
|
||||
}
|
||||
final CardCollection typeList = CardLists.getValidCards(hand, type.split(","), source.getController(), source, sa);
|
||||
final CardCollection typeList = CardLists.getValidCards(hand, type.split(","), source.getController(), source, null);
|
||||
if (typeList.size() > ai.getMaxHandSize()) {
|
||||
continue;
|
||||
}
|
||||
@@ -163,12 +149,13 @@ public class ComputerUtilCost {
|
||||
Card pref = ComputerUtil.getCardPreference(ai, source, "DiscardCost", typeList);
|
||||
if (pref == null) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
typeList.remove(pref);
|
||||
hand.remove(pref);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -270,7 +257,7 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
final CardCollection sacList = new CardCollection();
|
||||
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
|
||||
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, null);
|
||||
|
||||
int count = 0;
|
||||
while (count < amount) {
|
||||
@@ -320,7 +307,7 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
final CardCollection sacList = new CardCollection();
|
||||
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
|
||||
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, null);
|
||||
|
||||
int count = 0;
|
||||
while (count < amount) {
|
||||
@@ -428,7 +415,7 @@ public class ComputerUtilCost {
|
||||
continue;
|
||||
}
|
||||
final int remainingLife = ai.getLife();
|
||||
final int lifeCost = part.convertAmount();
|
||||
final int lifeCost = ((CostPayLife) part).convertAmount();
|
||||
if ((remainingLife - lifeCost) < 10) {
|
||||
return false; //Don't pay life if it would put AI under 10 life
|
||||
} else if ((remainingLife / lifeCost) < 4) {
|
||||
@@ -471,9 +458,9 @@ public class ComputerUtilCost {
|
||||
if(!meetsRestriction)
|
||||
continue;
|
||||
|
||||
if (StringUtils.isNumeric(parts[0])) {
|
||||
extraManaNeeded += Integer.parseInt(parts[0]);
|
||||
} else {
|
||||
try {
|
||||
extraManaNeeded += Integer.parseInt(snem);
|
||||
} catch (final NumberFormatException e) {
|
||||
System.out.println("wrong SpellsNeedExtraMana SVar format on " + c);
|
||||
}
|
||||
}
|
||||
@@ -484,9 +471,9 @@ public class ComputerUtilCost {
|
||||
}
|
||||
final String snem = c.getSVar("SpellsNeedExtraManaEffect");
|
||||
if (!StringUtils.isBlank(snem)) {
|
||||
if (StringUtils.isNumeric(snem)) {
|
||||
try {
|
||||
extraManaNeeded += Integer.parseInt(snem);
|
||||
} else {
|
||||
} catch (final NumberFormatException e) {
|
||||
System.out.println("wrong SpellsNeedExtraManaEffect SVar format on " + c);
|
||||
}
|
||||
}
|
||||
@@ -494,7 +481,7 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
// Try not to lose Planeswalker if not threatened
|
||||
if (sa.isPwAbility()) {
|
||||
if (sa.getRestrictions().isPwAbility()) {
|
||||
for (final CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
if (part.convertAmount() != null && part.convertAmount() == sa.getHostCard().getCurrentLoyalty()) {
|
||||
@@ -533,7 +520,7 @@ public class ComputerUtilCost {
|
||||
public boolean apply(Card card) {
|
||||
boolean hasManaSa = false;
|
||||
for (final SpellAbility sa : card.getSpellAbilities()) {
|
||||
if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
|
||||
if (sa.isManaAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
hasManaSa = true;
|
||||
break;
|
||||
}
|
||||
@@ -556,7 +543,7 @@ public class ComputerUtilCost {
|
||||
final Card source = sa.getHostCard();
|
||||
final String aiLogic = sa.getParam("UnlessAI");
|
||||
boolean payForOwnOnly = "OnlyOwn".equals(aiLogic);
|
||||
boolean payOwner = sa.hasParam("UnlessAI") && aiLogic.startsWith("Defined");
|
||||
boolean payOwner = sa.hasParam("UnlessAI") ? aiLogic.startsWith("Defined") : false;
|
||||
boolean payNever = "Never".equals(aiLogic);
|
||||
boolean shockland = "Shockland".equals(aiLogic);
|
||||
boolean isMine = sa.getActivatingPlayer().equals(payer);
|
||||
@@ -598,11 +585,6 @@ public class ComputerUtilCost {
|
||||
if (c == null || c.isUntapped()) {
|
||||
return false;
|
||||
}
|
||||
} else if ("RiskFactor".equals(aiLogic)) {
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
if (!activator.canDraw() || activator.hasKeyword("You can't draw more than one card each turn.")) {
|
||||
return false;
|
||||
}
|
||||
} else if ("MorePowerful".equals(aiLogic)) {
|
||||
final int sourceCreatures = sa.getActivatingPlayer().getCreaturesInPlay().size();
|
||||
final int payerCreatures = payer.getCreaturesInPlay().size();
|
||||
@@ -623,8 +605,7 @@ public class ComputerUtilCost {
|
||||
if (combat.getAttackers().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if ("nonToken".equals(aiLogic) && !AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).isEmpty()
|
||||
&& AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0).isToken()) {
|
||||
} else if ("nonToken".equals(aiLogic) && AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0).isToken()) {
|
||||
return false;
|
||||
} else if ("LowPriority".equals(aiLogic) && MyRandom.getRandom().nextInt(100) < 67) {
|
||||
return false;
|
||||
@@ -641,11 +622,11 @@ public class ComputerUtilCost {
|
||||
return checkLifeCost(payer, cost, source, 4, sa)
|
||||
&& checkDamageCost(payer, cost, source, 4)
|
||||
&& (isMine || checkSacrificeCost(payer, cost, source, sa))
|
||||
&& (isMine || checkDiscardCost(payer, cost, source, sa))
|
||||
&& (isMine || checkDiscardCost(payer, cost, source))
|
||||
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
|
||||
&& (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2)
|
||||
&& (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1)
|
||||
&& (!source.getName().equals("Chain of Vapor") || (payer.getWeakestOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
|
||||
&& (!source.getName().equals("Chain of Vapor") || (ComputerUtil.getOpponentFor(payer).getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
|
||||
}
|
||||
|
||||
public static Set<String> getAvailableManaColors(Player ai, Card additionalLand) {
|
||||
@@ -681,62 +662,4 @@ public class ComputerUtilCost {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getMaxXValue(SpellAbility sa, Player ai) {
|
||||
final Card source = sa.getHostCard();
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
if (abCost == null || !abCost.hasXInAnyCostPart()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Integer val = null;
|
||||
|
||||
if (sa.costHasManaX()) {
|
||||
val = ComputerUtilMana.determineLeftoverMana(root, 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(root, 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, sa);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,8 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import forge.ai.ability.AnimateAi;
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
@@ -28,27 +10,13 @@ import forge.card.mana.ManaAtom;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostParser;
|
||||
import forge.card.mana.ManaCostShard;
|
||||
import forge.game.CardTraitPredicates;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostAdjustment;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.cost.CostPayEnergy;
|
||||
import forge.game.cost.CostPayment;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.mana.Mana;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.mana.ManaPool;
|
||||
@@ -56,17 +24,16 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementLayer;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.AbilityManaPart;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ComputerUtilMana {
|
||||
private final static boolean DEBUG_MANA_PAYMENT = false;
|
||||
@@ -98,8 +65,6 @@ public class ComputerUtilMana {
|
||||
|
||||
// Does not check if mana sources can be used right now, just checks for potential chance.
|
||||
public static boolean hasEnoughManaSourcesToCast(final SpellAbility sa, final Player ai) {
|
||||
if(ai == null || sa == null)
|
||||
return false;
|
||||
sa.setActivatingPlayer(ai);
|
||||
return payManaCost(sa, ai, true, 0, false);
|
||||
}
|
||||
@@ -149,7 +114,7 @@ public class ComputerUtilMana {
|
||||
return score;
|
||||
}
|
||||
|
||||
private static void sortManaAbilities(final Multimap<ManaCostShard, SpellAbility> manaAbilityMap, final SpellAbility sa) {
|
||||
private static void sortManaAbilities(final Multimap<ManaCostShard, SpellAbility> manaAbilityMap) {
|
||||
final Map<Card, Integer> manaCardMap = Maps.newHashMap();
|
||||
final List<Card> orderedCards = Lists.newArrayList();
|
||||
|
||||
@@ -204,7 +169,8 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
return ability1.compareTo(ability2);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return preOrder;
|
||||
}
|
||||
}
|
||||
@@ -215,58 +181,6 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
manaAbilityMap.replaceValues(shard, newAbilities);
|
||||
|
||||
// Sort the first N abilities so that the preferred shard is selected, e.g. Adamant
|
||||
String manaPref = sa.getParamOrDefault("AIManaPref", "");
|
||||
if (manaPref.isEmpty() && sa.getHostCard() != null && sa.getHostCard().hasSVar("AIManaPref")) {
|
||||
manaPref = sa.getHostCard().getSVar("AIManaPref");
|
||||
}
|
||||
|
||||
if (!manaPref.isEmpty()) {
|
||||
final String[] prefShardInfo = manaPref.split(":");
|
||||
final String preferredShard = prefShardInfo[0];
|
||||
final int preferredShardAmount = prefShardInfo.length > 1 ? Integer.parseInt(prefShardInfo[1]) : 3;
|
||||
|
||||
if (!preferredShard.isEmpty()) {
|
||||
final List<SpellAbility> prefSortedAbilities = new ArrayList<>(newAbilities);
|
||||
final List<SpellAbility> otherSortedAbilities = new ArrayList<>(newAbilities);
|
||||
|
||||
Collections.sort(prefSortedAbilities, new Comparator<SpellAbility>() {
|
||||
@Override
|
||||
public int compare(final SpellAbility ability1, final SpellAbility ability2) {
|
||||
if (ability1.getManaPart().mana().contains(preferredShard))
|
||||
return -1;
|
||||
else if (ability2.getManaPart().mana().contains(preferredShard))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
Collections.sort(otherSortedAbilities, new Comparator<SpellAbility>() {
|
||||
@Override
|
||||
public int compare(final SpellAbility ability1, final SpellAbility ability2) {
|
||||
if (ability1.getManaPart().mana().contains(preferredShard))
|
||||
return 1;
|
||||
else if (ability2.getManaPart().mana().contains(preferredShard))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
final List<SpellAbility> finalAbilities = new ArrayList<>();
|
||||
for (int i = 0; i < preferredShardAmount && i < prefSortedAbilities.size(); i++) {
|
||||
finalAbilities.add(prefSortedAbilities.get(i));
|
||||
}
|
||||
for (int i = 0; i < otherSortedAbilities.size(); i++) {
|
||||
SpellAbility ab = otherSortedAbilities.get(i);
|
||||
if (!finalAbilities.contains(ab))
|
||||
finalAbilities.add(ab);
|
||||
}
|
||||
|
||||
manaAbilityMap.replaceValues(shard, finalAbilities);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,8 +233,8 @@ public class ComputerUtilMana {
|
||||
|
||||
// Exception: when paying generic mana with Cavern of Souls, prefer the colored mana producing ability
|
||||
// to attempt to make the spell uncounterable when possible.
|
||||
if (ComputerUtilAbility.getAbilitySourceName(ma).equals("Cavern of Souls") && ma.hasChosenType()
|
||||
&& sa.getHostCard().getType().getCreatureTypes().contains(ma.getChosenType(0))) {
|
||||
if (ComputerUtilAbility.getAbilitySourceName(ma).equals("Cavern of Souls")
|
||||
&& sa.getHostCard().getType().getCreatureTypes().contains(ma.getHostCard().getChosenType())) {
|
||||
if (toPay == ManaCostShard.COLORLESS && cost.getUnpaidShards().contains(ManaCostShard.GENERIC)) {
|
||||
// Deprioritize Cavern of Souls, try to pay generic mana with it instead to use the NoCounter ability
|
||||
continue;
|
||||
@@ -348,189 +262,6 @@ public class ComputerUtilMana {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String predictManaReplacement(SpellAbility saPayment, Player ai, ManaCostShard toPay) {
|
||||
Card hostCard = saPayment.getHostCard();
|
||||
Game game = hostCard.getGame();
|
||||
String manaProduced = toPay.isSnow() && hostCard.isSnow() ? "S" : GameActionUtil.generatedTotalMana(saPayment);
|
||||
//String originalProduced = manaProduced;
|
||||
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.newMap();
|
||||
repParams.put(AbilityKey.Mana, manaProduced);
|
||||
repParams.put(AbilityKey.Affected, hostCard);
|
||||
repParams.put(AbilityKey.Player, ai);
|
||||
repParams.put(AbilityKey.AbilityMana, saPayment); // RootAbility
|
||||
|
||||
// TODO Damping Sphere might replace later?
|
||||
|
||||
// add flags to replacementEffects to filter better?
|
||||
List<ReplacementEffect> reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other);
|
||||
|
||||
List<SpellAbility> replaceMana = Lists.newArrayList();
|
||||
List<SpellAbility> replaceType = Lists.newArrayList();
|
||||
List<SpellAbility> replaceAmount = Lists.newArrayList(); // currently only multi
|
||||
|
||||
// try to guess the color the mana gets replaced to
|
||||
for (ReplacementEffect re : reList) {
|
||||
SpellAbility o = re.getOverridingAbility();
|
||||
|
||||
if (o == null || o.getApi() != ApiType.ReplaceMana) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// this one does replace the amount too
|
||||
if (o.hasParam("ReplaceMana")) {
|
||||
replaceMana.add(o);
|
||||
} else if (o.hasParam("ReplaceType") || o.hasParam("ReplaceColor")) {
|
||||
// this one replaces the color/type
|
||||
// check if this one can be replaced into wanted mana shard
|
||||
replaceType.add(o);
|
||||
} else if (o.hasParam("ReplaceAmount")) {
|
||||
replaceAmount.add(o);
|
||||
}
|
||||
}
|
||||
|
||||
// it is better to apply these ones first
|
||||
if (!replaceMana.isEmpty()) {
|
||||
for (SpellAbility saMana : replaceMana) {
|
||||
// one of then has to Any
|
||||
// one of then has to C
|
||||
// one of then has to B
|
||||
String m = saMana.getParam("ReplaceMana");
|
||||
if ("Any".equals(m)) {
|
||||
byte rs = MagicColor.GREEN;
|
||||
for (byte c : MagicColor.WUBRGC) {
|
||||
if (toPay.canBePaidWithManaOfColor(c)) {
|
||||
rs = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
manaProduced = MagicColor.toShortString(rs);
|
||||
} else {
|
||||
manaProduced = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// then apply this one
|
||||
if (!replaceType.isEmpty()) {
|
||||
for (SpellAbility saMana : replaceAmount) {
|
||||
if (saMana.hasParam("ReplaceType")) {
|
||||
// replace color and colorless
|
||||
String color = saMana.getParam("ReplaceType");
|
||||
if ("Any".equals(color)) {
|
||||
byte rs = MagicColor.GREEN;
|
||||
for (byte c : MagicColor.WUBRGC) {
|
||||
if (toPay.canBePaidWithManaOfColor(c)) {
|
||||
rs = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
color = MagicColor.toShortString(rs);
|
||||
}
|
||||
for (byte c : MagicColor.WUBRGC) {
|
||||
String s = MagicColor.toShortString(c);
|
||||
manaProduced = manaProduced.replace(s, color);
|
||||
}
|
||||
} else if (saMana.hasParam("ReplaceColor")) {
|
||||
// replace color
|
||||
String color = saMana.getParam("ReplaceColor");
|
||||
if ("Chosen".equals(color)) {
|
||||
if (saMana.hasChosenColor()) {
|
||||
color = MagicColor.toShortString(saMana.getChosenColor());
|
||||
}
|
||||
}
|
||||
if (saMana.hasParam("ReplaceOnly")) {
|
||||
manaProduced = manaProduced.replace(saMana.getParam("ReplaceOnly"), color);
|
||||
} else {
|
||||
for (byte c : MagicColor.WUBRG) {
|
||||
String s = MagicColor.toShortString(c);
|
||||
manaProduced = manaProduced.replace(s, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// then multiply if able
|
||||
if (!replaceAmount.isEmpty()) {
|
||||
int totalAmount = 1;
|
||||
for (SpellAbility saMana : replaceAmount) {
|
||||
totalAmount *= Integer.valueOf(saMana.getParam("ReplaceAmount"));
|
||||
}
|
||||
manaProduced = StringUtils.repeat(manaProduced, " ", totalAmount);
|
||||
}
|
||||
|
||||
return manaProduced;
|
||||
}
|
||||
|
||||
public static String predictManafromSpellAbility(SpellAbility saPayment, Player ai, ManaCostShard toPay) {
|
||||
Card hostCard = saPayment.getHostCard();
|
||||
|
||||
String manaProduced = predictManaReplacement(saPayment, ai, toPay);
|
||||
String originalProduced = manaProduced;
|
||||
|
||||
if (originalProduced.isEmpty()) {
|
||||
return manaProduced;
|
||||
}
|
||||
|
||||
// Run triggers like Nissa
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(hostCard);
|
||||
runParams.put(AbilityKey.Player, ai); // assuming AI would only ever gives itself mana
|
||||
runParams.put(AbilityKey.AbilityMana, saPayment);
|
||||
runParams.put(AbilityKey.Produced, manaProduced);
|
||||
runParams.put(AbilityKey.Activator, ai);
|
||||
for (Trigger tr : ai.getGame().getTriggerHandler().getActiveTrigger(TriggerType.TapsForMana, runParams)) {
|
||||
SpellAbility trSA = tr.ensureAbility();
|
||||
if (trSA == null) {
|
||||
continue;
|
||||
}
|
||||
if (ApiType.Mana.equals(trSA.getApi())) {
|
||||
int pAmount = AbilityUtils.calculateAmount(trSA.getHostCard(), trSA.getParamOrDefault("Amount", "1"), trSA);
|
||||
String produced = trSA.getParam("Produced");
|
||||
if (produced.equals("Chosen")) {
|
||||
produced = MagicColor.toShortString(trSA.getHostCard().getChosenColor(trSA));
|
||||
}
|
||||
manaProduced += " " + StringUtils.repeat(produced, pAmount);
|
||||
} else if (ApiType.ManaReflected.equals(trSA.getApi())) {
|
||||
final String colorOrType = trSA.getParamOrDefault("ColorOrType", "Color");
|
||||
// currently Color or Type, Type is colors + colorless
|
||||
final String reflectProperty = trSA.getParam("ReflectProperty");
|
||||
|
||||
if (reflectProperty.equals("Produced") && !originalProduced.isEmpty()) {
|
||||
// check if a colorless shard can be paid from the trigger
|
||||
if (toPay.equals(ManaCostShard.COLORLESS) && colorOrType.equals("Type") && originalProduced.contains("C")) {
|
||||
manaProduced += " " + "C";
|
||||
} else if (originalProduced.length() == 1) {
|
||||
// if length is only one, and it either is equal C == Type
|
||||
if (colorOrType.equals("Type") || !originalProduced.equals("C")) {
|
||||
manaProduced += " " + originalProduced;
|
||||
}
|
||||
} else {
|
||||
// should it look for other shards too?
|
||||
boolean found = false;
|
||||
for (String s : originalProduced.split(" ")) {
|
||||
if (colorOrType.equals("Type") || !s.equals("C") && toPay.canBePaidWithManaOfColor(MagicColor.fromName(s))) {
|
||||
found = true;
|
||||
manaProduced += " " + s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// no good mana found? just add the first generated color
|
||||
if (!found) {
|
||||
for (String s : originalProduced.split(" ")) {
|
||||
if (colorOrType.equals("Type") || !s.equals("C")) {
|
||||
manaProduced += " " + s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return manaProduced;
|
||||
}
|
||||
|
||||
public static CardCollection getManaSourcesToPayCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
|
||||
CardCollection manaSources = new CardCollection();
|
||||
|
||||
@@ -575,7 +306,7 @@ public class ComputerUtilMana {
|
||||
// select which abilities may be used for each shard
|
||||
Multimap<ManaCostShard, SpellAbility> sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
|
||||
|
||||
sortManaAbilities(sourcesForShards, sa);
|
||||
sortManaAbilities(sourcesForShards);
|
||||
|
||||
ManaCostShard toPay;
|
||||
// Loop over mana needed
|
||||
@@ -589,30 +320,30 @@ public class ComputerUtilMana {
|
||||
|
||||
SpellAbility saPayment = chooseManaAbility(cost, sa, ai, toPay, saList, true);
|
||||
if (saPayment == null) {
|
||||
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
|
||||
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2)) {
|
||||
if (!toPay.isPhyrexian() || !ai.canPayLife(2)) {
|
||||
break; // cannot pay
|
||||
}
|
||||
|
||||
if (toPay.isPhyrexian()) {
|
||||
cost.payPhyrexian();
|
||||
} else if (lifeInsteadOfBlack) {
|
||||
cost.decreaseShard(ManaCostShard.BLACK, 1);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
manaSources.add(saPayment.getHostCard());
|
||||
setExpressColorChoice(sa, ai, cost, toPay, saPayment);
|
||||
|
||||
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
||||
|
||||
String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment);
|
||||
manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced);
|
||||
//System.out.println(manaProduced);
|
||||
payMultipleMana(cost, manaProduced, ai);
|
||||
|
||||
// remove from available lists
|
||||
Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard()));
|
||||
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
|
||||
while (itSa.hasNext()) {
|
||||
SpellAbility srcSa = itSa.next();
|
||||
if (srcSa.getHostCard().equals(saPayment.getHostCard())) {
|
||||
itSa.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleOfferingsAI(sa, true, cost.isPaid());
|
||||
@@ -624,19 +355,9 @@ public class ComputerUtilMana {
|
||||
|
||||
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, boolean checkPlayable) {
|
||||
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
|
||||
List<Mana> manaSpentToPay = test ? new ArrayList<>() : sa.getPayingMana();
|
||||
List<Mana> manaSpentToPay = test ? new ArrayList<Mana>() : sa.getPayingMana();
|
||||
boolean purePhyrexian = cost.containsOnlyPhyrexianMana();
|
||||
int testEnergyPool = ai.getCounters(CounterEnumType.ENERGY);
|
||||
|
||||
boolean ignoreColor = false, ignoreType = false;
|
||||
StaticAbility mayPlay = sa.getMayPlay();
|
||||
if (mayPlay != null) {
|
||||
if (mayPlay.hasParam("MayPlayIgnoreColor")) {
|
||||
ignoreColor = true;
|
||||
} else if (mayPlay.hasParam("MayPlayIgnoreType")) {
|
||||
ignoreType = true;
|
||||
}
|
||||
}
|
||||
int testEnergyPool = ai.getCounters(CounterType.ENERGY);
|
||||
|
||||
List<SpellAbility> paymentList = Lists.newArrayList();
|
||||
|
||||
@@ -646,8 +367,7 @@ public class ComputerUtilMana {
|
||||
|
||||
boolean hasConverge = sa.getHostCard().hasConverge();
|
||||
ListMultimap<ManaCostShard, SpellAbility> sourcesForShards = getSourcesForShards(cost, sa, ai, test,
|
||||
checkPlayable, manaSpentToPay, hasConverge, ignoreColor, ignoreType);
|
||||
|
||||
checkPlayable, manaSpentToPay, hasConverge);
|
||||
if (sourcesForShards == null && !purePhyrexian) {
|
||||
return false; // no mana abilities to use for paying
|
||||
}
|
||||
@@ -658,41 +378,13 @@ public class ComputerUtilMana {
|
||||
|
||||
// Loop over mana needed
|
||||
while (!cost.isPaid()) {
|
||||
while (!cost.isPaid() && !manapool.isEmpty()) {
|
||||
boolean found = false;
|
||||
|
||||
// Apply the color/type conversion matrix if necessary
|
||||
final CostPayment pay = new CostPayment(sa.getPayCosts(), sa);
|
||||
if (ignoreType) {
|
||||
AbilityUtils.applyManaColorConversion(pay, MagicColor.Constant.ANY_TYPE_CONVERSION);
|
||||
} else if (ignoreColor) {
|
||||
AbilityUtils.applyManaColorConversion(pay, MagicColor.Constant.ANY_COLOR_CONVERSION);
|
||||
}
|
||||
manapool.applyCardMatrix(pay);
|
||||
|
||||
for (byte color : MagicColor.WUBRGC) {
|
||||
if (manapool.tryPayCostWithColor(color, sa, cost)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cost.isPaid()) {
|
||||
break;
|
||||
}
|
||||
toPay = getNextShardToPay(cost);
|
||||
|
||||
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
|
||||
|
||||
Collection<SpellAbility> saList = null;
|
||||
if (hasConverge &&
|
||||
(toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) {
|
||||
final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION;
|
||||
for (final byte b : ColorSet.fromMask(unpaidColors)) {
|
||||
// try and pay other colors for converge
|
||||
for (final byte b : ColorSet.fromMask(unpaidColors)) { // try and pay other colors for converge
|
||||
final ManaCostShard shard = ManaCostShard.valueOf(b);
|
||||
saList = sourcesForShards.get(shard);
|
||||
if (saList != null && !saList.isEmpty()) {
|
||||
@@ -700,8 +392,7 @@ public class ComputerUtilMana {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (saList == null || saList.isEmpty()) {
|
||||
// failed to converge, revert to paying generic
|
||||
if (saList == null || saList.isEmpty()) { // failed to converge, revert to paying generic
|
||||
saList = sourcesForShards.get(toPay);
|
||||
hasConverge = false;
|
||||
}
|
||||
@@ -721,7 +412,7 @@ public class ComputerUtilMana {
|
||||
SpellAbility saPayment = saList.isEmpty() ? null : chooseManaAbility(cost, sa, ai, toPay, saList, checkPlayable || !test);
|
||||
|
||||
if (saPayment != null && ComputerUtilCost.isSacrificeSelfCost(saPayment.getPayCosts())) {
|
||||
if (sa.getTargets() != null && sa.getTargets().contains(saPayment.getHostCard())) {
|
||||
if (sa.getTargets() != null && sa.getTargets().isTargeting(saPayment.getHostCard())) {
|
||||
saExcludeList.add(saPayment); // not a good idea to sac a card that you're targeting with the SA you're paying for
|
||||
continue;
|
||||
}
|
||||
@@ -739,8 +430,7 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
if (saPayment == null) {
|
||||
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2)
|
||||
|| (ai.getLife() <= 2 && !ai.cantLoseForZeroOrLessLife())) {
|
||||
if (!toPay.isPhyrexian() || !ai.canPayLife(2) || (ai.getLife() <= 2 && !ai.cantLoseForZeroOrLessLife())) {
|
||||
break; // cannot pay
|
||||
}
|
||||
|
||||
@@ -755,12 +445,7 @@ public class ComputerUtilMana {
|
||||
}
|
||||
}
|
||||
|
||||
if (toPay.isPhyrexian()) {
|
||||
cost.payPhyrexian();
|
||||
} else if (lifeInsteadOfBlack) {
|
||||
cost.decreaseShard(ManaCostShard.BLACK, 1);
|
||||
}
|
||||
|
||||
if (!test) {
|
||||
ai.payLife(2, sa.getHostCard());
|
||||
}
|
||||
@@ -781,25 +466,32 @@ public class ComputerUtilMana {
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: if we're ignoring color or type, assume that the color/type of the mana produced will fit the case
|
||||
// for the purpose of testing (since adding appropriate sources for shards in this particular case is handled
|
||||
// inside getSourcesForShards)
|
||||
// This is hacky and may be prone to bugs, so better implementation ideas are highly welcome.
|
||||
String manaProduced = ignoreColor || ignoreType ? MagicColor.toShortString(toPay.getColorMask())
|
||||
: predictManafromSpellAbility(saPayment, ai, toPay);
|
||||
|
||||
// System.out.println(manaProduced);
|
||||
String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment);
|
||||
manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced);
|
||||
//System.out.println(manaProduced);
|
||||
payMultipleMana(cost, manaProduced, ai);
|
||||
|
||||
// remove from available lists
|
||||
Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard()));
|
||||
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
|
||||
while (itSa.hasNext()) {
|
||||
SpellAbility srcSa = itSa.next();
|
||||
if (srcSa.getHostCard().equals(saPayment.getHostCard())) {
|
||||
itSa.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (saPayment.getPayCosts() != null) {
|
||||
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
|
||||
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) {
|
||||
saList.remove(saPayment);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.err.println("Ability " + saPayment + " from " + saPayment.getHostCard() + " had NULL as payCost");
|
||||
saPayment.getHostCard().tap();
|
||||
}
|
||||
|
||||
ai.getGame().getStack().addAndUnfreeze(saPayment);
|
||||
// subtract mana from mana pool
|
||||
@@ -808,9 +500,15 @@ public class ComputerUtilMana {
|
||||
// no need to remove abilities from resource map,
|
||||
// once their costs are paid and consume resources, they can not be used again
|
||||
|
||||
if (hasConverge) {
|
||||
// hack to prevent converge re-using sources
|
||||
Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard()));
|
||||
if (hasConverge) { // hack to prevent converge re-using sources
|
||||
// remove from available lists
|
||||
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
|
||||
while (itSa.hasNext()) {
|
||||
SpellAbility srcSa = itSa.next();
|
||||
if (srcSa.getHostCard().equals(saPayment.getHostCard())) {
|
||||
itSa.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -823,6 +521,15 @@ public class ComputerUtilMana {
|
||||
// extraMana, sa.getHostCard(), sa.toUnsuppressedString(), StringUtils.join(paymentPlan, "\n\t"));
|
||||
// }
|
||||
|
||||
// See if it's possible to pay with something that was left in the mana pool in corner cases,
|
||||
// e.g. Gemstone Caverns with a Luck counter on it generating colored mana (which fails to be
|
||||
// processed correctly on a per-ability basis, leaving floating mana in the pool)
|
||||
if (!cost.isPaid() && !manapool.isEmpty()) {
|
||||
for (byte color : MagicColor.WUBRGC) {
|
||||
manapool.tryPayCostWithColor(color, sa, cost);
|
||||
}
|
||||
}
|
||||
|
||||
// The cost is still unpaid, so refund the mana and report
|
||||
if (!cost.isPaid()) {
|
||||
refundMana(manaSpentToPay, ai, sa);
|
||||
@@ -836,6 +543,13 @@ 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) {
|
||||
refundMana(manaSpentToPay, ai, sa);
|
||||
resetPayment(paymentList);
|
||||
@@ -850,31 +564,16 @@ public class ComputerUtilMana {
|
||||
}
|
||||
}
|
||||
|
||||
private static void addAllSourcesForMagicColorRange(final ListMultimap<Integer, SpellAbility> manaAbilityMap, final ListMultimap<ManaCostShard, SpellAbility> sourcesForShards, final byte[] range) {
|
||||
for (final byte b : range) {
|
||||
final ManaCostShard shard = ManaCostShard.valueOf(b);
|
||||
if (!sourcesForShards.containsKey(shard)) {
|
||||
for (final byte c : range) {
|
||||
for (SpellAbility saMana : manaAbilityMap.get((int) c)) {
|
||||
if (!sourcesForShards.get(shard).contains(saMana)) {
|
||||
sourcesForShards.get(shard).add(saMana);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mapping between the required mana shards and the available spell abilities to pay for them
|
||||
*/
|
||||
private static ListMultimap<ManaCostShard, SpellAbility> getSourcesForShards(final ManaCostBeingPaid cost,
|
||||
final SpellAbility sa, final Player ai, final boolean test, final boolean checkPlayable,
|
||||
List<Mana> manaSpentToPay, final boolean hasConverge, final boolean ignoreColor, final boolean ignoreType) {
|
||||
List<Mana> manaSpentToPay, final boolean hasConverge) {
|
||||
// arrange all mana abilities by color produced.
|
||||
final ListMultimap<Integer, SpellAbility> manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, checkPlayable);
|
||||
if (manaAbilityMap.isEmpty()) {
|
||||
// no mana abilities, bailing out
|
||||
if (manaAbilityMap.isEmpty()) { // no mana abilities, bailing out
|
||||
refundMana(manaSpentToPay, ai, sa);
|
||||
handleOfferingsAI(sa, test, cost.isPaid());
|
||||
return null;
|
||||
@@ -885,29 +584,20 @@ public class ComputerUtilMana {
|
||||
|
||||
// select which abilities may be used for each shard
|
||||
ListMultimap<ManaCostShard, SpellAbility> sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
|
||||
if (hasConverge) {
|
||||
// add extra colors for paying converge
|
||||
if (hasConverge) { // add extra colors for paying converge
|
||||
final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION;
|
||||
for (final byte b : ColorSet.fromMask(unpaidColors)) {
|
||||
final ManaCostShard shard = ManaCostShard.valueOf(b);
|
||||
if (!sourcesForShards.containsKey(shard)) {
|
||||
if (ai.getManaPool().canPayForShardWithColor(shard, b)) {
|
||||
for (SpellAbility saMana : manaAbilityMap.get((int)b)) {
|
||||
sourcesForShards.get(shard).add(saMana);
|
||||
sourcesForShards.get(shard).add(sourcesForShards.get(shard).size(), saMana);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add all other types/colors if the specific type/color doesn't matter
|
||||
if (ignoreType) {
|
||||
addAllSourcesForMagicColorRange(manaAbilityMap, sourcesForShards, MagicColor.WUBRGC);
|
||||
} else if (ignoreColor) {
|
||||
addAllSourcesForMagicColorRange(manaAbilityMap, sourcesForShards, MagicColor.WUBRG);
|
||||
}
|
||||
|
||||
sortManaAbilities(sourcesForShards, sa);
|
||||
sortManaAbilities(sourcesForShards);
|
||||
if (DEBUG_MANA_PAYMENT) {
|
||||
System.out.println("DEBUG_MANA_PAYMENT: sourcesForShards = " + sourcesForShards);
|
||||
}
|
||||
@@ -1021,7 +711,7 @@ public class ComputerUtilMana {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsSpellAndShardRestrictions(saBeingPaidFor, shard, thisMana.getColor())) {
|
||||
if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsManaRestrictions(saBeingPaidFor)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1117,10 +807,11 @@ public class ComputerUtilMana {
|
||||
if (checkCosts) {
|
||||
// Check if AI can still play this mana ability
|
||||
ma.setActivatingPlayer(ai);
|
||||
// if the AI can't pay the additional costs skip the mana ability
|
||||
if (ma.getPayCosts() != null) { // if the AI can't pay the additional costs skip the mana ability
|
||||
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (sourceCard.isTapped()) {
|
||||
return false;
|
||||
} else if (ma.getRestrictions() != null && ma.getRestrictions().isInstantSpeed()) {
|
||||
@@ -1174,12 +865,10 @@ public class ComputerUtilMana {
|
||||
|
||||
// For combat tricks, always obey mana reservation
|
||||
if (curPhase == PhaseType.COMBAT_DECLARE_BLOCKERS || curPhase == PhaseType.CLEANUP) {
|
||||
if (!(ai.getGame().getPhaseHandler().isPlayerTurn(ai))) {
|
||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
|
||||
} else if (!(ai.getGame().getPhaseHandler().isPlayerTurn(ai)) && (curPhase == PhaseType.COMBAT_DECLARE_BLOCKERS || curPhase == PhaseType.CLEANUP)) {
|
||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
|
||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
|
||||
}
|
||||
else
|
||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
|
||||
} else {
|
||||
if ((AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK)) ||
|
||||
(AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK))) {
|
||||
@@ -1268,7 +957,7 @@ public class ComputerUtilMana {
|
||||
choice = abMana.getExpressChoice();
|
||||
abMana.clearExpressChoice();
|
||||
byte colorMask = ManaAtom.fromName(choice);
|
||||
if (manaAb.canProduce(choice) && satisfiesColorChoice(abMana, choiceString, choice) && testCost.isAnyPartPayableWith(colorMask, ai.getManaPool())) {
|
||||
if (abMana.canProduce(choice, manaAb) && testCost.isAnyPartPayableWith(colorMask, ai.getManaPool())) {
|
||||
choiceString.append(choice);
|
||||
payMultipleMana(testCost, choice, ai);
|
||||
continue;
|
||||
@@ -1278,7 +967,7 @@ public class ComputerUtilMana {
|
||||
if (!testCost.isPaid()) {
|
||||
// Loop over combo colors
|
||||
for (String color : comboColors) {
|
||||
if (satisfiesColorChoice(abMana, choiceString, choice) && testCost.isAnyPartPayableWith(ManaAtom.fromName(color), ai.getManaPool())) {
|
||||
if (testCost.isAnyPartPayableWith(ManaAtom.fromName(color), ai.getManaPool())) {
|
||||
payMultipleMana(testCost, color, ai);
|
||||
if (nMana != 1) {
|
||||
choiceString.append(" ");
|
||||
@@ -1293,18 +982,14 @@ public class ComputerUtilMana {
|
||||
}
|
||||
}
|
||||
// check if combo mana can produce most common color in hand
|
||||
String commonColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Hand));
|
||||
if (!commonColor.isEmpty() && satisfiesColorChoice(abMana, choiceString, MagicColor.toShortString(commonColor)) && abMana.getComboColors().contains(MagicColor.toShortString(commonColor))) {
|
||||
String commonColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(
|
||||
ZoneType.Hand));
|
||||
if (!commonColor.isEmpty() && abMana.getComboColors().contains(MagicColor.toShortString(commonColor))) {
|
||||
choice = MagicColor.toShortString(commonColor);
|
||||
}
|
||||
else {
|
||||
// default to first available color
|
||||
for (String c : comboColors) {
|
||||
if (satisfiesColorChoice(abMana, choiceString, c)) {
|
||||
choice = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// default to first color
|
||||
choice = comboColors[0];
|
||||
}
|
||||
if (nMana != 1) {
|
||||
choiceString.append(" ");
|
||||
@@ -1319,10 +1004,6 @@ public class ComputerUtilMana {
|
||||
abMana.setExpressChoice(choiceString.toString());
|
||||
}
|
||||
|
||||
private static boolean satisfiesColorChoice(AbilityManaPart abMana, StringBuilder choices, String choice) {
|
||||
return !abMana.getOrigProduced().contains("Different") || !choices.toString().contains(choice);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* payMultipleMana.
|
||||
@@ -1412,7 +1093,7 @@ public class ComputerUtilMana {
|
||||
* @param extraMana extraMana
|
||||
* @return ManaCost
|
||||
*/
|
||||
public static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) {
|
||||
static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) {
|
||||
Card card = sa.getHostCard();
|
||||
ZoneType castFromBackup = null;
|
||||
if (test && sa.isSpell()) {
|
||||
@@ -1431,19 +1112,32 @@ public class ComputerUtilMana {
|
||||
ManaCostBeingPaid cost = new ManaCostBeingPaid(mana, restriction);
|
||||
|
||||
// Tack xMana Payments into mana here if X is a set value
|
||||
if (cost.getXcounter() > 0 || extraMana > 0) {
|
||||
if (sa.getPayCosts() != null && (cost.getXcounter() > 0 || extraMana > 0)) {
|
||||
int manaToAdd = 0;
|
||||
if (test && extraMana > 0) {
|
||||
final int multiplicator = Math.max(cost.getXcounter(), 1);
|
||||
manaToAdd = extraMana * multiplicator;
|
||||
} else {
|
||||
manaToAdd = AbilityUtils.calculateAmount(card, "X", sa) * cost.getXcounter();
|
||||
// For Count$xPaid set PayX in the AFs then use that here
|
||||
// Else calculate it as appropriate.
|
||||
final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
|
||||
if (!sa.getSVar(xSvar).isEmpty() || card.hasSVar(xSvar)) {
|
||||
if (xSvar.equals("PayX") && card.hasSVar(xSvar)) {
|
||||
// X SVar may end up being an empty string when copying a spell with no cost (e.g. Jhoira Avatar)
|
||||
String xValue = card.getSVar(xSvar);
|
||||
manaToAdd = xValue.isEmpty() ? 0 : Integer.parseInt(card.getSVar(xSvar)) * cost.getXcounter(); // X
|
||||
} else {
|
||||
manaToAdd = AbilityUtils.calculateAmount(card, xSvar, sa) * cost.getXcounter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cost.increaseShard(ManaCostShard.parseNonGeneric(sa.getParamOrDefault("XColor", "1")), manaToAdd);
|
||||
String manaXColor = sa.getParam("XColor");
|
||||
ManaCostShard shardToGrow = ManaCostShard.parseNonGeneric(manaXColor == null ? "1" : manaXColor);
|
||||
cost.increaseShard(shardToGrow, manaToAdd);
|
||||
|
||||
if (!test) {
|
||||
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
||||
card.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1492,7 +1186,7 @@ public class ComputerUtilMana {
|
||||
for (SpellAbility ma : src.getManaAbilities()) {
|
||||
ma.setActivatingPlayer(p);
|
||||
if (!checkPlayable || ma.canPlay()) {
|
||||
int costsToActivate = ma.getPayCosts().getCostMana() != null ? ma.getPayCosts().getCostMana().convertAmount() : 0;
|
||||
int costsToActivate = ma.getPayCosts() != null && ma.getPayCosts().getCostMana() != null ? ma.getPayCosts().getCostMana().convertAmount() : 0;
|
||||
int producedMana = ma.getParamOrDefault("Produced", "").split(" ").length;
|
||||
int producedAmount = AbilityUtils.calculateAmount(src, ma.getParamOrDefault("Amount", "1"), ma);
|
||||
|
||||
@@ -1555,16 +1249,6 @@ public class ComputerUtilMana {
|
||||
// 3. Use lands that produce any color many
|
||||
// 4. all other sources (creature, costs, drawback, etc.)
|
||||
for (Card card : manaSources) {
|
||||
// exclude creature sources that will tap as a part of an attack declaration
|
||||
if (card.isCreature()) {
|
||||
if (card.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai)) {
|
||||
Combat combat = card.getGame().getCombat();
|
||||
if (combat.getAttackers().indexOf(card) != -1 && !card.hasKeyword(Keyword.VIGILANCE)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (card.isCreature() || card.isEnchanted()) {
|
||||
otherManaSources.add(card);
|
||||
continue; // don't use creatures before other permanents
|
||||
@@ -1647,6 +1331,19 @@ public class ComputerUtilMana {
|
||||
final ListMultimap<Integer, SpellAbility> manaMap = ArrayListMultimap.create();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
List<ReplacementEffect> replacementEffects = new ArrayList<ReplacementEffect>();
|
||||
for (final Player p : game.getPlayers()) {
|
||||
for (final Card crd : p.getAllCards()) {
|
||||
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
|
||||
if (replacementEffect.requirementsCheck(game)
|
||||
&& replacementEffect.getMapParams().containsKey("ManaReplacement")
|
||||
&& replacementEffect.zonesCheck(game.getZoneOf(crd))) {
|
||||
replacementEffects.add(replacementEffect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop over all current available mana sources
|
||||
for (final Card sourceCard : getAvailableManaSources(ai, checkPlayable)) {
|
||||
if (DEBUG_MANA_PAYMENT) {
|
||||
@@ -1676,80 +1373,49 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
manaMap.get(ManaAtom.GENERIC).add(m); // add to generic source list
|
||||
|
||||
SpellAbility tail = m;
|
||||
while (tail != null) {
|
||||
AbilityManaPart mp = m.getManaPart();
|
||||
if (mp != null && tail.metConditions()) {
|
||||
// TODO Replacement Check currently doesn't work for reflected colors
|
||||
|
||||
// setup produce mana replacement effects
|
||||
String origin = mp.getOrigProduced();
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.newMap();
|
||||
repParams.put(AbilityKey.Mana, origin);
|
||||
repParams.put(AbilityKey.Affected, sourceCard);
|
||||
repParams.put(AbilityKey.Player, ai);
|
||||
repParams.put(AbilityKey.AbilityMana, m); // RootAbility
|
||||
final Map<String, Object> repParams = new HashMap<>();
|
||||
repParams.put("Event", "ProduceMana");
|
||||
repParams.put("Mana", mp.getOrigProduced());
|
||||
repParams.put("Affected", sourceCard);
|
||||
repParams.put("Player", ai);
|
||||
repParams.put("AbilityMana", m);
|
||||
|
||||
List<ReplacementEffect> reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other);
|
||||
for (final ReplacementEffect replacementEffect : replacementEffects) {
|
||||
if (replacementEffect.canReplace(repParams)) {
|
||||
Card crd = replacementEffect.getHostCard();
|
||||
String repType = crd.getSVar(replacementEffect.getMapParams().get("ManaReplacement"));
|
||||
if (repType.contains("Chosen")) {
|
||||
repType = TextUtil.fastReplace(repType, "Chosen", MagicColor.toShortString(crd.getChosenColor()));
|
||||
}
|
||||
mp.setManaReplaceType(repType);
|
||||
}
|
||||
}
|
||||
|
||||
if (reList.isEmpty()) {
|
||||
Set<String> reflectedColors = CardUtil.getReflectableManaColors(m);
|
||||
// find possible colors
|
||||
for (byte color : MagicColor.WUBRG) {
|
||||
if (tail.canThisProduce(MagicColor.toShortString(color)) || reflectedColors.contains(MagicColor.toLongString(color))) {
|
||||
manaMap.put((int)color, m);
|
||||
if (mp.canProduce("W", m) || reflectedColors.contains(MagicColor.Constant.WHITE)) {
|
||||
manaMap.get(ManaAtom.WHITE).add(m);
|
||||
}
|
||||
if (mp.canProduce("U", m) || reflectedColors.contains(MagicColor.Constant.BLUE)) {
|
||||
manaMap.get(ManaAtom.BLUE).add(m);
|
||||
}
|
||||
if (m.canThisProduce("C") || reflectedColors.contains(MagicColor.Constant.COLORLESS)) {
|
||||
manaMap.put(ManaAtom.COLORLESS, m);
|
||||
if (mp.canProduce("B", m) || reflectedColors.contains(MagicColor.Constant.BLACK)) {
|
||||
manaMap.get(ManaAtom.BLACK).add(m);
|
||||
}
|
||||
} else {
|
||||
// try to guess the color the mana gets replaced to
|
||||
for (ReplacementEffect re : reList) {
|
||||
SpellAbility o = re.getOverridingAbility();
|
||||
String replaced = origin;
|
||||
if (o == null || o.getApi() != ApiType.ReplaceMana) {
|
||||
continue;
|
||||
if (mp.canProduce("R", m) || reflectedColors.contains(MagicColor.Constant.RED)) {
|
||||
manaMap.get(ManaAtom.RED).add(m);
|
||||
}
|
||||
if (o.hasParam("ReplaceMana")) {
|
||||
replaced = o.getParam("ReplaceMana");
|
||||
} else if (o.hasParam("ReplaceType")) {
|
||||
String color = o.getParam("ReplaceType");
|
||||
for (byte c : MagicColor.WUBRGC) {
|
||||
String s = MagicColor.toShortString(c);
|
||||
replaced = replaced.replace(s, color);
|
||||
if (mp.canProduce("G", m) || reflectedColors.contains(MagicColor.Constant.GREEN)) {
|
||||
manaMap.get(ManaAtom.GREEN).add(m);
|
||||
}
|
||||
} else if (o.hasParam("ReplaceColor")) {
|
||||
String color = o.getParam("ReplaceColor");
|
||||
if (o.hasParam("ReplaceOnly")) {
|
||||
replaced = replaced.replace(o.getParam("ReplaceOnly"), color);
|
||||
} else {
|
||||
for (byte c : MagicColor.WUBRG) {
|
||||
String s = MagicColor.toShortString(c);
|
||||
replaced = replaced.replace(s, color);
|
||||
if (mp.canProduce("C", m) || reflectedColors.contains(MagicColor.Constant.COLORLESS)) {
|
||||
manaMap.get(ManaAtom.COLORLESS).add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (byte color : MagicColor.WUBRG) {
|
||||
if ("Any".equals(replaced) || replaced.contains(MagicColor.toShortString(color))) {
|
||||
manaMap.put((int)color, m);
|
||||
}
|
||||
}
|
||||
|
||||
if (replaced.contains("C")) {
|
||||
manaMap.put(ManaAtom.COLORLESS, m);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
tail = tail.getSubAbility();
|
||||
}
|
||||
|
||||
if (m.getHostCard().isSnow()) {
|
||||
manaMap.put(ManaAtom.IS_SNOW, m);
|
||||
if (mp.isSnow()) {
|
||||
manaMap.get(ManaAtom.IS_SNOW).add(m);
|
||||
}
|
||||
if (DEBUG_MANA_PAYMENT) {
|
||||
System.out.println("DEBUG_MANA_PAYMENT: groupSourcesByManaColor manaMap = " + manaMap);
|
||||
@@ -1871,7 +1537,7 @@ public class ComputerUtilMana {
|
||||
* @return map between creatures and shards to convoke
|
||||
*/
|
||||
public static Map<Card, ManaCostShard> getConvokeOrImproviseFromList(final ManaCost cost, List<Card> list, boolean improvise) {
|
||||
final Map<Card, ManaCostShard> convoke = new HashMap<>();
|
||||
final Map<Card, ManaCostShard> convoke = new HashMap<Card, ManaCostShard>();
|
||||
Card convoked = null;
|
||||
if (!improvise) {
|
||||
for (ManaCostShard toPay : cost) {
|
||||
@@ -1894,4 +1560,31 @@ public class ComputerUtilMana {
|
||||
}
|
||||
return convoke;
|
||||
}
|
||||
|
||||
public static int determineMaxAffordableX(Player ai, SpellAbility sa) {
|
||||
if (sa.getPayCosts() == null || 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.google.common.base.Function;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.CostPayEnergy;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
@@ -242,11 +242,11 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
&& "+X".equals(sa.getParam("NumDef"))
|
||||
&& !sa.usesTargeting()
|
||||
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
|
||||
if (sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||
// Electrostatic Pummeler, can be expanded for similar cards
|
||||
int initPower = getEffectivePower(sa.getHostCard());
|
||||
int pumpedPower = initPower;
|
||||
int energy = sa.getHostCard().getController().getCounters(CounterEnumType.ENERGY);
|
||||
int energy = sa.getHostCard().getController().getCounters(CounterType.ENERGY);
|
||||
if (energy > 0) {
|
||||
int numActivations = energy / 3;
|
||||
for (int i = 0; i < numActivations; i++) {
|
||||
|
||||
@@ -1,39 +1,19 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.effects.DetachedCardEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCloneStates;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.combat.Combat;
|
||||
@@ -44,7 +24,6 @@ import forge.game.mana.ManaPool;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilityManaPart;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.PlayerZone;
|
||||
@@ -53,9 +32,16 @@ import forge.item.IPaperCard;
|
||||
import forge.item.PaperCard;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public abstract class GameState {
|
||||
private static final Map<ZoneType, String> ZONES = new HashMap<>();
|
||||
private static final Map<ZoneType, String> ZONES = new HashMap<ZoneType, String>();
|
||||
static {
|
||||
ZONES.put(ZoneType.Battlefield, "battlefield");
|
||||
ZONES.put(ZoneType.Hand, "hand");
|
||||
@@ -80,21 +66,18 @@ public abstract class GameState {
|
||||
|
||||
private boolean puzzleCreatorState = false;
|
||||
|
||||
private final Map<ZoneType, String> humanCardTexts = new EnumMap<>(ZoneType.class);
|
||||
private final Map<ZoneType, String> aiCardTexts = new EnumMap<>(ZoneType.class);
|
||||
private final Map<ZoneType, String> humanCardTexts = new EnumMap<ZoneType, String>(ZoneType.class);
|
||||
private final Map<ZoneType, String> aiCardTexts = new EnumMap<ZoneType, String>(ZoneType.class);
|
||||
|
||||
private final Map<Integer, Card> idToCard = new HashMap<>();
|
||||
private final Map<Card, Integer> cardToAttachId = new HashMap<>();
|
||||
private final Map<Card, Integer> cardToEnchantPlayerId = new HashMap<>();
|
||||
private final Map<Card, Integer> markedDamage = new HashMap<>();
|
||||
private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>();
|
||||
private final Map<Card, CardCollection> cardToChosenCards = new HashMap<>();
|
||||
private final Map<Card, List<String>> cardToChosenTypes = new HashMap<>();
|
||||
private final Map<Card, String> cardToChosenType = 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>> cardToMergedCards = 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, Card> cardAttackMap = new HashMap<>();
|
||||
|
||||
@@ -113,13 +96,8 @@ public abstract class GameState {
|
||||
private String precastHuman = null;
|
||||
private String precastAI = null;
|
||||
|
||||
private String putOnStackHuman = null;
|
||||
private String putOnStackAI = null;
|
||||
|
||||
private int turn = 1;
|
||||
|
||||
private boolean removeSummoningSickness = false;
|
||||
|
||||
// Targeting for precast spells in a game state (mostly used by Puzzle Mode game states)
|
||||
private final int TARGET_NONE = -1; // untargeted spell (e.g. Joraga Invocation)
|
||||
private final int TARGET_HUMAN = -2;
|
||||
@@ -233,10 +211,6 @@ public abstract class GameState {
|
||||
// Remember the IDs of imprinted cards
|
||||
cardsReferencedByID.add(i);
|
||||
}
|
||||
for (Card i : card.getChosenCards()) {
|
||||
// Remember the IDs of chosen cards
|
||||
cardsReferencedByID.add(i);
|
||||
}
|
||||
if (game.getCombat() != null && game.getCombat().isAttacking(card)) {
|
||||
// Remember the IDs of attacked planeswalkers
|
||||
GameEntity def = game.getCombat().getDefenderByAttacker(card);
|
||||
@@ -259,7 +233,7 @@ public abstract class GameState {
|
||||
if (card instanceof DetachedCardEffect) {
|
||||
continue;
|
||||
}
|
||||
addCard(zone, card.getController() == ai ? aiCardTexts : humanCardTexts, card);
|
||||
addCard(zone, card.getOwner() == ai ? aiCardTexts : humanCardTexts, card);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,19 +244,13 @@ public abstract class GameState {
|
||||
newText.append(";");
|
||||
}
|
||||
if (c.isToken()) {
|
||||
newText.append("t:").append(new TokenInfo(c).toString());
|
||||
newText.append("t:" + new TokenInfo(c).toString());
|
||||
} else {
|
||||
if (c.getPaperCard() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c.getMergedCards().isEmpty()) {
|
||||
// we have to go by the current top card name here
|
||||
newText.append(c.getTopMergedCard().getPaperCard().getName());
|
||||
} else {
|
||||
newText.append(c.getPaperCard().getName());
|
||||
}
|
||||
}
|
||||
if (c.isCommander()) {
|
||||
newText.append("|IsCommander");
|
||||
}
|
||||
@@ -292,10 +260,6 @@ public abstract class GameState {
|
||||
}
|
||||
|
||||
if (zoneType == ZoneType.Battlefield) {
|
||||
if (c.getOwner() != c.getController()) {
|
||||
// TODO: Handle more than 2-player games.
|
||||
newText.append("|Owner:" + (c.getOwner().isAI() ? "AI" : "Human"));
|
||||
}
|
||||
if (c.isTapped()) {
|
||||
newText.append("|Tapped");
|
||||
}
|
||||
@@ -323,8 +287,6 @@ public abstract class GameState {
|
||||
newText.append("|Flipped");
|
||||
} else if (c.getCurrentStateName().equals(CardStateName.Meld)) {
|
||||
newText.append("|Meld");
|
||||
} else if (c.getCurrentStateName().equals(CardStateName.Modal)) {
|
||||
newText.append("|Modal");
|
||||
}
|
||||
if (c.isAttachedToEntity()) {
|
||||
newText.append("|AttachedTo:").append(c.getEntityAttachedTo().getId());
|
||||
@@ -340,33 +302,15 @@ public abstract class GameState {
|
||||
newText.append("|Damage:").append(c.getDamage());
|
||||
}
|
||||
|
||||
SpellAbility first = c.getFirstSpellAbility();
|
||||
if (first != null) {
|
||||
if (first.hasChosenColor()) {
|
||||
newText.append("|ChosenColor:").append(TextUtil.join(first.getChosenColors(), ","));
|
||||
if (!c.getChosenColor().isEmpty()) {
|
||||
newText.append("|ChosenColor:").append(TextUtil.join(c.getChosenColors(), ","));
|
||||
}
|
||||
if (first.hasChosenType()) {
|
||||
newText.append("|ChosenType:").append(TextUtil.join(first.getChosenType(), ","));
|
||||
if (!c.getChosenType().isEmpty()) {
|
||||
newText.append("|ChosenType:").append(c.getChosenType());
|
||||
}
|
||||
}
|
||||
|
||||
if (!c.getNamedCard().isEmpty()) {
|
||||
newText.append("|NamedCard:").append(c.getNamedCard());
|
||||
}
|
||||
if (!c.getNamedCard2().isEmpty()) {
|
||||
newText.append("|NamedCard2:").append(c.getNamedCard2());
|
||||
}
|
||||
|
||||
List<String> chosenCardIds = Lists.newArrayList();
|
||||
for (Object obj : c.getChosenCards()) {
|
||||
if (obj instanceof Card) {
|
||||
int id = ((Card)obj).getId();
|
||||
chosenCardIds.add(String.valueOf(id));
|
||||
}
|
||||
}
|
||||
if (!chosenCardIds.isEmpty()) {
|
||||
newText.append("|ChosenCards:").append(TextUtil.join(chosenCardIds, ","));
|
||||
}
|
||||
|
||||
List<String> rememberedCardIds = Lists.newArrayList();
|
||||
for (Object obj : c.getRemembered()) {
|
||||
@@ -387,17 +331,6 @@ public abstract class GameState {
|
||||
if (!imprintedCardIds.isEmpty()) {
|
||||
newText.append("|Imprinting:").append(TextUtil.join(imprintedCardIds, ","));
|
||||
}
|
||||
|
||||
if (!c.getMergedCards().isEmpty()) {
|
||||
List<String> mergedCardNames = new ArrayList<>();
|
||||
for (Card merged : c.getMergedCards()) {
|
||||
if (c.getTopMergedCard() == merged) {
|
||||
continue;
|
||||
}
|
||||
mergedCardNames.add(merged.getPaperCard().getName().replace(",", "^"));
|
||||
}
|
||||
newText.append("|MergedCards:").append(TextUtil.join(mergedCardNames, ","));
|
||||
}
|
||||
}
|
||||
|
||||
if (zoneType == ZoneType.Exile) {
|
||||
@@ -407,12 +340,6 @@ public abstract class GameState {
|
||||
if (c.isFaceDown()) {
|
||||
newText.append("|FaceDown"); // Exiled face down
|
||||
}
|
||||
if (c.isAdventureCard() && c.getZone().is(ZoneType.Exile)) {
|
||||
// TODO: this will basically default all exiled cards with Adventure to being "On Adventure".
|
||||
// Need to figure out a better way to detect if it's actually on adventure.
|
||||
newText.append("|OnAdventure");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (zoneType == ZoneType.Battlefield || zoneType == ZoneType.Exile) {
|
||||
@@ -429,7 +356,7 @@ public abstract class GameState {
|
||||
newText.append("|Attacking");
|
||||
GameEntity def = c.getGame().getCombat().getDefenderByAttacker(c);
|
||||
if (def instanceof Card) {
|
||||
newText.append(":").append(def.getId());
|
||||
newText.append(":" + def.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -505,10 +432,6 @@ public abstract class GameState {
|
||||
turn = Integer.parseInt(categoryValue);
|
||||
}
|
||||
|
||||
else if (categoryName.equals("removesummoningsickness")) {
|
||||
removeSummoningSickness = categoryValue.equalsIgnoreCase("true");
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("life")) {
|
||||
if (isHuman)
|
||||
humanLife = Integer.parseInt(categoryValue);
|
||||
@@ -590,13 +513,6 @@ public abstract class GameState {
|
||||
precastAI = categoryValue;
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("putonstack")) {
|
||||
if (isHuman)
|
||||
putOnStackHuman = categoryValue;
|
||||
else
|
||||
putOnStackAI = categoryValue;
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("manapool")) {
|
||||
if (isHuman)
|
||||
humanManaPool = categoryValue;
|
||||
@@ -634,12 +550,9 @@ public abstract class GameState {
|
||||
cardToEnchantPlayerId.clear();
|
||||
cardToRememberedId.clear();
|
||||
cardToExiledWithId.clear();
|
||||
cardToImprintedId.clear();
|
||||
markedDamage.clear();
|
||||
cardToChosenClrs.clear();
|
||||
cardToChosenCards.clear();
|
||||
cardToChosenTypes.clear();
|
||||
cardToMergedCards.clear();
|
||||
cardToChosenType.clear();
|
||||
cardToScript.clear();
|
||||
cardAttackMap.clear();
|
||||
|
||||
@@ -672,24 +585,18 @@ public abstract class GameState {
|
||||
handleCardAttachments();
|
||||
handleChosenEntities();
|
||||
handleRememberedEntities();
|
||||
handleMergedCards();
|
||||
handleScriptExecution(game);
|
||||
handlePrecastSpells(game);
|
||||
handleMarkedDamage();
|
||||
|
||||
game.getTriggerHandler().setSuppressAllTriggers(false);
|
||||
|
||||
// SAs added to stack cause triggers to fire, as if the relevant SAs were cast
|
||||
handleAddSAsToStack(game);
|
||||
|
||||
// Combat only works for 1v1 matches for now (which are the only matches dev mode supports anyway)
|
||||
// Note: triggers may fire during combat declarations ("whenever X attacks, ...", etc.)
|
||||
if (newPhase == PhaseType.COMBAT_DECLARE_ATTACKERS || newPhase == PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
||||
boolean toDeclareBlockers = newPhase == PhaseType.COMBAT_DECLARE_BLOCKERS;
|
||||
if (newPlayerTurn != null) {
|
||||
handleCombat(game, newPlayerTurn, newPlayerTurn.getSingleOpponent(), toDeclareBlockers);
|
||||
}
|
||||
}
|
||||
|
||||
game.getStack().setResolving(false);
|
||||
|
||||
@@ -698,25 +605,19 @@ public abstract class GameState {
|
||||
game.getPhaseHandler().devAdvanceToPhase(advPhase);
|
||||
}
|
||||
|
||||
if (removeSummoningSickness) {
|
||||
for (Card card : game.getCardsInGame()) {
|
||||
card.setSickness(false);
|
||||
}
|
||||
}
|
||||
|
||||
game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
|
||||
}
|
||||
|
||||
private String processManaPool(ManaPool manaPool) {
|
||||
StringBuilder mana = new StringBuilder();
|
||||
String mana = "";
|
||||
for (final byte c : MagicColor.WUBRGC) {
|
||||
int amount = manaPool.getAmountOfColor(c);
|
||||
for (int i = 0; i < amount; i++) {
|
||||
mana.append(MagicColor.toShortString(c)).append(" ");
|
||||
mana += MagicColor.toShortString(c) + " ";
|
||||
}
|
||||
}
|
||||
|
||||
return mana.toString().trim();
|
||||
return mana.trim();
|
||||
}
|
||||
|
||||
private void updateManaPool(Player p, String manaDef, boolean clearPool, boolean persistent) {
|
||||
@@ -733,7 +634,7 @@ public abstract class GameState {
|
||||
if (persistent) {
|
||||
produced.put("PersistentMana", "True");
|
||||
}
|
||||
final AbilityManaPart abMana = new AbilityManaPart(dummy, null, produced);
|
||||
final AbilityManaPart abMana = new AbilityManaPart(dummy, produced);
|
||||
game.getAction().invoke(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -774,10 +675,10 @@ public abstract class GameState {
|
||||
for (final Card c : combat.getAttackers()) {
|
||||
attackedTarget.add(combat.getDefenderByAttacker(c));
|
||||
}
|
||||
final Map<AbilityKey, Object> runParams = Maps.newEnumMap(AbilityKey.class);
|
||||
runParams.put(AbilityKey.Attackers, combat.getAttackers());
|
||||
runParams.put(AbilityKey.AttackingPlayer, combat.getAttackingPlayer());
|
||||
runParams.put(AbilityKey.AttackedTarget, attackedTarget);
|
||||
final Map<String, Object> runParams = Maps.newHashMap();
|
||||
runParams.put("Attackers", combat.getAttackers());
|
||||
runParams.put("AttackingPlayer", combat.getAttackingPlayer());
|
||||
runParams.put("AttackedTarget", attackedTarget);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false);
|
||||
}
|
||||
|
||||
@@ -827,8 +728,6 @@ public abstract class GameState {
|
||||
|
||||
Card exiledWith = idToCard.get(Integer.parseInt(id));
|
||||
c.setExiledWith(exiledWith);
|
||||
c.setExiledBy(exiledWith.getController());
|
||||
exiledWith.addExiledWith(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -863,12 +762,6 @@ public abstract class GameState {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberTargets")) {
|
||||
for (final GameObject o : sa.getTargets()) {
|
||||
sa.getHostCard().addRemembered(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleScriptExecution(final Game game) {
|
||||
@@ -881,9 +774,6 @@ public abstract class GameState {
|
||||
}
|
||||
|
||||
private void executeScript(Game game, Card c, String sPtr) {
|
||||
executeScript(game, c, sPtr, false);
|
||||
}
|
||||
private void executeScript(Game game, Card c, String sPtr, boolean putOnStack) {
|
||||
int tgtID = TARGET_NONE;
|
||||
if (sPtr.contains("->")) {
|
||||
String tgtDef = sPtr.substring(sPtr.lastIndexOf("->") + 2);
|
||||
@@ -956,14 +846,9 @@ public abstract class GameState {
|
||||
}
|
||||
}
|
||||
|
||||
if (sa != null) {
|
||||
sa.setActivatingPlayer(c.getController());
|
||||
}
|
||||
handleScriptedTargetingForSA(game, sa, tgtID);
|
||||
|
||||
if (putOnStack) {
|
||||
game.getStack().addAndUnfreeze(sa);
|
||||
} else {
|
||||
sa.resolve();
|
||||
|
||||
// resolve subabilities
|
||||
@@ -973,7 +858,6 @@ public abstract class GameState {
|
||||
subSa = subSa.getSubAbility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePrecastSpells(final Game game) {
|
||||
Player human = game.getPlayers().get(0);
|
||||
@@ -993,28 +877,7 @@ public abstract class GameState {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAddSAsToStack(final Game game) {
|
||||
Player human = game.getPlayers().get(0);
|
||||
Player ai = game.getPlayers().get(1);
|
||||
|
||||
if (putOnStackHuman != null) {
|
||||
String[] spellList = TextUtil.split(putOnStackHuman, ';');
|
||||
for (String spell : spellList) {
|
||||
precastSpellFromCard(spell, human, game, true);
|
||||
}
|
||||
}
|
||||
if (putOnStackAI != null) {
|
||||
String[] spellList = TextUtil.split(putOnStackAI, ';');
|
||||
for (String spell : spellList) {
|
||||
precastSpellFromCard(spell, ai, game, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void precastSpellFromCard(String spellDef, final Player activator, final Game game) {
|
||||
precastSpellFromCard(spellDef, activator, game, false);
|
||||
}
|
||||
private void precastSpellFromCard(String spellDef, final Player activator, final Game game, final boolean putOnStack) {
|
||||
int tgtID = TARGET_NONE;
|
||||
String scriptID = "";
|
||||
|
||||
@@ -1028,17 +891,6 @@ public abstract class GameState {
|
||||
spellDef = spellDef.substring(0, spellDef.indexOf("->")).trim();
|
||||
}
|
||||
|
||||
Card c = null;
|
||||
|
||||
if (StringUtils.isNumeric(spellDef)) {
|
||||
// Precast from a specific host
|
||||
c = idToCard.get(Integer.parseInt(spellDef));
|
||||
if (c == null) {
|
||||
System.err.println("ERROR: Could not find a card with ID " + spellDef + " to precast!");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Precast from a card by name
|
||||
PaperCard pc = StaticData.instance().getCommonCards().getCard(spellDef);
|
||||
|
||||
if (pc == null) {
|
||||
@@ -1046,13 +898,11 @@ public abstract class GameState {
|
||||
return;
|
||||
}
|
||||
|
||||
c = Card.fromPaperCard(pc, activator);
|
||||
}
|
||||
|
||||
Card c = Card.fromPaperCard(pc, activator);
|
||||
SpellAbility sa = null;
|
||||
|
||||
if (!scriptID.isEmpty()) {
|
||||
executeScript(game, c, scriptID, putOnStack);
|
||||
executeScript(game, c, scriptID);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1061,12 +911,8 @@ public abstract class GameState {
|
||||
|
||||
handleScriptedTargetingForSA(game, sa, tgtID);
|
||||
|
||||
if (putOnStack) {
|
||||
game.getStack().addAndUnfreeze(sa);
|
||||
} else {
|
||||
sa.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMarkedDamage() {
|
||||
for (Entry<Card, Integer> entry : markedDamage.entrySet()) {
|
||||
@@ -1087,13 +933,13 @@ public abstract class GameState {
|
||||
Card c = entry.getKey();
|
||||
List<String> colors = entry.getValue();
|
||||
|
||||
c.setChosenColors(colors, c.getFirstSpellAbility());
|
||||
c.setChosenColors(colors);
|
||||
}
|
||||
|
||||
// Chosen type
|
||||
for (Entry<Card, List<String>> entry : cardToChosenTypes.entrySet()) {
|
||||
for (Entry<Card, String> entry : cardToChosenType.entrySet()) {
|
||||
Card c = entry.getKey();
|
||||
c.setChosenType(entry.getValue(), c.getFirstSpellAbility());
|
||||
c.setChosenType(entry.getValue());
|
||||
}
|
||||
|
||||
// Named card
|
||||
@@ -1101,18 +947,6 @@ public abstract class GameState {
|
||||
Card c = entry.getKey();
|
||||
c.setNamedCard(entry.getValue());
|
||||
}
|
||||
|
||||
// Named card 2
|
||||
for (Entry<Card,String> entry : cardToNamedCard2.entrySet()) {
|
||||
Card c = entry.getKey();
|
||||
c.setNamedCard2(entry.getValue());
|
||||
}
|
||||
|
||||
// Chosen cards
|
||||
for (Entry<Card, CardCollection> entry : cardToChosenCards.entrySet()) {
|
||||
Card c = entry.getKey();
|
||||
c.setChosenCards(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCardAttachments() {
|
||||
@@ -1142,61 +976,12 @@ public abstract class GameState {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMergedCards() {
|
||||
for(Entry<Card, List<String>> entry : cardToMergedCards.entrySet()) {
|
||||
Card mergedTo = entry.getKey();
|
||||
for(String mergedCardName : entry.getValue()) {
|
||||
Card c;
|
||||
PaperCard pc = StaticData.instance().getCommonCards().getCard(mergedCardName. replace("^", ","));
|
||||
if (pc == null) {
|
||||
System.err.println("ERROR: Tried to create a non-existent card named " + mergedCardName + " (as a merged card) when loading game state!");
|
||||
continue;
|
||||
}
|
||||
|
||||
c = Card.fromPaperCard(pc, mergedTo.getOwner());
|
||||
emulateMergeViaMutate(mergedTo, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void emulateMergeViaMutate(Card top, Card bottom) {
|
||||
if (top == null || bottom == null) {
|
||||
System.err.println("ERROR: Tried to call emulateMergeViaMutate with a null card!");
|
||||
return;
|
||||
}
|
||||
|
||||
Game game = top.getGame();
|
||||
|
||||
bottom.setMergedToCard(top);
|
||||
if (!top.hasMergedCard()) {
|
||||
top.addMergedCard(top);
|
||||
}
|
||||
top.addMergedCard(bottom);
|
||||
|
||||
if (top.getMutatedTimestamp() != -1) {
|
||||
top.removeCloneState(top.getMutatedTimestamp());
|
||||
}
|
||||
|
||||
final Long ts = game.getNextTimestamp();
|
||||
top.setMutatedTimestamp(ts);
|
||||
if (top.getCurrentStateName() != CardStateName.FaceDown) {
|
||||
final CardCloneStates mutatedStates = CardFactory.getMutatedCloneStates(top, null/*FIXME*/);
|
||||
top.addCloneState(mutatedStates, ts);
|
||||
}
|
||||
bottom.setTapped(top.isTapped());
|
||||
bottom.setFlipped(top.isFlipped());
|
||||
top.setTimesMutated(top.getTimesMutated() + 1);
|
||||
top.updateTokenView();
|
||||
|
||||
// TODO: Merged commanders aren't supported yet
|
||||
}
|
||||
|
||||
private void applyCountersToGameEntity(GameEntity entity, String counterString) {
|
||||
entity.setCounters(Maps.newHashMap());
|
||||
entity.setCounters(Maps.<CounterType, Integer>newEnumMap(CounterType.class));
|
||||
String[] allCounterStrings = counterString.split(",");
|
||||
for (final String counterPair : allCounterStrings) {
|
||||
String[] pair = counterPair.split("=", 2);
|
||||
entity.addCounter(CounterType.getType(pair[0]), Integer.parseInt(pair[1]), null, false, false, null);
|
||||
entity.addCounter(CounterType.valueOf(pair[0]), Integer.parseInt(pair[1]), null, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1209,7 +994,7 @@ public abstract class GameState {
|
||||
p.getZone(zt).removeAllCards(true);
|
||||
}
|
||||
|
||||
Map<ZoneType, CardCollectionView> playerCards = new EnumMap<>(ZoneType.class);
|
||||
Map<ZoneType, CardCollectionView> playerCards = new EnumMap<ZoneType, CardCollectionView>(ZoneType.class);
|
||||
for (Entry<ZoneType, String> kv : cardTexts.entrySet()) {
|
||||
String value = kv.getValue();
|
||||
playerCards.put(kv.getKey(), processCardsForZone(value.isEmpty() ? new String[0] : value.split(";"), p));
|
||||
@@ -1222,7 +1007,7 @@ public abstract class GameState {
|
||||
for (Entry<ZoneType, CardCollectionView> kv : playerCards.entrySet()) {
|
||||
PlayerZone zone = p.getZone(kv.getKey());
|
||||
if (kv.getKey() == ZoneType.Battlefield) {
|
||||
List<Card> cards = new ArrayList<>();
|
||||
List<Card> cards = new ArrayList<Card>();
|
||||
for (final Card c : kv.getValue()) {
|
||||
if (c.isToken()) {
|
||||
cards.add(c);
|
||||
@@ -1238,7 +1023,7 @@ public abstract class GameState {
|
||||
Map<CounterType, Integer> counters = c.getCounters();
|
||||
// Note: Not clearCounters() since we want to keep the counters
|
||||
// var as-is.
|
||||
c.setCounters(Maps.newHashMap());
|
||||
c.setCounters(Maps.<CounterType, Integer>newEnumMap(CounterType.class));
|
||||
if (c.isAura()) {
|
||||
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
|
||||
// (will be overridden later, so the actual value shouldn't matter)
|
||||
@@ -1262,9 +1047,7 @@ public abstract class GameState {
|
||||
zone.setCards(kv.getValue());
|
||||
}
|
||||
}
|
||||
for (Card cmd : p.getCommanders()) {
|
||||
p.getZone(ZoneType.Command).add(Player.createCommanderEffect(p.getGame(), cmd));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1325,7 +1108,7 @@ public abstract class GameState {
|
||||
} else if (info.startsWith("SummonSick")) {
|
||||
c.setSickness(true);
|
||||
} else if (info.startsWith("FaceDown")) {
|
||||
c.turnFaceDown(true);
|
||||
c.setState(CardStateName.FaceDown, true);
|
||||
if (info.endsWith("Manifested")) {
|
||||
c.setManifested(true);
|
||||
}
|
||||
@@ -1335,23 +1118,11 @@ public abstract class GameState {
|
||||
c.setState(CardStateName.Flipped, true);
|
||||
} else if (info.startsWith("Meld")) {
|
||||
c.setState(CardStateName.Meld, true);
|
||||
} else if (info.startsWith("Modal")) {
|
||||
c.setState(CardStateName.Modal, true);
|
||||
}
|
||||
else if (info.startsWith("OnAdventure")) {
|
||||
String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
|
||||
AbilitySub saAdventure = (AbilitySub)AbilityFactory.getAbility(abAdventure, c);
|
||||
StringBuilder sbPlay = new StringBuilder();
|
||||
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
|
||||
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
|
||||
saAdventure.setSVar("Play", sbPlay.toString());
|
||||
saAdventure.setActivatingPlayer(c.getOwner());
|
||||
saAdventure.resolve();
|
||||
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")) {
|
||||
// TODO: This doesn't seem to properly restore the ability to play the commander. Why?
|
||||
c.setCommander(true);
|
||||
player.setCommanders(Lists.newArrayList(c));
|
||||
player.getZone(ZoneType.Command).add(Player.createCommanderEffect(player.getGame(), c));
|
||||
} else if (info.startsWith("Id:")) {
|
||||
int id = Integer.parseInt(info.substring(3));
|
||||
idToCard.put(id, c);
|
||||
@@ -1362,14 +1133,6 @@ public abstract class GameState {
|
||||
// TODO: improve this for game states with more than two players
|
||||
String tgt = info.substring(info.indexOf(':') + 1);
|
||||
cardToEnchantPlayerId.put(c, tgt.equalsIgnoreCase("AI") ? TARGET_AI : TARGET_HUMAN);
|
||||
} else if (info.startsWith("Owner:")) {
|
||||
// TODO: improve this for game states with more than two players
|
||||
Player human = player.getGame().getPlayers().get(0);
|
||||
Player ai = player.getGame().getPlayers().get(1);
|
||||
String owner = info.substring(info.indexOf(':') + 1);
|
||||
Player controller = c.getController();
|
||||
c.setOwner(owner.equalsIgnoreCase("AI") ? ai : human);
|
||||
c.setController(controller, c.getGame().getNextTimestamp());
|
||||
} else if (info.startsWith("Ability:")) {
|
||||
String abString = info.substring(info.indexOf(':') + 1).toLowerCase();
|
||||
c.addSpellAbility(AbilityFactory.getAbility(abilityString.get(abString), c));
|
||||
@@ -1379,21 +1142,9 @@ public abstract class GameState {
|
||||
} else if (info.startsWith("ChosenColor:")) {
|
||||
cardToChosenClrs.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
|
||||
} else if (info.startsWith("ChosenType:")) {
|
||||
cardToChosenTypes.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
|
||||
} else if (info.startsWith("ChosenCards:")) {
|
||||
CardCollection chosen = new CardCollection();
|
||||
String[] idlist = info.substring(info.indexOf(':') + 1).split(",");
|
||||
for (String id : idlist) {
|
||||
chosen.add(idToCard.get(Integer.parseInt(id)));
|
||||
}
|
||||
cardToChosenCards.put(c, chosen);
|
||||
} else if (info.startsWith("MergedCards:")) {
|
||||
List<String> cardNames = Arrays.asList(info.substring(info.indexOf(':') + 1).split(","));
|
||||
cardToMergedCards.put(c, cardNames);
|
||||
cardToChosenType.put(c, info.substring(info.indexOf(':') + 1));
|
||||
} else if (info.startsWith("NamedCard:")) {
|
||||
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:")) {
|
||||
cardToScript.put(c, info.substring(info.indexOf(':') + 1));
|
||||
} else if (info.startsWith("RememberedCards:")) {
|
||||
|
||||
@@ -1,23 +1,11 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import forge.LobbyPlayer;
|
||||
import forge.ai.ability.ProtectAi;
|
||||
import forge.card.CardStateName;
|
||||
@@ -33,43 +21,18 @@ import forge.game.GameObject;
|
||||
import forge.game.GameType;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CardView;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostAdjustment;
|
||||
import forge.game.cost.CostExile;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.mana.Mana;
|
||||
import forge.game.mana.ManaConversionMatrix;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.DelayedReveal;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerController;
|
||||
import forge.game.player.PlayerView;
|
||||
import forge.game.player.*;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.spellability.Ability;
|
||||
import forge.game.spellability.AbilityStatic;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.LandAbility;
|
||||
import forge.game.spellability.OptionalCost;
|
||||
import forge.game.spellability.OptionalCostValue;
|
||||
import forge.game.spellability.Spell;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.trigger.WrappedAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.PaperCard;
|
||||
@@ -78,6 +41,13 @@ import forge.util.ITriggerEvent;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
* A prototype for player controller class
|
||||
@@ -106,8 +76,10 @@ public class PlayerControllerAi extends PlayerController {
|
||||
if (abilities.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return abilities.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
public AiController getAi() {
|
||||
return brains;
|
||||
@@ -119,7 +91,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PaperCard> sideboard(Deck deck, GameType gameType, String message) {
|
||||
public List<PaperCard> sideboard(Deck deck, GameType gameType) {
|
||||
// AI does not know how to sideboard
|
||||
return null;
|
||||
}
|
||||
@@ -130,7 +102,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer announceRequirements(SpellAbility ability, String announce) {
|
||||
public Integer announceRequirements(SpellAbility ability, String announce, boolean allowZero) {
|
||||
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
|
||||
if (ability.getApi() != null) {
|
||||
switch (ability.getApi()) {
|
||||
@@ -172,12 +144,12 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardCollectionView chooseCardsForEffect(CardCollectionView sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional, Map<String, Object> params) {
|
||||
return brains.chooseCardsForEffect(sourceList, sa, min, max, isOptional, params);
|
||||
public CardCollectionView chooseCardsForEffect(CardCollectionView sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional) {
|
||||
return brains.chooseCardsForEffect(sourceList, sa, min, max, isOptional);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, DelayedReveal delayedReveal, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
public <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, DelayedReveal delayedReveal, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer) {
|
||||
if (delayedReveal != null) {
|
||||
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix());
|
||||
}
|
||||
@@ -185,47 +157,19 @@ public class PlayerControllerAi extends PlayerController {
|
||||
if (null == api) {
|
||||
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
||||
}
|
||||
return SpellApiToAi.Converter.get(api).chooseSingleEntity(player, sa, (FCollection<T>)optionList, isOptional, targetedPlayer, params);
|
||||
return SpellApiToAi.Converter.get(api).chooseSingleEntity(player, sa, (FCollection<T>)optionList, isOptional, targetedPlayer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends GameEntity> List<T> chooseEntitiesForEffect(
|
||||
FCollectionView<T> optionList, int min, int max, DelayedReveal delayedReveal, SpellAbility sa, String title,
|
||||
Player targetedPlayer, Map<String, Object> params) {
|
||||
if (delayedReveal != null) {
|
||||
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix());
|
||||
}
|
||||
FCollection<T> remaining = new FCollection<>(optionList);
|
||||
List<T> selecteds = new ArrayList<>();
|
||||
T selected;
|
||||
do {
|
||||
selected = chooseSingleEntityForEffect(remaining, null, sa, title, selecteds.size()>=min, targetedPlayer, params);
|
||||
if ( selected != null ) {
|
||||
remaining.remove(selected);
|
||||
selecteds.add(selected);
|
||||
}
|
||||
} while ( (selected != null ) && (selecteds.size() < max) );
|
||||
return selecteds;
|
||||
FCollectionView<T> optionList, DelayedReveal delayedReveal, SpellAbility sa, String title,
|
||||
Player targetedPlayer) {
|
||||
// this isn't used
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SpellAbility> chooseSpellAbilitiesForEffect(List<SpellAbility> spells, SpellAbility sa, String title,
|
||||
int num, Map<String, Object> params) {
|
||||
List<SpellAbility> remaining = Lists.newArrayList(spells);
|
||||
List<SpellAbility> selecteds = Lists.newArrayList();
|
||||
SpellAbility selected;
|
||||
do {
|
||||
selected = chooseSingleSpellForEffect(remaining, sa, title, params);
|
||||
if ( selected != null ) {
|
||||
remaining.remove(selected);
|
||||
selecteds.add(selected);
|
||||
}
|
||||
} while ( (selected != null ) && (selecteds.size() < num) );
|
||||
return selecteds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellForEffect(List<SpellAbility> spells, SpellAbility sa, String title,
|
||||
public SpellAbility chooseSingleSpellForEffect(java.util.List<SpellAbility> spells, SpellAbility sa, String title,
|
||||
Map<String, Object> params) {
|
||||
ApiType api = sa.getApi();
|
||||
if (null == api) {
|
||||
@@ -251,13 +195,15 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmTrigger(WrappedAbility wrapper) {
|
||||
public boolean confirmTrigger(WrappedAbility wrapper, Map<String, String> triggerParams, boolean isMandatory) {
|
||||
final SpellAbility sa = wrapper.getWrappedAbility();
|
||||
//final Trigger regtrig = wrapper.getTrigger();
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Deathmist Raptor")) {
|
||||
return true;
|
||||
}
|
||||
if (wrapper.isMandatory()) {
|
||||
if (triggerParams.containsKey("DelayedTrigger") || isMandatory) {
|
||||
//TODO: The only card with an optional delayed trigger is Shirei, Shizo's Caretaker,
|
||||
// needs to be expanded when a more difficult cards comes up
|
||||
return true;
|
||||
}
|
||||
// Store/replace target choices more properly to get this SA cleared.
|
||||
@@ -294,7 +240,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player chooseStartingPlayer(boolean isFirstgame) {
|
||||
public Player chooseStartingPlayer(boolean isFirstGame) {
|
||||
return this.player; // AI is brave :)
|
||||
}
|
||||
|
||||
@@ -391,11 +337,12 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
if (destinationZone == ZoneType.Graveyard) {
|
||||
// In presence of Volrath's Shapeshifter in deck, try to place the best creature on top of the graveyard
|
||||
if (!CardLists.filter(getGame().getCardsInGame(), new Predicate<Card>() {
|
||||
if (!CardLists.filter(game.getCardsInGame(), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
// need a custom predicate here since Volrath's Shapeshifter may have a different name OTB
|
||||
return card.getOriginalState(CardStateName.Original).getName().equals("Volrath's Shapeshifter");
|
||||
return card.getName().equals("Volrath's Shapeshifter")
|
||||
|| card.getStates().contains(CardStateName.OriginalText) && card.getState(CardStateName.OriginalText).getName().equals("Volrath's Shapeshifter");
|
||||
}
|
||||
}).isEmpty()) {
|
||||
int bestValue = 0;
|
||||
@@ -512,7 +459,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) {
|
||||
if (canSetupTargets)
|
||||
brains.doTrigger(effectSA, true); // first parameter does not matter, since return value won't be used
|
||||
ComputerUtil.playNoStack(player, effectSA, getGame());
|
||||
ComputerUtil.playNoStack(player, effectSA, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -521,7 +468,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetChoices chooseNewTargetsFor(SpellAbility ability, Predicate<GameObject> filter, boolean optional) {
|
||||
public TargetChoices chooseNewTargetsFor(SpellAbility ability) {
|
||||
// AI currently can't do this. But when it can it will need to be based on Ability API
|
||||
return null;
|
||||
}
|
||||
@@ -533,7 +480,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
|
||||
return new CardCollection(toDiscard);
|
||||
}
|
||||
return getAi().getCardsToDiscard(num, null, sa);
|
||||
return getAi().getCardsToDiscard(num, (String[])null, sa);
|
||||
}
|
||||
|
||||
|
||||
@@ -543,18 +490,20 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> chooseSomeType(String kindOfType, SpellAbility sa, int min, int max, List<String> validTypes) {
|
||||
List<String> chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), min, max, validTypes);
|
||||
if (chosen.isEmpty()) {
|
||||
return validTypes.subList(0, min);
|
||||
public String chooseSomeType(String kindOfType, SpellAbility sa, List<String> validTypes, List<String> invalidTypes, boolean isOptional) {
|
||||
String chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), invalidTypes);
|
||||
if (StringUtils.isBlank(chosen) && !validTypes.isEmpty())
|
||||
{
|
||||
chosen = validTypes.get(0);
|
||||
System.err.println("AI has no idea how to choose " + kindOfType +", defaulting to 1st element: chosen");
|
||||
}
|
||||
getGame().getAction().notifyOfValue(sa, player, chosen.toString(), player);
|
||||
game.getAction().nofityOfValue(sa, player, chosen, player);
|
||||
return chosen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer) {
|
||||
return ComputerUtil.vote(player, options, sa, votes, forPlayer);
|
||||
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes) {
|
||||
return ComputerUtil.vote(player, options, sa, votes);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -562,66 +511,15 @@ public class PlayerControllerAi extends PlayerController {
|
||||
return brains.aiShouldRun(replacementEffect, effectSA);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mulliganKeepHand(Player firstPlayer, int cardsToReturn) {
|
||||
return !ComputerUtil.wantMulligan(player, cardsToReturn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardCollectionView getCardsToMulligan(Player firstPlayer) {
|
||||
if (!ComputerUtil.wantMulligan(player, 0)) {
|
||||
if (!ComputerUtil.wantMulligan(player)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return player.getCardsIn(ZoneType.Hand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardCollectionView londonMulliganReturnCards(final Player mulliganingPlayer, int cardsToReturn) {
|
||||
// TODO This is better than it was before, but still suboptimal (but fast).
|
||||
// Maybe score a bunch of hands based on projected hand size and return the "duds"
|
||||
CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
||||
int numLandsDesired = (mulliganingPlayer.getStartingHandSize() - cardsToReturn) / 2;
|
||||
|
||||
CardCollection toReturn = new CardCollection();
|
||||
for (int i = 0; i < cardsToReturn; i++) {
|
||||
hand.removeAll(toReturn);
|
||||
|
||||
CardCollection landsInHand = CardLists.filter(hand, Presets.LANDS);
|
||||
int numLandsInHand = landsInHand.size() - CardLists.filter(toReturn, Presets.LANDS).size();
|
||||
|
||||
// If we're flooding with lands, get rid of the worst land we have
|
||||
if (numLandsInHand > 0 && numLandsInHand > numLandsDesired) {
|
||||
CardCollection producingLands = CardLists.filter(landsInHand, Presets.LANDS_PRODUCING_MANA);
|
||||
CardCollection nonProducingLands = CardLists.filter(landsInHand, Predicates.not(Presets.LANDS_PRODUCING_MANA));
|
||||
Card worstLand = nonProducingLands.isEmpty() ? ComputerUtilCard.getWorstLand(producingLands)
|
||||
: ComputerUtilCard.getWorstLand(nonProducingLands);
|
||||
toReturn.add(worstLand);
|
||||
continue;
|
||||
}
|
||||
|
||||
// See if we'd scry something to the bottom in this situation. If we want to, probably get rid of it.
|
||||
CardCollection scryBottom = new CardCollection();
|
||||
for (Card c : hand) {
|
||||
// Lands are evaluated separately above, factoring in the number of cards to be returned to the library
|
||||
if (!c.isLand() && !toReturn.contains(c) && !willPutCardOnTop(c)) {
|
||||
scryBottom.add(c);
|
||||
}
|
||||
}
|
||||
if (!scryBottom.isEmpty()) {
|
||||
CardLists.sortByCmcDesc(scryBottom);
|
||||
toReturn.add(scryBottom.getFirst()); // assume the max CMC one is worse since we're not guaranteed to have lands for it
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we don't want to scry anything to the bottom, remove the worst card that we have in order to satisfy
|
||||
// the requirement
|
||||
toReturn.add(ComputerUtilCard.getWorstAI(hand));
|
||||
}
|
||||
|
||||
return CardCollection.getView(toReturn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declareAttackers(Player attacker, Combat combat) {
|
||||
brains.declareAttackers(attacker, combat);
|
||||
@@ -638,22 +536,20 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean playChosenSpellAbility(SpellAbility sa) {
|
||||
public void playChosenSpellAbility(SpellAbility sa) {
|
||||
// System.out.println("Playing sa: " + sa);
|
||||
if (sa instanceof LandAbility) {
|
||||
if (sa.canPlay()) {
|
||||
sa.resolve();
|
||||
getGame().updateLastStateForCard(sa.getHostCard());
|
||||
}
|
||||
} else {
|
||||
ComputerUtil.handlePlayingSpellAbility(player, sa, getGame());
|
||||
ComputerUtil.handlePlayingSpellAbility(player, sa, game);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard) {
|
||||
return brains.getCardsToDiscard(numDiscard, null, null);
|
||||
return brains.getCardsToDiscard(numDiscard, (String[])null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -686,7 +582,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
// - End of hack for Exile a card from library Cumulative Upkeep -
|
||||
|
||||
if (ComputerUtilCost.canPayCost(ability, c.getController())) {
|
||||
ComputerUtil.playNoStack(c.getController(), ability, getGame());
|
||||
ComputerUtil.playNoStack(c.getController(), ability, game);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -710,7 +606,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
||||
}
|
||||
return SpellApiToAi.Converter.get(api).chooseNumber(player, sa, min, max, params);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int chooseNumber(SpellAbility sa, String title, List<Integer> options, Player relatedPlayer) {
|
||||
@@ -770,15 +666,14 @@ public class PlayerControllerAi extends PlayerController {
|
||||
return true;
|
||||
} else {
|
||||
Card rem = (Card) source.getFirstRemembered();
|
||||
if (!rem.isInZone(ZoneType.Battlefield)) {
|
||||
if (!rem.getZone().is(ZoneType.Battlefield)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "BetterTgtThanRemembered":
|
||||
if (source.getRememberedCount() > 0) {
|
||||
Card rem = (Card) source.getFirstRemembered();
|
||||
if (!rem.isInZone(ZoneType.Battlefield)) {
|
||||
if (!rem.getZone().is(ZoneType.Battlefield)) {
|
||||
return true;
|
||||
}
|
||||
for (Card c : source.getController().getCreaturesInPlay()) {
|
||||
@@ -788,7 +683,6 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -796,7 +690,6 @@ public class PlayerControllerAi extends PlayerController {
|
||||
return defaultVal != null && defaultVal.booleanValue();
|
||||
case UntapTimeVault: return false; // TODO Should AI skip his turn for time vault?
|
||||
case LeftOrRight: return brains.chooseDirection(sa);
|
||||
case OddsOrEvens: return brains.chooseEvenOdd(sa); // false is Odd, true is Even
|
||||
default:
|
||||
return MyRandom.getRandom().nextBoolean();
|
||||
}
|
||||
@@ -827,8 +720,8 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> possible, int min, int num, boolean allowRepeat) {
|
||||
List<AbilitySub> result = brains.chooseModeForAbility(sa, possible, min, num, allowRepeat);
|
||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat) {
|
||||
List<AbilitySub> result = brains.chooseModeForAbility(sa, min, num, allowRepeat);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
@@ -859,9 +752,6 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
@Override
|
||||
public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
|
||||
if (colors.countColors() < 2) {
|
||||
return Iterables.getFirst(colors, MagicColor.WHITE);
|
||||
}
|
||||
// You may switch on sa.getApi() here and use sa.getParam("AILogic")
|
||||
CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
|
||||
if (sa.getApi() == ApiType.Mana) {
|
||||
@@ -911,16 +801,16 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplacementEffect chooseSingleReplacementEffect(String prompt, List<ReplacementEffect> possibleReplacers) {
|
||||
return brains.chooseSingleReplacementEffect(possibleReplacers);
|
||||
public ReplacementEffect chooseSingleReplacementEffect(String prompt, List<ReplacementEffect> possibleReplacers, Map<String, Object> runParams) {
|
||||
return brains.chooseSingleReplacementEffect(possibleReplacers, runParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseProtectionType(String string, SpellAbility sa, List<String> choices) {
|
||||
String choice = choices.get(0);
|
||||
SpellAbility hostsa = null; //for Protect sub-ability
|
||||
if (getGame().stack.size() > 1) {
|
||||
for (SpellAbilityStackInstance si : getGame().getStack()) {
|
||||
if (game.stack.size() > 1) {
|
||||
for (SpellAbilityStackInstance si : game.getStack()) {
|
||||
SpellAbility spell = si.getSpellAbility(true);
|
||||
if (sa != spell && sa.getHostCard() != spell.getHostCard()) {
|
||||
String s = ProtectAi.toProtectFrom(spell.getHostCard(), sa);
|
||||
@@ -931,10 +821,10 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
}
|
||||
}
|
||||
final Combat combat = getGame().getCombat();
|
||||
final Combat combat = game.getCombat();
|
||||
if (combat != null) {
|
||||
if (getGame().stack.size() == 1) {
|
||||
SpellAbility topstack = getGame().stack.peekAbility();
|
||||
if (game.stack.size() == 1) {
|
||||
SpellAbility topstack = game.stack.peekAbility();
|
||||
if (topstack.getSubAbility() == sa) {
|
||||
hostsa = topstack;
|
||||
}
|
||||
@@ -957,7 +847,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
}
|
||||
}
|
||||
final PhaseHandler ph = getGame().getPhaseHandler();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
if (ph.getPlayerTurn() == sa.getActivatingPlayer() && ph.getPhase() == PhaseType.MAIN1 && sa.getTargetCard() != null) {
|
||||
AiAttackController aiAtk = new AiAttackController(sa.getActivatingPlayer(), sa.getTargetCard());
|
||||
String s = aiAtk.toProtectAttacker(sa);
|
||||
@@ -972,7 +862,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
list.addAll(opp.getCreaturesInPlay());
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
list = CardLists.filterControlledBy(getGame().getCardsInGame(), player.getOpponents());
|
||||
list = CardLists.filterControlledBy(game.getCardsInGame(), player.getOpponents());
|
||||
}
|
||||
if (!list.isEmpty()) {
|
||||
choice = ComputerUtilCard.getMostProminentColor(list);
|
||||
@@ -986,10 +876,8 @@ public class PlayerControllerAi extends PlayerController {
|
||||
final Card source = sa.getHostCard();
|
||||
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
|
||||
emptyAbility.setActivatingPlayer(player);
|
||||
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
||||
emptyAbility.setSVars(sa.getSVars());
|
||||
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
||||
ComputerUtil.playNoStack(player, emptyAbility, getGame()); // AI needs something to resolve to pay that cost
|
||||
ComputerUtil.playNoStack(player, emptyAbility, game); // AI needs something to resolve to pay that cost
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -998,34 +886,8 @@ public class PlayerControllerAi extends PlayerController {
|
||||
@Override
|
||||
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
|
||||
for (final SpellAbility sa : getAi().orderPlaySa(activePlayerSAs)) {
|
||||
if (sa.isTrigger()) {
|
||||
if (prepareSingleSa(sa.getHostCard(), sa, true)) {
|
||||
ComputerUtil.playStack(sa, player, getGame());
|
||||
}
|
||||
} else {
|
||||
if (sa.isCopied()) {
|
||||
if (sa.isSpell()) {
|
||||
if (!sa.getHostCard().isInZone(ZoneType.Stack)) {
|
||||
sa.setHostCard(player.getGame().getAction().moveToStack(sa.getHostCard(), sa));
|
||||
} else {
|
||||
player.getGame().getStackZone().add(sa.getHostCard());
|
||||
}
|
||||
}
|
||||
|
||||
/* FIXME: the new implementation (below) requires implementing setupNewTargets in the AI controller, among other possible changes, otherwise breaks AI
|
||||
if (sa.isMayChooseNewTargets()) {
|
||||
sa.setupNewTargets(player);
|
||||
}
|
||||
*/
|
||||
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
|
||||
if (sa.isSpell()) {
|
||||
sa.getHostCard().ceaseToExist();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// need finally add the new spell to the stack
|
||||
player.getGame().getStack().add(sa);
|
||||
if (prepareSingleSa(sa.getHostCard(),sa,true)) {
|
||||
ComputerUtil.playStack(sa, player, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1043,7 +905,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
@Override
|
||||
public void playTrigger(Card host, WrappedAbility wrapperAbility, boolean isMandatory) {
|
||||
if (prepareSingleSa(host, wrapperAbility, isMandatory)) {
|
||||
ComputerUtil.playNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility, getGame());
|
||||
ComputerUtil.playNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility, game);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1055,9 +917,9 @@ public class PlayerControllerAi extends PlayerController {
|
||||
Spell spell = (Spell) tgtSA;
|
||||
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
|
||||
if (noManaCost) {
|
||||
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
|
||||
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, game);
|
||||
} else {
|
||||
return ComputerUtil.playStack(tgtSA, player, getGame());
|
||||
return ComputerUtil.playStack(tgtSA, player, game);
|
||||
}
|
||||
} else
|
||||
return false; // didn't play spell
|
||||
@@ -1065,6 +927,11 @@ public class PlayerControllerAi extends PlayerController {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<GameEntity, CounterType> chooseProliferation(SpellAbility sa) {
|
||||
return brains.chooseProliferation(sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chooseTargetsFor(SpellAbility currentAbility) {
|
||||
return brains.doTrigger(currentAbility, true);
|
||||
@@ -1141,7 +1008,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return new HashMap<>();
|
||||
return new HashMap<Card, ManaCostShard>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1167,7 +1034,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
public String chooseCardName(SpellAbility sa, Predicate<ICardFace> cpp, String valid, String message) {
|
||||
if (sa.hasParam("AILogic")) {
|
||||
CardCollectionView aiLibrary = player.getCardsIn(ZoneType.Library);
|
||||
CardCollectionView oppLibrary = player.getWeakestOpponent().getCardsIn(ZoneType.Library);
|
||||
CardCollectionView oppLibrary = ComputerUtil.getOpponentFor(player).getCardsIn(ZoneType.Library);
|
||||
final Card source = sa.getHostCard();
|
||||
final String logic = sa.getParam("AILogic");
|
||||
|
||||
@@ -1189,21 +1056,20 @@ public class PlayerControllerAi extends PlayerController {
|
||||
} else if (logic.equals("MostProminentInHumanDeck")) {
|
||||
return ComputerUtilCard.getMostProminentCardName(oppLibrary);
|
||||
} else if (logic.equals("MostProminentCreatureInComputerDeck")) {
|
||||
CardCollectionView cards = CardLists.getValidCards(aiLibrary, "Creature", player, sa.getHostCard(), sa);
|
||||
CardCollectionView cards = CardLists.getValidCards(aiLibrary, "Creature", player, sa.getHostCard());
|
||||
return ComputerUtilCard.getMostProminentCardName(cards);
|
||||
} else if (logic.equals("BestCreatureInComputerDeck")) {
|
||||
Card bestCreature = ComputerUtilCard.getBestCreatureAI(aiLibrary);
|
||||
return bestCreature != null ? bestCreature.getName() : "Plains";
|
||||
return ComputerUtilCard.getBestCreatureAI(aiLibrary).getName();
|
||||
} else if (logic.equals("RandomInComputerDeck")) {
|
||||
return Aggregates.random(aiLibrary).getName();
|
||||
} else if (logic.equals("MostProminentSpellInComputerDeck")) {
|
||||
CardCollectionView cards = CardLists.getValidCards(aiLibrary, "Card.Instant,Card.Sorcery", player, sa.getHostCard(), sa);
|
||||
CardCollectionView cards = CardLists.getValidCards(aiLibrary, "Card.Instant,Card.Sorcery", player, sa.getHostCard());
|
||||
return ComputerUtilCard.getMostProminentCardName(cards);
|
||||
} else if (logic.equals("CursedScroll")) {
|
||||
return SpecialCardAi.CursedScroll.chooseCard(player, sa);
|
||||
}
|
||||
} else {
|
||||
CardCollectionView list = CardLists.filterControlledBy(getGame().getCardsInGame(), player.getOpponents());
|
||||
CardCollectionView list = CardLists.filterControlledBy(game.getCardsInGame(), player.getOpponents());
|
||||
list = CardLists.filter(list, Predicates.not(Presets.LANDS));
|
||||
if (!list.isEmpty()) {
|
||||
return list.get(0).getName();
|
||||
@@ -1224,7 +1090,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
@Override
|
||||
public List<Card> chooseCardsForZoneChange(
|
||||
ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList, int min, int max,
|
||||
ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList,
|
||||
DelayedReveal delayedReveal, String selectPrompt, Player decider) {
|
||||
// this isn't used
|
||||
return null;
|
||||
@@ -1284,24 +1150,12 @@ public class PlayerControllerAi extends PlayerController {
|
||||
public List<OptionalCostValue> chooseOptionalCosts(SpellAbility chosen,
|
||||
List<OptionalCostValue> optionalCostValues) {
|
||||
List<OptionalCostValue> chosenOptCosts = Lists.newArrayList();
|
||||
Cost costSoFar = chosen.getPayCosts().copy();
|
||||
Cost costSoFar = chosen.getPayCosts() != null ? chosen.getPayCosts().copy() : Cost.Zero;
|
||||
|
||||
for (OptionalCostValue opt : optionalCostValues) {
|
||||
// Choose the optional cost if it can be paid (to be improved later, check for playability and other conditions perhaps)
|
||||
Cost fullCost = opt.getCost().copy().add(costSoFar);
|
||||
SpellAbility fullCostSa = chosen.copyWithDefinedCost(fullCost);
|
||||
|
||||
// Playability check for Kicker
|
||||
if (opt.getType() == OptionalCost.Kicker1 || opt.getType() == OptionalCost.Kicker2) {
|
||||
SpellAbility kickedSaCopy = fullCostSa.copy();
|
||||
kickedSaCopy.addOptionalCost(opt.getType());
|
||||
Card copy = CardUtil.getLKICopy(chosen.getHostCard());
|
||||
copy.addOptionalCostPaid(opt.getType());
|
||||
if (ComputerUtilCard.checkNeedsToPlayReqs(copy, kickedSaCopy) != AiPlayDecision.WillPlay) {
|
||||
continue; // don't choose kickers we don't want to play
|
||||
}
|
||||
}
|
||||
|
||||
if (ComputerUtilCost.canPayCost(fullCostSa, player)) {
|
||||
chosenOptCosts.add(opt);
|
||||
costSoFar.add(opt.getCost());
|
||||
@@ -1310,47 +1164,4 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
return chosenOptCosts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmMulliganScry(Player p) {
|
||||
// Always true?
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int chooseNumberForKeywordCost(SpellAbility sa, Cost cost, KeywordInterface keyword, String prompt,
|
||||
int max) {
|
||||
// TODO: improve the logic depending on the keyword and the playability of the cost-modified SA (enough targets present etc.)
|
||||
int chosenAmount = 0;
|
||||
|
||||
Cost costSoFar = sa.getPayCosts().copy();
|
||||
|
||||
for (int i = 0; i < max; i++) {
|
||||
costSoFar.add(cost);
|
||||
SpellAbility fullCostSa = sa.copyWithDefinedCost(costSoFar);
|
||||
if (ComputerUtilCost.canPayCost(fullCostSa, player)) {
|
||||
chosenAmount++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return chosenAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardCollection chooseCardsForEffectMultiple(Map<String, CardCollection> validMap, SpellAbility sa, String title, boolean isOptional) {
|
||||
CardCollection choices = new CardCollection();
|
||||
|
||||
for (String mapKey: validMap.keySet()) {
|
||||
CardCollection cc = validMap.get(mapKey);
|
||||
cc.removeAll(choices);
|
||||
Card chosen = ComputerUtilCard.getBestAI(cc);
|
||||
if (chosen != null) {
|
||||
choices.add(chosen);
|
||||
}
|
||||
}
|
||||
|
||||
return choices;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,372 +0,0 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ability.TokenAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
/*
|
||||
* This class contains logic which is shared by several cards with different ability types (e.g. AF ChangeZone / AF Destroy)
|
||||
* Ideally, the naming scheme for methods in this class should be doXXXLogic, where XXX is the name of the logic,
|
||||
* and the signature of the method should be "public static boolean doXXXLogic(final Player ai, final SpellAbility sa),
|
||||
* possibly followed with any additional necessary parameters. These AI logic routines generally do all the work, so returning
|
||||
* true from them should indicate that the AI has made a decision and configured the spell ability (targeting, etc.) as it
|
||||
* deemed necessary.
|
||||
*/
|
||||
|
||||
public class SpecialAiLogic {
|
||||
// A logic for cards like Pongify, Crib Swap, Angelic Ascension
|
||||
public static boolean doPongifyLogic(final Player ai, final SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
Game game = source.getGame();
|
||||
PhaseHandler ph = game.getPhaseHandler();
|
||||
boolean isDestroy = ApiType.Destroy.equals(sa.getApi());
|
||||
SpellAbility tokenSA = sa.findSubAbilityByType(ApiType.Token);
|
||||
if (tokenSA == null) {
|
||||
// Used wrong AI logic?
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Card> targetable = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa);
|
||||
|
||||
CardCollection listOpp = CardLists.filterControlledBy(targetable, ai.getOpponents());
|
||||
if (isDestroy) {
|
||||
listOpp = CardLists.getNotKeyword(listOpp, Keyword.INDESTRUCTIBLE);
|
||||
// TODO add handling for cards like targeting dies
|
||||
}
|
||||
|
||||
Card choice = null;
|
||||
if (!listOpp.isEmpty()) {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(listOpp);
|
||||
// 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)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(choice);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ((!choice.isCreature() || choice.isTapped()) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && ph.isPlayerTurn(ai) // prevent surprise combatant
|
||||
|| ComputerUtilCard.evaluateCreature(choice) < 1.5 * ComputerUtilCard.evaluateCreature(token)) {
|
||||
choice = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See if we have anything we can upgrade
|
||||
if (choice == null) {
|
||||
CardCollection listOwn = CardLists.filterControlledBy(targetable, ai);
|
||||
final Card token = TokenAi.spawnToken(ai, tokenSA);
|
||||
|
||||
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
|
||||
public boolean apply(Card card) {
|
||||
return card.isCreature() && (ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| ComputerUtilCard.evaluateCreature(token) > 2 * ComputerUtilCard.evaluateCreature(card));
|
||||
}
|
||||
}));
|
||||
}
|
||||
if (bestOwnCardToUpgrade != null) {
|
||||
if (ComputerUtilCard.isUselessCreature(ai, bestOwnCardToUpgrade) || (ph.getPhase().isAfter(PhaseType.COMBAT_END) || !ph.isPlayerTurn(ai))) {
|
||||
choice = bestOwnCardToUpgrade;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (choice != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(choice);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// A logic for cards that say "Sacrifice a creature: CARDNAME gets +X/+X until EOT"
|
||||
public static boolean doAristocratLogic(final Player ai, final SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
final Card source = sa.getHostCard();
|
||||
final int numOtherCreats = Math.max(0, ai.getCreaturesInPlay().size() - 1);
|
||||
final int powerBonus = sa.hasParam("NumAtt") ? AbilityUtils.calculateAmount(source, sa.getParam("NumAtt"), sa) : 0;
|
||||
final int toughnessBonus = sa.hasParam("NumDef") ? AbilityUtils.calculateAmount(source, sa.getParam("NumDef"), sa) : 0;
|
||||
final boolean indestructible = sa.hasParam("KW") && sa.getParam("KW").contains("Indestructible");
|
||||
final int selfEval = ComputerUtilCard.evaluateCreature(source);
|
||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(source);
|
||||
|
||||
if (numOtherCreats == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to save the card from death by pumping it if it's threatened with a damage spell
|
||||
if (isThreatened && (toughnessBonus > 0 || indestructible)) {
|
||||
SpellAbility saTop = game.getStack().peekAbility();
|
||||
|
||||
if (saTop.getApi() == ApiType.DealDamage || saTop.getApi() == ApiType.DamageAll) {
|
||||
int dmg = AbilityUtils.calculateAmount(saTop.getHostCard(), saTop.getParam("NumDmg"), saTop) + source.getDamage();
|
||||
final int numCreatsToSac = indestructible ? 1 : Math.max(1, (int)Math.ceil((dmg - source.getNetToughness() + 1) / toughnessBonus));
|
||||
|
||||
if (numCreatsToSac > 1) { // probably not worth sacrificing too much
|
||||
return false;
|
||||
}
|
||||
|
||||
if (indestructible || (source.getNetToughness() <= dmg && source.getNetToughness() + toughnessBonus * numCreatsToSac > dmg)) {
|
||||
final CardCollection sacFodder = CardLists.filter(ai.getCreaturesInPlay(),
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| card.hasSVar("SacMe")
|
||||
|| ComputerUtilCard.evaluateCreature(card) < selfEval; // Maybe around 150 is OK?
|
||||
}
|
||||
}
|
||||
);
|
||||
return sacFodder.size() >= numCreatsToSac;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (combat == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (combat.isAttacking(source)) {
|
||||
if (combat.getBlockers(source).isEmpty()) {
|
||||
// Unblocked. Check if able to deal lethal, then sac'ing everything is fair game if
|
||||
// the opponent is tapped out or if we're willing to risk it (will currently risk it
|
||||
// in case it sacs less than half its creatures to deal lethal damage)
|
||||
|
||||
// TODO: also teach the AI to account for Trample, but that's trickier (needs to account fully
|
||||
// for potential damage prevention, various effects like reducing damage to 0, etc.)
|
||||
|
||||
final Player defPlayer = combat.getDefendingPlayerRelatedTo(source);
|
||||
final boolean defTappedOut = ComputerUtilMana.getAvailableManaEstimate(defPlayer) == 0;
|
||||
|
||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT); // Flesh-Eater Imp
|
||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||
|
||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||
}
|
||||
|
||||
final int numCreatsToSac = indestructible ? 1 : (lethalDmg - source.getNetCombatDamage()) / (powerBonus != 0 ? powerBonus : 1);
|
||||
|
||||
if (defTappedOut || numCreatsToSac < numOtherCreats / 2) {
|
||||
return source.getNetCombatDamage() < lethalDmg
|
||||
&& source.getNetCombatDamage() + numOtherCreats * powerBonus >= lethalDmg;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// We have already attacked. Thus, see if we have a creature to sac that is worse to lose
|
||||
// than the card we attacked with.
|
||||
final CardCollection sacTgts = CardLists.filter(ai.getCreaturesInPlay(),
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| ComputerUtilCard.evaluateCreature(card) < selfEval;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (sacTgts.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int minDefT = Aggregates.min(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetToughness);
|
||||
final int DefP = indestructible ? 0 : Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
|
||||
|
||||
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
|
||||
return source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT;
|
||||
}
|
||||
} else {
|
||||
// We can't deal lethal, check if there's any sac fodder than can be used for other circumstances
|
||||
final CardCollection sacFodder = CardLists.filter(ai.getCreaturesInPlay(),
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| card.hasSVar("SacMe")
|
||||
|| ComputerUtilCard.evaluateCreature(card) < selfEval; // Maybe around 150 is OK?
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return !sacFodder.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
// A logic for cards that say "Sacrifice a creature: put X +1/+1 counters on CARDNAME" (e.g. Falkenrath Aristocrat)
|
||||
public static boolean doAristocratWithCountersLogic(final Player ai, final SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String logic = sa.getParam("AILogic"); // should not even get here unless there's an Aristocrats logic applied
|
||||
final boolean isDeclareBlockers = ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS);
|
||||
|
||||
final int numOtherCreats = Math.max(0, ai.getCreaturesInPlay().size() - 1);
|
||||
if (numOtherCreats == 0) {
|
||||
// Cut short if there's nothing to sac at all
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the standard Aristocrats logic applies first (if in the right conditions for it)
|
||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(source);
|
||||
if (isDeclareBlockers || isThreatened) {
|
||||
if (doAristocratLogic(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if anything is to be gained from the PutCounter subability
|
||||
SpellAbility countersSa = null;
|
||||
if (sa.getSubAbility() == null || sa.getSubAbility().getApi() != ApiType.PutCounter) {
|
||||
if (sa.getApi() == ApiType.PutCounter) {
|
||||
// called directly from CountersPutAi
|
||||
countersSa = sa;
|
||||
}
|
||||
} else {
|
||||
countersSa = sa.getSubAbility();
|
||||
}
|
||||
|
||||
if (countersSa == null) {
|
||||
// Shouldn't get here if there is no PutCounter subability (wrong AI logic specified?)
|
||||
System.err.println("Warning: AILogic AristocratCounters was specified on " + source + ", but there was no PutCounter SA in chain!");
|
||||
return false;
|
||||
}
|
||||
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
final int selfEval = ComputerUtilCard.evaluateCreature(source);
|
||||
|
||||
String typeToGainCtr = "";
|
||||
if (logic.contains(".")) {
|
||||
typeToGainCtr = logic.substring(logic.indexOf(".") + 1);
|
||||
}
|
||||
CardCollection relevantCreats = typeToGainCtr.isEmpty() ? ai.getCreaturesInPlay()
|
||||
: CardLists.filter(ai.getCreaturesInPlay(), CardPredicates.isType(typeToGainCtr));
|
||||
relevantCreats.remove(source);
|
||||
if (relevantCreats.isEmpty()) {
|
||||
// No relevant creatures to sac
|
||||
return false;
|
||||
}
|
||||
|
||||
int numCtrs = AbilityUtils.calculateAmount(source, countersSa.getParam("CounterNum"), countersSa);
|
||||
|
||||
if (combat != null && combat.isAttacking(source) && isDeclareBlockers) {
|
||||
if (combat.getBlockers(source).isEmpty()) {
|
||||
// Unblocked. Check if we can deal lethal after receiving counters.
|
||||
final Player defPlayer = combat.getDefendingPlayerRelatedTo(source);
|
||||
final boolean defTappedOut = ComputerUtilMana.getAvailableManaEstimate(defPlayer) == 0;
|
||||
|
||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT);
|
||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||
|
||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||
}
|
||||
|
||||
// Check if there's anything that will die anyway that can be eaten to gain a perma-bonus
|
||||
final CardCollection forcedSacTgts = CardLists.filter(relevantCreats,
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
|
||||
|| (combat.isAttacking(card) && combat.isBlocked(card) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, card, combat));
|
||||
}
|
||||
}
|
||||
);
|
||||
if (!forcedSacTgts.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final int numCreatsToSac = Math.max(0, (lethalDmg - source.getNetCombatDamage()) / numCtrs);
|
||||
|
||||
if (defTappedOut || numCreatsToSac < relevantCreats.size() / 2) {
|
||||
return source.getNetCombatDamage() < lethalDmg
|
||||
&& source.getNetCombatDamage() + relevantCreats.size() * numCtrs >= lethalDmg;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// We have already attacked. Thus, see if we have a creature to sac that is worse to lose
|
||||
// than the card we attacked with. Since we're getting a permanent bonus, consider sacrificing
|
||||
// things that are also threatened to be destroyed anyway.
|
||||
final CardCollection sacTgts = CardLists.filter(relevantCreats,
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| ComputerUtilCard.evaluateCreature(card) < selfEval
|
||||
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (sacTgts.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean sourceCantDie = ComputerUtilCombat.attackerCantBeDestroyedInCombat(ai, source);
|
||||
final int minDefT = Aggregates.min(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetToughness);
|
||||
final int DefP = sourceCantDie ? 0 : Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
|
||||
|
||||
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
|
||||
return source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT;
|
||||
}
|
||||
} else {
|
||||
// We can't deal lethal, check if there's any sac fodder than can be used for other circumstances
|
||||
final boolean isBlocking = combat != null && combat.isBlocking(source);
|
||||
final CardCollection sacFodder = CardLists.filter(relevantCreats,
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| card.hasSVar("SacMe")
|
||||
|| (isBlocking && ComputerUtilCard.evaluateCreature(card) < selfEval)
|
||||
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return !sacFodder.isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,33 +17,19 @@
|
||||
*/
|
||||
package forge.ai;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ability.AnimateAi;
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameType;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.CostPart;
|
||||
@@ -54,7 +40,6 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityPredicates;
|
||||
import forge.game.spellability.SpellPermanent;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
@@ -64,6 +49,11 @@ import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.maps.LinkedHashMapToAmount;
|
||||
import forge.util.maps.MapToAmount;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Special logic for individual cards
|
||||
@@ -104,8 +94,12 @@ public class SpecialCardAi {
|
||||
int minCMC = isLowCMCDeck ? 3 : 4; // probably not worth wasting a lotus on a low-CMC spell (<4 CMC), except in low-CMC decks, where 3 CMC may be fine
|
||||
int paidCMC = cost.getConvertedManaCost();
|
||||
if (paidCMC < minCMC) {
|
||||
if (paidCMC == 3 && numManaSrcs < 3) {
|
||||
// if it's a CMC 3 spell and we're more than one mana source short for it, might be worth it anyway
|
||||
return paidCMC == 3 && numManaSrcs < 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -153,29 +147,6 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
// Crawling Barrens
|
||||
public static class CrawlingBarrens {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
final Combat combat = ai.getGame().getCombat();
|
||||
|
||||
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa);
|
||||
animated.addType("Creature");
|
||||
if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) {
|
||||
animated.addCounter(CounterEnumType.P1P1, 2, ai, false, null);
|
||||
}
|
||||
boolean isOppEOT = ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
|
||||
boolean isValuableAttacker = ph.is(PhaseType.MAIN1, ai) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animated);
|
||||
boolean isValuableBlocker = combat != null && combat.getDefendingPlayers().contains(ai) && ComputerUtilCard.doesSpecifiedCreatureBlock(ai, animated);
|
||||
|
||||
return isOppEOT || isValuableAttacker || isValuableBlocker;
|
||||
}
|
||||
|
||||
public static SpellAbility considerAnimating(final Player ai, final SpellAbility sa, final List<SpellAbility> options) {
|
||||
return ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) ? options.get(0) : options.get(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Cursed Scroll
|
||||
public static class CursedScroll {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
@@ -201,7 +172,7 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
return best != null ? best.getName() : "";
|
||||
return best.getName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +198,7 @@ public class SpecialCardAi {
|
||||
sa.getTargets().add(worstCreat);
|
||||
}
|
||||
|
||||
return sa.getTargets().size() > 0;
|
||||
return sa.getTargets().getNumTargeted() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +218,11 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
return ai.getLife() <= sa.getHostCard().getNetPower() && !hasUsefulBlocker;
|
||||
if (ai.getLife() <= sa.getHostCard().getNetPower() && !hasUsefulBlocker) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getSacThreshold() {
|
||||
@@ -360,7 +335,7 @@ public class SpecialCardAi {
|
||||
boolean canTrample = source.hasKeyword(Keyword.TRAMPLE);
|
||||
|
||||
if (!isBlocking && combat.getDefenderByAttacker(source) instanceof Card) {
|
||||
int loyalty = combat.getDefenderByAttacker(source).getCounters(CounterEnumType.LOYALTY);
|
||||
int loyalty = ((Card)combat.getDefenderByAttacker(source)).getCounters(CounterType.LOYALTY);
|
||||
int totalDamageToPW = 0;
|
||||
for (Card atk : (combat.getAttackersOf(combat.getDefenderByAttacker(source)))) {
|
||||
if (combat.isUnblocked(atk)) {
|
||||
@@ -436,11 +411,15 @@ public class SpecialCardAi {
|
||||
Pair<Integer, Integer> predictedPT = getPumpedPT(ai, source.getNetCombatDamage(), source.getNetToughness());
|
||||
int oppT = Aggregates.sum(potentialBlockers, CardPredicates.Accessors.fnGetNetToughness);
|
||||
|
||||
return potentialBlockers.isEmpty() || (source.hasKeyword(Keyword.TRAMPLE) && predictedPT.getLeft() - oppT >= oppLife);
|
||||
if (potentialBlockers.isEmpty() || (source.hasKeyword(Keyword.TRAMPLE) && predictedPT.getLeft() - oppT >= oppLife)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Pair<Integer, Integer> getPumpedPT(Player ai, int power, int toughness) {
|
||||
int energy = ai.getCounters(CounterEnumType.ENERGY);
|
||||
int energy = ai.getCounters(CounterType.ENERGY);
|
||||
if (energy > 0) {
|
||||
int numActivations = energy / 3;
|
||||
for (int i = 0; i < numActivations; i++) {
|
||||
@@ -522,49 +501,6 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
// Gideon Blackblade
|
||||
public static class GideonBlackblade {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
CardCollectionView otb = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isTargetableBy(sa));
|
||||
if (!otb.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestAI(otb));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static SpellAbility chooseSpellAbility(final Player ai, final SpellAbility sa, final List<SpellAbility> spells) {
|
||||
// TODO: generalize and improve this so that it acts in a more reasonable way and can potentially be used for other cards too
|
||||
List<SpellAbility> best = Lists.newArrayList();
|
||||
List<SpellAbility> possible = Lists.newArrayList();
|
||||
Card tgtCard = sa.getTargetCard();
|
||||
if (tgtCard != null) {
|
||||
for (SpellAbility sp : spells) {
|
||||
if (SpellApiToAi.Converter.get(sp.getApi()).canPlayAIWithSubs(ai, sp)) {
|
||||
best.add(sp); // these SAs are prioritized since the AI sees a reason to play them now
|
||||
}
|
||||
final List<String> keywords = sp.hasParam("KW") ? Arrays.asList(sp.getParam("KW").split(" & "))
|
||||
: Lists.newArrayList();
|
||||
for (String kw : keywords) {
|
||||
if (!tgtCard.hasKeyword(kw)) {
|
||||
if ("Indestructible".equals(kw) && ai.getOpponents().getCreaturesInPlay().isEmpty()) {
|
||||
continue; // nothing to damage or kill the creature with
|
||||
}
|
||||
possible.add(sp); // these SAs at least don't duplicate a keyword on the card
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!best.isEmpty()) {
|
||||
return Aggregates.random(best);
|
||||
} else if (!possible.isEmpty()) {
|
||||
return Aggregates.random(possible);
|
||||
} else {
|
||||
return Aggregates.random(spells); // if worst comes to worst, it's a PW +1 ability, so do at least something
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Guilty Conscience
|
||||
public static class GuiltyConscience {
|
||||
public static Card getBestAttachTarget(final Player ai, final SpellAbility sa, final List<Card> list) {
|
||||
@@ -589,7 +525,10 @@ public class SpecialCardAi {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
// Don't enchant creatures that can survive
|
||||
return c.canBeDestroyed() && c.getNetCombatDamage() >= c.getNetToughness() && !c.isEnchantedBy("Guilty Conscience");
|
||||
if (!c.canBeDestroyed() || c.getNetCombatDamage() < c.getNetToughness() || c.isEnchantedBy("Guilty Conscience")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
chosen = ComputerUtilCard.getBestCreatureAI(creatures);
|
||||
@@ -668,7 +607,10 @@ public class SpecialCardAi {
|
||||
boolean canRetFromGrave = false;
|
||||
String name = c.getName().replace(',', ';');
|
||||
for (Trigger t : c.getTriggers()) {
|
||||
SpellAbility ab = t.ensureAbility();
|
||||
SpellAbility ab = null;
|
||||
if (t.hasParam("Execute")) {
|
||||
ab = AbilityFactory.getAbility(c.getSVar(t.getParam("Execute")), c);
|
||||
}
|
||||
if (ab == null) { continue; }
|
||||
|
||||
if (ab.getApi() == ApiType.ChangeZone
|
||||
@@ -738,7 +680,7 @@ public class SpecialCardAi {
|
||||
// if there's another reanimator card currently suspended, don't cast a new one until the previous
|
||||
// one resolves, otherwise the reanimation attempt will be ruined (e.g. Living End)
|
||||
for (Card ex : ai.getCardsIn(ZoneType.Exile)) {
|
||||
if (ex.hasSVar("IsReanimatorCard") && ex.getCounters(CounterEnumType.TIME) > 0) {
|
||||
if (ex.hasSVar("IsReanimatorCard") && ex.getCounters(CounterType.TIME) > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -786,37 +728,6 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
// Maze's End
|
||||
public static class MazesEnd {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
CardCollection availableGates = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.isType("Gate"));
|
||||
|
||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai && !availableGates.isEmpty();
|
||||
}
|
||||
|
||||
public static Card considerCardToGet(final Player ai, final SpellAbility sa)
|
||||
{
|
||||
CardCollection currentGates = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Gate"));
|
||||
CardCollection availableGates = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.isType("Gate"));
|
||||
|
||||
if (availableGates.isEmpty())
|
||||
return null; // shouldn't get here
|
||||
|
||||
for (Card gate : availableGates)
|
||||
{
|
||||
if (CardLists.filter(currentGates, CardPredicates.nameEquals(gate.getName())).isEmpty())
|
||||
{
|
||||
// Diversify our mana base
|
||||
return gate;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch a random gate if we already have all types
|
||||
return Aggregates.random(availableGates);
|
||||
}
|
||||
}
|
||||
|
||||
// Mairsil, the Pretender
|
||||
public static class MairsilThePretender {
|
||||
// Scan the fetch list for a card with at least one activated ability.
|
||||
@@ -828,7 +739,7 @@ public class SpecialCardAi {
|
||||
Player controller = c.getController();
|
||||
boolean wasCaged = false;
|
||||
for (Card caged : CardLists.filter(controller.getCardsIn(ZoneType.Exile),
|
||||
CardPredicates.hasCounter(CounterEnumType.CAGE))) {
|
||||
CardPredicates.hasCounter(CounterType.CAGE))) {
|
||||
if (c.getName().equals(caged.getName())) {
|
||||
wasCaged = true;
|
||||
break;
|
||||
@@ -894,7 +805,7 @@ public class SpecialCardAi {
|
||||
}
|
||||
|
||||
// Set PayX here to maximum value.
|
||||
int tokenSize = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
int tokenSize = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
// Some basic strategy for Momir
|
||||
if (tokenSize < 2) {
|
||||
@@ -905,7 +816,7 @@ public class SpecialCardAi {
|
||||
tokenSize = 11;
|
||||
}
|
||||
|
||||
sa.setXManaCostPaid(tokenSize);
|
||||
source.setSVar("PayX", Integer.toString(tokenSize));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -965,6 +876,7 @@ public class SpecialCardAi {
|
||||
// Only activate in AI's own turn (sans the exception above)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -986,7 +898,11 @@ public class SpecialCardAi {
|
||||
|
||||
}
|
||||
// Maybe use it for some important high-impact spells even if there are more cards in hand?
|
||||
return ai.getCardsIn(ZoneType.Hand).size() <= 1 || hasEnsnaringBridgeEffect;
|
||||
if (ai.getCardsIn(ZoneType.Hand).size() > 1 && !hasEnsnaringBridgeEffect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1000,7 +916,7 @@ public class SpecialCardAi {
|
||||
return false;
|
||||
}
|
||||
String prominentColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield));
|
||||
int devotion = AbilityUtils.calculateAmount(sa.getHostCard(), "Count$Devotion." + prominentColor, sa);
|
||||
int devotion = CardFactoryUtil.xCount(sa.getHostCard(), "Count$Devotion." + prominentColor);
|
||||
int activationCost = sa.getPayCosts().getTotalMana().getCMC() + (sa.getPayCosts().hasTapCost() ? 1 : 0);
|
||||
|
||||
// do not use this SA if devotion to most prominent color is less than its own activation cost + 1 (to actually get advantage)
|
||||
@@ -1134,7 +1050,7 @@ public class SpecialCardAi {
|
||||
// Sarkhan the Mad
|
||||
public static class SarkhanTheMad {
|
||||
public static boolean considerDig(final Player ai, final SpellAbility sa) {
|
||||
return sa.getHostCard().getCounters(CounterEnumType.LOYALTY) == 1;
|
||||
return sa.getHostCard().getCounters(CounterType.LOYALTY) == 1;
|
||||
}
|
||||
|
||||
public static boolean considerMakeDragon(final Player ai, final SpellAbility sa) {
|
||||
@@ -1167,44 +1083,6 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
// Sorin, Vengeful Bloodlord
|
||||
public static class SorinVengefulBloodlord {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
int loyalty = sa.getHostCard().getCounters(CounterEnumType.LOYALTY);
|
||||
CardCollection creaturesToGet = CardLists.filter(ai.getCardsIn(ZoneType.Graveyard),
|
||||
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.lessCMC(loyalty - 1), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
final Card copy = CardUtil.getLKICopy(card);
|
||||
ComputerUtilCard.applyStaticContPT(ai.getGame(), copy, null);
|
||||
return copy.getNetToughness() > 0;
|
||||
}
|
||||
}));
|
||||
CardLists.sortByCmcDesc(creaturesToGet);
|
||||
|
||||
if (creaturesToGet.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// pick the best creature that will stay on the battlefield
|
||||
Card best = creaturesToGet.getFirst();
|
||||
for (Card c : creaturesToGet) {
|
||||
if (best != c && ComputerUtilCard.evaluateCreature(c, true, false) >
|
||||
ComputerUtilCard.evaluateCreature(best, true, false)) {
|
||||
best = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (best != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Survival of the Fittest
|
||||
public static class SurvivalOfTheFittest {
|
||||
public static Card considerDiscardTarget(final Player ai) {
|
||||
@@ -1272,7 +1150,7 @@ public class SpecialCardAi {
|
||||
// no options with smaller CMC, so discard the one that is harder to cast for the one that is
|
||||
// easier to cast right now, but only if the best card in the library is at least CMC 3
|
||||
// (probably not worth it to grab low mana cost cards this way)
|
||||
if (maxCMC != null && bestInLib != null && maxCMC.getCMC() < bestInLib.getCMC() && bestInLib.getCMC() >= 3) {
|
||||
if (maxCMC != null && maxCMC.getCMC() < bestInLib.getCMC() && bestInLib.getCMC() >= 3) {
|
||||
return maxCMC;
|
||||
}
|
||||
// We appear to be playing Reanimator (or we have a reanimator card in hand already), so it's
|
||||
@@ -1332,7 +1210,7 @@ public class SpecialCardAi {
|
||||
sa.getTargets().add(worstOwnCreat);
|
||||
}
|
||||
|
||||
return sa.getTargets().size() > 0;
|
||||
return sa.getTargets().getNumTargeted() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1351,29 +1229,13 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (aiHandSize < HAND_SIZE_THRESHOLD || maxOppHandSize - aiHandSize > HAND_SIZE_THRESHOLD) {
|
||||
// use in case we're getting low on cards or if we're significantly behind our opponent in cards in hand
|
||||
return aiHandSize < HAND_SIZE_THRESHOLD || maxOppHandSize - aiHandSize > HAND_SIZE_THRESHOLD;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Timmerian Fiends
|
||||
public static class TimmerianFiends {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
final Card targeted = sa.getParentTargetingCard().getTargetCard();
|
||||
if (targeted == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targeted.isCreature()) {
|
||||
if (ComputerUtil.aiLifeInDanger(ai, true, 0)) {
|
||||
return true; // do it, hoping to save a valuable potential blocker etc.
|
||||
}
|
||||
return ComputerUtilCard.evaluateCreature(targeted) >= 200; // might need tweaking
|
||||
} else {
|
||||
// TODO: this currently compares purely by CMC. To be somehow improved, especially for stuff like the Power Nine etc.
|
||||
return ComputerUtilCard.evaluatePermanentList(new CardCollection(targeted)) >= 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Volrath's Shapeshifter
|
||||
@@ -1399,7 +1261,9 @@ public class SpecialCardAi {
|
||||
if (topGY == null
|
||||
|| !topGY.isCreature()
|
||||
|| ComputerUtilCard.evaluateCreature(creatHand) > ComputerUtilCard.evaluateCreature(topGY) + 80) {
|
||||
return numCreatsInHand > 1 || !ComputerUtilMana.canPayManaCost(creatHand.getSpellPermanent(), ai, 0);
|
||||
if (numCreatsInHand > 1 || !ComputerUtilMana.canPayManaCost(creatHand.getSpellPermanent(), ai, 0)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1426,11 +1290,11 @@ public class SpecialCardAi {
|
||||
Card source = sa.getHostCard();
|
||||
Game game = source.getGame();
|
||||
|
||||
final int loyalty = source.getCounters(CounterEnumType.LOYALTY);
|
||||
final int loyalty = source.getCounters(CounterType.LOYALTY);
|
||||
int x = -1, best = 0;
|
||||
Card single = null;
|
||||
for (int i = 0; i < loyalty; i++) {
|
||||
sa.setXManaCostPaid(i);
|
||||
sa.setSVar("ChosenX", "Number$" + i);
|
||||
oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
|
||||
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
|
||||
computerType = AbilityUtils.filterListByType(ai.getCardsIn(origin), sa.getParam("ChangeType"), sa);
|
||||
@@ -1447,8 +1311,13 @@ public class SpecialCardAi {
|
||||
}
|
||||
// check if +1 would be sufficient
|
||||
if (single != null) {
|
||||
// TODO use better logic to find the right Deal Damage Effect?
|
||||
SpellAbility ugin_burn = Iterables.find(source.getSpellAbilities(), SpellAbilityPredicates.isApi(ApiType.DealDamage), null);
|
||||
SpellAbility ugin_burn = null;
|
||||
for (final SpellAbility s : source.getSpellAbilities()) {
|
||||
if (s.getApi() == ApiType.DealDamage) {
|
||||
ugin_burn = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ugin_burn != null) {
|
||||
// basic logic copied from DamageDealAi::dealDamageChooseTgtC
|
||||
if (ugin_burn.canTarget(single)) {
|
||||
@@ -1459,17 +1328,17 @@ public class SpecialCardAi {
|
||||
if (can_kill) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// simple check to burn player instead of exiling planeswalker
|
||||
if (single.isPlaneswalker() && single.getCurrentLoyalty() <= 3) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (x == -1) {
|
||||
return false;
|
||||
}
|
||||
sa.setXManaCostPaid(x);
|
||||
sa.setSVar("ChosenX", "Number$" + x);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1516,6 +1385,7 @@ public class SpecialCardAi {
|
||||
// Only activate in AI's own turn (sans the exception above)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.ICardFace;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostParser;
|
||||
@@ -26,6 +20,10 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityCondition;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Base class for API-specific AI logic
|
||||
* <p>
|
||||
@@ -79,14 +77,16 @@ public abstract class SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (!checkApiLogic(ai, sa)) {
|
||||
return false;
|
||||
if (sa.hasParam("AITgtBeforeCostEval")) {
|
||||
// Cost payment requires a valid target to be specified, e.g. Quillmane Baku, so run the API logic first
|
||||
// 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)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
|
||||
@@ -112,7 +112,7 @@ public abstract class SpellAbilityAi {
|
||||
if (aiLogic.equals("CheckCondition")) {
|
||||
SpellAbility saCopy = sa.copy();
|
||||
saCopy.setActivatingPlayer(ai);
|
||||
return saCopy.metConditions();
|
||||
return saCopy.getConditions().areMet(saCopy);
|
||||
}
|
||||
|
||||
return !("Never".equals(aiLogic));
|
||||
@@ -127,7 +127,7 @@ public abstract class SpellAbilityAi {
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source, sa)) {
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa)) {
|
||||
@@ -167,8 +167,7 @@ public abstract class SpellAbilityAi {
|
||||
|
||||
// a mandatory SpellAbility with targeting but without candidates,
|
||||
// does not need to go any deeper
|
||||
if (sa.usesTargeting() && mandatory && !sa.isTargetNumberValid()
|
||||
&& !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||
if (sa.usesTargeting() && mandatory && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -248,8 +247,7 @@ public abstract class SpellAbilityAi {
|
||||
protected static boolean isSorcerySpeed(final SpellAbility sa) {
|
||||
return (sa.getRootAbility().isSpell() && sa.getHostCard().isSorcery())
|
||||
|| (sa.getRootAbility().isAbility() && sa.getRestrictions().isSorcerySpeed())
|
||||
|| (sa.getRootAbility().isAdventure() && sa.getHostCard().getState(CardStateName.Adventure).getType().isSorcery())
|
||||
|| (sa.isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed."));
|
||||
|| (sa.getRestrictions().isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed."));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,7 +264,7 @@ public abstract class SpellAbilityAi {
|
||||
|
||||
// TODO probably also consider if winter orb or similar are out
|
||||
|
||||
if (sa instanceof AbilitySub) {
|
||||
if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
|
||||
return true; // This is only true for Drawbacks and triggers
|
||||
}
|
||||
|
||||
@@ -278,7 +276,7 @@ public abstract class SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sa.isPwAbility() && phase.is(PhaseType.MAIN2)) {
|
||||
if (sa.getRestrictions().isPwAbility() && phase.is(PhaseType.MAIN2)) {
|
||||
return true;
|
||||
}
|
||||
if (sa.isSpell() && !sa.isBuyBackAbility()) {
|
||||
@@ -306,7 +304,7 @@ public abstract class SpellAbilityAi {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
|
||||
boolean hasPlayer = false;
|
||||
boolean hasCard = false;
|
||||
boolean hasPlaneswalker = false;
|
||||
@@ -323,11 +321,11 @@ public abstract class SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (hasPlayer && hasPlaneswalker) {
|
||||
return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options, params);
|
||||
return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options);
|
||||
} else if (hasCard) {
|
||||
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer, params);
|
||||
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
|
||||
} else if (hasPlayer) {
|
||||
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options, params);
|
||||
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -338,17 +336,17 @@ public abstract class SpellAbilityAi {
|
||||
return spells.get(0);
|
||||
}
|
||||
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleCard is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayer is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayerOrPlaneswalker is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ai.ability.*;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.util.ReflectionUtil;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public enum SpellApiToAi {
|
||||
Converter;
|
||||
|
||||
@@ -22,7 +21,6 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
|
||||
.put(ApiType.AddPhase, AddPhaseAi.class)
|
||||
.put(ApiType.AddTurn, AddTurnAi.class)
|
||||
.put(ApiType.Amass, AmassAi.class)
|
||||
.put(ApiType.Animate, AnimateAi.class)
|
||||
.put(ApiType.AnimateAll, AnimateAllAi.class)
|
||||
.put(ApiType.Attach, AttachAi.class)
|
||||
@@ -34,16 +32,14 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.BidLife, BidLifeAi.class)
|
||||
.put(ApiType.Bond, BondAi.class)
|
||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
||||
.put(ApiType.ChangeCombatants, CannotPlayAi.class)
|
||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
||||
.put(ApiType.ChangeZone, ChangeZoneAi.class)
|
||||
.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class)
|
||||
.put(ApiType.Charm, CharmAi.class)
|
||||
.put(ApiType.ChooseCard, ChooseCardAi.class)
|
||||
.put(ApiType.ChooseColor, ChooseColorAi.class)
|
||||
.put(ApiType.ChooseDirection, ChooseDirectionAi.class)
|
||||
.put(ApiType.ChooseEvenOdd, ChooseEvenOddAi.class)
|
||||
.put(ApiType.ChooseNumber, ChooseNumberAi.class)
|
||||
.put(ApiType.ChoosePlayer, ChoosePlayerAi.class)
|
||||
.put(ApiType.ChooseSource, ChooseSourceAi.class)
|
||||
@@ -64,7 +60,6 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.Destroy, DestroyAi.class)
|
||||
.put(ApiType.DestroyAll, DestroyAllAi.class)
|
||||
.put(ApiType.Dig, DigAi.class)
|
||||
.put(ApiType.DigMultiple, DigMultipleAi.class)
|
||||
.put(ApiType.DigUntil, DigUntilAi.class)
|
||||
.put(ApiType.Discard, DiscardAi.class)
|
||||
.put(ApiType.DrainMana, DrainManaAi.class)
|
||||
@@ -72,7 +67,6 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.EachDamage, DamageEachAi.class)
|
||||
.put(ApiType.Effect, EffectAi.class)
|
||||
.put(ApiType.Encode, EncodeAi.class)
|
||||
.put(ApiType.EndCombatPhase, EndTurnAi.class)
|
||||
.put(ApiType.EndTurn, EndTurnAi.class)
|
||||
.put(ApiType.ExchangeLife, LifeExchangeAi.class)
|
||||
.put(ApiType.ExchangeLifeVariant, LifeExchangeVariantAi.class)
|
||||
@@ -85,15 +79,14 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.FlipACoin, FlipACoinAi.class)
|
||||
.put(ApiType.Fog, FogAi.class)
|
||||
.put(ApiType.GainControl, ControlGainAi.class)
|
||||
.put(ApiType.GainControlVariant, ControlGainVariantAi.class)
|
||||
.put(ApiType.GainControlVariant, AlwaysPlayAi.class)
|
||||
.put(ApiType.GainLife, LifeGainAi.class)
|
||||
.put(ApiType.GainOwnership, CannotPlayAi.class)
|
||||
.put(ApiType.GameDrawn, CannotPlayAi.class)
|
||||
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
|
||||
.put(ApiType.Goad, GoadAi.class)
|
||||
.put(ApiType.Haunt, HauntAi.class)
|
||||
.put(ApiType.ImmediateTrigger, ImmediateTriggerAi.class)
|
||||
.put(ApiType.Investigate, InvestigateAi.class)
|
||||
.put(ApiType.ImmediateTrigger, AlwaysPlayAi.class)
|
||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||
.put(ApiType.LosesGame, GameLossAi.class)
|
||||
.put(ApiType.Mana, ManaEffectAi.class)
|
||||
@@ -106,7 +99,6 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.MultiplyCounter, CountersMultiplyAi.class)
|
||||
.put(ApiType.MustAttack, MustAttackAi.class)
|
||||
.put(ApiType.MustBlock, MustBlockAi.class)
|
||||
.put(ApiType.Mutate, MutateAi.class)
|
||||
.put(ApiType.NameCard, ChooseCardNameAi.class)
|
||||
.put(ApiType.NoteCounters, AlwaysPlayAi.class)
|
||||
.put(ApiType.PeekAndReveal, PeekAndRevealAi.class)
|
||||
@@ -143,9 +135,8 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.Reveal, RevealAi.class)
|
||||
.put(ApiType.RevealHand, RevealHandAi.class)
|
||||
.put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class)
|
||||
.put(ApiType.RollDice, RollDiceAi.class)
|
||||
.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class)
|
||||
.put(ApiType.RunChaos, AlwaysPlayAi.class)
|
||||
.put(ApiType.RunSVarAbility, AlwaysPlayAi.class)
|
||||
.put(ApiType.Sacrifice, SacrificeAi.class)
|
||||
.put(ApiType.SacrificeAll, SacrificeAllAi.class)
|
||||
.put(ApiType.Scry, ScryAi.class)
|
||||
@@ -153,10 +144,9 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.SetLife, LifeSetAi.class)
|
||||
.put(ApiType.SetState, SetStateAi.class)
|
||||
.put(ApiType.Shuffle, ShuffleAi.class)
|
||||
.put(ApiType.SkipPhase, SkipPhaseAi.class)
|
||||
.put(ApiType.SkipTurn, SkipTurnAi.class)
|
||||
.put(ApiType.StoreMap, StoreMapAi.class)
|
||||
.put(ApiType.StoreSVar, StoreSVarAi.class)
|
||||
.put(ApiType.Subgame, AlwaysPlayAi.class)
|
||||
.put(ApiType.Surveil, SurveilAi.class)
|
||||
.put(ApiType.Tap, TapAi.class)
|
||||
.put(ApiType.TapAll, TapAllAi.class)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
@@ -13,6 +11,9 @@ import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
@@ -21,7 +22,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
||||
@@ -45,7 +46,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
@@ -56,9 +57,12 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
} else {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
return defined.contains(opp);
|
||||
if (!defined.contains(opp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
@@ -83,7 +87,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai.getWeakestOpponent());
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactory_Turns class.
|
||||
@@ -67,8 +67,10 @@ public class AddTurnAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!StringUtils.isNumeric(sa.getParam("NumTurns"))) {
|
||||
// TODO: improve ai for Sage of Hours
|
||||
return StringUtils.isNumeric(sa.getParam("NumTurns"));
|
||||
return false;
|
||||
}
|
||||
// not sure if the AI should be playing with cards that give the
|
||||
// Human more turns.
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class AmassAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, final SpellAbility sa) {
|
||||
CardCollection aiArmies = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Army"));
|
||||
Card host = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
if (!aiArmies.isEmpty()) {
|
||||
return CardLists.count(aiArmies, CardPredicates.canReceiveCounters(CounterEnumType.P1P1)) > 0;
|
||||
}
|
||||
final String tokenScript = "b_0_0_zombie_army";
|
||||
final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa);
|
||||
|
||||
Card token = TokenInfo.getProtoType(tokenScript, sa, false);
|
||||
|
||||
if (token == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
token.setController(ai, 0);
|
||||
token.setLastKnownZone(ai.getZone(ZoneType.Battlefield));
|
||||
|
||||
boolean result = true;
|
||||
|
||||
// need to check what the cards would be on the battlefield
|
||||
// do not attach yet, that would cause Events
|
||||
CardCollection preList = new CardCollection(token);
|
||||
game.getAction().checkStaticAbilities(false, Sets.newHashSet(token), preList);
|
||||
|
||||
if (token.canReceiveCounters(CounterEnumType.P1P1)) {
|
||||
token.setCounters(CounterEnumType.P1P1, amount);
|
||||
}
|
||||
|
||||
if (token.isCreature() && token.getNetToughness() < 1) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
//reset static abilities
|
||||
game.getAction().checkStaticAbilities(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
// TODO: Special check for instant speed logic? Something like Lazotep Plating.
|
||||
/*
|
||||
boolean isInstant = sa.getRestrictions().isInstantSpeed();
|
||||
CardCollection aiArmies = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Army"));
|
||||
|
||||
if (isInstant) {
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory || checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
Iterable<Card> better = CardLists.filter(options, CardPredicates.canReceiveCounters(CounterEnumType.P1P1));
|
||||
if (Iterables.isEmpty(better)) {
|
||||
better = options;
|
||||
}
|
||||
return ComputerUtilCard.getBestAI(better);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,34 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.ai.*;
|
||||
import forge.card.CardType;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementHandler;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityContinuous;
|
||||
import forge.game.staticability.StaticAbilityLayer;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardType;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.AnimateEffectBase;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardTraitChanges;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.cost.CostPutCounter;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityContinuous;
|
||||
import forge.game.staticability.StaticAbilityLayer;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -48,13 +42,15 @@ import forge.game.zone.ZoneType;
|
||||
public class AnimateAi extends SpellAbilityAi {
|
||||
@Override
|
||||
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 PhaseHandler ph = game.getPhaseHandler();
|
||||
if ("Attacking".equals(aiLogic)) { // Launch the Fleet
|
||||
if (ph.getPlayerTurn().isOpponentOf(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
List<Card> list = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
|
||||
List<Card> list = CardLists.getValidCards(ai.getCreaturesInPlay(), tgt.getValidTgts(), ai, source, sa);
|
||||
for (Card c : list) {
|
||||
if (ComputerUtilCard.doesCreatureAttackAI(ai, c)) {
|
||||
sa.getTargets().add(c);
|
||||
@@ -81,13 +77,13 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
num = (num == null) ? "1" : num;
|
||||
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
|
||||
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
||||
ai.getWeakestOpponent(), topStack.getHostCard(), topStack);
|
||||
ComputerUtil.getOpponentFor(ai), topStack.getHostCard(), topStack);
|
||||
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
|
||||
ComputerUtilCard.sortByEvaluateCreature(list);
|
||||
if (!list.isEmpty() && list.size() == nToSac && ComputerUtilCost.canPayCost(sa, ai)) {
|
||||
Card animatedCopy = becomeAnimated(source, sa);
|
||||
list.add(animatedCopy);
|
||||
list = CardLists.getValidCards(list, valid.split(","), ai.getWeakestOpponent(), topStack.getHostCard(),
|
||||
list = CardLists.getValidCards(list, valid.split(","), ComputerUtil.getOpponentFor(ai), topStack.getHostCard(),
|
||||
topStack);
|
||||
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
|
||||
if (ComputerUtilCard.evaluateCreature(animatedCopy) < ComputerUtilCard.evaluateCreature(list.get(0))
|
||||
@@ -113,16 +109,11 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
if (ph.is(PhaseType.MAIN2) && !sa.hasParam("Permanent") && !sa.hasParam("UntilYourNextTurn")) {
|
||||
return false;
|
||||
}
|
||||
// Don't animate if the AI won't attack anyway or use as a potential blocker
|
||||
// Don't animate if the AI won't attack anyway
|
||||
Player opponent = ai.getWeakestOpponent();
|
||||
// Activating as a potential blocker is only viable if it's an ability activated from a permanent, otherwise
|
||||
// the AI will waste resources
|
||||
boolean activateAsPotentialBlocker = sa.hasParam("UntilYourNextTurn")
|
||||
&& ai.getGame().getPhaseHandler().getNextTurn() != ai
|
||||
&& source.isPermanent();
|
||||
if (ph.isPlayerTurn(ai) && ai.getLife() < 6 && opponent.getLife() > 6
|
||||
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)
|
||||
&& !sa.hasParam("AILogic") && !sa.hasParam("Permanent") && !activateAsPotentialBlocker) {
|
||||
&& !sa.hasParam("AILogic") && !sa.hasParam("Permanent")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -133,7 +124,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = aiPlayer.getGame();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
||||
if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) {
|
||||
return false; // what is this for?
|
||||
}
|
||||
if (!game.getStack().isEmpty() && game.getStack().peekAbility().getApi() == ApiType.Sacrifice) {
|
||||
@@ -210,16 +201,21 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
return bFlag; // All of the defined stuff is animated, not very useful
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
return animateTgtAI(sa);
|
||||
if (!animateTgtAI(sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
return animateTgtAI(sa);
|
||||
if (!animateTgtAI(sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -232,7 +228,10 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
} else if (sa.usesTargeting() && mandatory) {
|
||||
// fallback if animate is mandatory
|
||||
sa.resetTargets();
|
||||
List<Card> list = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa);
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
CardCollectionView list = aiPlayer.getGame().getCardsIn(tgt.getZone());
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), aiPlayer, source, sa);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -246,26 +245,26 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
private boolean animateTgtAI(final SpellAbility sa) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
final boolean alwaysActivatePWAbility = sa.hasParam("Planeswalker")
|
||||
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class)
|
||||
&& sa.getTargetRestrictions() != null
|
||||
&& sa.getTargetRestrictions().getMinTargets(sa.getHostCard(), sa) == 0;
|
||||
|
||||
final CardType types = new CardType(true);
|
||||
final CardType types = new CardType();
|
||||
if (sa.hasParam("Types")) {
|
||||
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
|
||||
}
|
||||
|
||||
// something is used for animate into creature
|
||||
if (types.isCreature()) {
|
||||
final Game game = ai.getGame();
|
||||
CardCollectionView list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
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
|
||||
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
|
||||
|
||||
// list is empty, no possible targets
|
||||
if (list.isEmpty() && !alwaysActivatePWAbility) {
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -299,7 +298,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
// evaluate their value to check if it becomes better
|
||||
if (c.isCreature()) {
|
||||
int cValue = ComputerUtilCard.evaluateCreature(c);
|
||||
if (cValue >= aValue)
|
||||
if (cValue <= aValue)
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -318,7 +317,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// data is empty, no good targets
|
||||
if (data.isEmpty() && !alwaysActivatePWAbility) {
|
||||
if (data.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -336,18 +335,8 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
|
||||
// select the worst of the best
|
||||
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);
|
||||
sa.getTargets().add(worst);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -369,6 +358,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
// duplicating AnimateEffect.resolve
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = sa.getActivatingPlayer().getGame();
|
||||
final Map<String, String> svars = source.getSVars();
|
||||
final long timestamp = game.getNextTimestamp();
|
||||
card.setSickness(hasOriginalCardSickness);
|
||||
|
||||
@@ -376,24 +366,18 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
Integer power = null;
|
||||
if (sa.hasParam("Power")) {
|
||||
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
||||
if (power == 0 && "PTByCMC".equals(sa.getParam("AILogic"))) {
|
||||
power = card.getManaCost().getCMC();
|
||||
}
|
||||
}
|
||||
Integer toughness = null;
|
||||
if (sa.hasParam("Toughness")) {
|
||||
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
|
||||
if (toughness == 0 && "PTByCMC".equals(sa.getParam("AILogic"))) {
|
||||
toughness = card.getManaCost().getCMC();
|
||||
}
|
||||
}
|
||||
|
||||
final CardType types = new CardType(true);
|
||||
final CardType types = new CardType();
|
||||
if (sa.hasParam("Types")) {
|
||||
types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
|
||||
}
|
||||
|
||||
final CardType removeTypes = new CardType(true);
|
||||
final CardType removeTypes = new CardType();
|
||||
if (sa.hasParam("RemoveTypes")) {
|
||||
removeTypes.addAll(Arrays.asList(sa.getParam("RemoveTypes").split(",")));
|
||||
}
|
||||
@@ -401,7 +385,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
// allow ChosenType - overrides anything else specified
|
||||
if (types.hasSubtype("ChosenType")) {
|
||||
types.clear();
|
||||
types.addAll(sa.getChosenType());
|
||||
types.add(source.getChosenType());
|
||||
}
|
||||
|
||||
final List<String> keywords = Lists.newArrayList();
|
||||
@@ -421,8 +405,8 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
// allow SVar substitution for keywords
|
||||
for (int i = 0; i < keywords.size(); i++) {
|
||||
final String k = keywords.get(i);
|
||||
if (source.hasSVar(k)) {
|
||||
keywords.add(source.getSVar(k));
|
||||
if (svars.containsKey(k)) {
|
||||
keywords.add(svars.get(k));
|
||||
keywords.remove(k);
|
||||
}
|
||||
}
|
||||
@@ -432,7 +416,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
if (sa.hasParam("Colors")) {
|
||||
final String colors = sa.getParam("Colors");
|
||||
if (colors.equals("ChosenColor")) {
|
||||
tmpDesc = CardUtil.getShortColorsString(sa.getChosenColors());
|
||||
tmpDesc = CardUtil.getShortColorsString(source.getChosenColors());
|
||||
} else {
|
||||
tmpDesc = CardUtil.getShortColorsString(Lists.newArrayList(Arrays.asList(colors.split(","))));
|
||||
}
|
||||
@@ -469,16 +453,81 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
sVars.addAll(Arrays.asList(sa.getParam("sVars").split(",")));
|
||||
}
|
||||
|
||||
AnimateEffectBase.doAnimate(card, sa, power, toughness, types, removeTypes, finalDesc,
|
||||
keywords, removeKeywords, hiddenKeywords,
|
||||
abilities, triggers, replacements, stAbs,
|
||||
timestamp);
|
||||
AnimateEffectBase.doAnimate(card, sa, power, toughness, types, removeTypes, finalDesc, keywords, removeKeywords, hiddenKeywords, timestamp);
|
||||
|
||||
// check if animate added static Abilities
|
||||
CardTraitChanges traits = card.getChangedCardTraits().get(timestamp);
|
||||
if (traits != null) {
|
||||
for (StaticAbility stAb : traits.getStaticAbilities()) {
|
||||
if ("Continuous".equals(stAb.getParam("Mode"))) {
|
||||
// back to duplicating AnimateEffect.resolve
|
||||
// TODO will all these abilities/triggers/replacements/etc. lead to
|
||||
// memory leaks or unintended effects?
|
||||
// remove abilities
|
||||
final List<SpellAbility> removedAbilities = Lists.newArrayList();
|
||||
boolean clearAbilities = sa.hasParam("OverwriteAbilities");
|
||||
boolean clearSpells = sa.hasParam("OverwriteSpells");
|
||||
boolean removeAll = sa.hasParam("RemoveAllAbilities");
|
||||
boolean removeIntrinsic = sa.hasParam("RemoveIntrinsicAbilities");
|
||||
|
||||
if (clearAbilities || clearSpells || removeAll) {
|
||||
for (final SpellAbility ab : card.getSpellAbilities()) {
|
||||
if (removeAll
|
||||
|| (ab.isIntrinsic() && removeIntrinsic && !ab.isBasicLandAbility())
|
||||
|| (ab.isAbility() && clearAbilities)
|
||||
|| (ab.isSpell() && clearSpells)) {
|
||||
card.removeSpellAbility(ab);
|
||||
removedAbilities.add(ab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// give abilities
|
||||
final List<SpellAbility> addedAbilities = Lists.newArrayList();
|
||||
if (abilities.size() > 0) {
|
||||
for (final String s : abilities) {
|
||||
final String actualAbility = source.getSVar(s);
|
||||
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, source);
|
||||
addedAbilities.add(grantedAbility);
|
||||
card.addSpellAbility(grantedAbility);
|
||||
}
|
||||
}
|
||||
|
||||
// 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, source, false);
|
||||
addedTriggers.add(card.addTrigger(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,
|
||||
source, false);
|
||||
addedReplacements.add(card.addReplacementEffect(parsedReplacement));
|
||||
}
|
||||
}
|
||||
|
||||
// suppress triggers from the animated card
|
||||
final List<Trigger> removedTriggers = Lists.newArrayList();
|
||||
if (sa.hasParam("OverwriteTriggers") || removeAll || removeIntrinsic) {
|
||||
for (final Trigger trigger : card.getTriggers()) {
|
||||
if (removeIntrinsic && !trigger.isIntrinsic()) {
|
||||
continue;
|
||||
}
|
||||
trigger.setSuppressed(true);
|
||||
removedTriggers.add(trigger);
|
||||
}
|
||||
}
|
||||
|
||||
// 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.getMapParams().get("Mode"))) {
|
||||
for (final StaticAbilityLayer layer : stAb.getLayers()) {
|
||||
StaticAbilityContinuous.applyContinuousAbility(stAb, new CardCollection(card), layer);
|
||||
}
|
||||
@@ -499,6 +548,30 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
card.setSVar(name, actualsVar);
|
||||
}
|
||||
}
|
||||
|
||||
// suppress static abilities from the animated card
|
||||
final List<StaticAbility> removedStatics = Lists.newArrayList();
|
||||
if (sa.hasParam("OverwriteStatics") || removeAll || removeIntrinsic) {
|
||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||
if (removeIntrinsic && !stAb.isIntrinsic()) {
|
||||
continue;
|
||||
}
|
||||
stAb.setTemporarilySuppressed(true);
|
||||
removedStatics.add(stAb);
|
||||
}
|
||||
}
|
||||
|
||||
// suppress static abilities from the animated card
|
||||
final List<ReplacementEffect> removedReplacements = Lists.newArrayList();
|
||||
if (sa.hasParam("OverwriteReplacements") || removeAll || removeIntrinsic) {
|
||||
for (final ReplacementEffect re : card.getReplacementEffects()) {
|
||||
if (removeIntrinsic && !re.isIntrinsic()) {
|
||||
continue;
|
||||
}
|
||||
re.setTemporarilySuppressed(true);
|
||||
removedReplacements.add(re);
|
||||
}
|
||||
}
|
||||
ComputerUtilCard.applyStaticContPT(game, card, null);
|
||||
}
|
||||
|
||||
@@ -509,12 +582,4 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
public static boolean isAnimatedThisTurn(Player ai, Card c) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
@@ -10,23 +8,12 @@ public class AnimateAllAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if ("CreatureAdvantage".equals(logic) && !aiPlayer.getCreaturesInPlay().isEmpty()) {
|
||||
// TODO: improve this or implement a better logic for abilities like Oko, the Trickster ultimate
|
||||
for (Card c : aiPlayer.getCreaturesInPlay()) {
|
||||
if (ComputerUtilCard.doesCreatureAttackAI(aiPlayer, c)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "Always".equals(logic);
|
||||
return false;
|
||||
} // end animateAllCanPlayAI()
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory || canPlayAI(aiPlayer, sa);
|
||||
return mandatory;
|
||||
}
|
||||
|
||||
} // end class AbilityFactoryAnimate
|
||||
|
||||
@@ -1,39 +1,17 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
@@ -53,6 +31,11 @@ import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -89,6 +72,11 @@ public class AttachAi extends SpellAbilityAi {
|
||||
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
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
@@ -108,21 +96,20 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if (ai.getController().isAI()) {
|
||||
advancedFlash = ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.FLASH_ENABLE_ADVANCED_LOGIC);
|
||||
}
|
||||
if ((source.hasKeyword(Keyword.FLASH) || (!ai.canCastSorcery() && sa.canCastTiming(ai)))
|
||||
&& source.isAura() && advancedFlash && !doAdvancedFlashAuraLogic(ai, sa, sa.getTargetCard())) {
|
||||
if (source.withFlash(ai) && source.isAura() && advancedFlash && !doAdvancedFlashAuraLogic(ai, sa, sa.getTargetCard())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (abCost.getTotalMana().countX() > 0 && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
if (abCost.getTotalMana().countX() > 0 && source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value. (Endless Scream and Venarian
|
||||
// Gold)
|
||||
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
if (xPay == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sa.setXManaCostPaid(xPay);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Chained to the Rocks")) {
|
||||
@@ -136,7 +123,9 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return !(c.hasProtectionFrom(source) || c.hasKeyword(Keyword.SHROUD) || c.hasKeyword(Keyword.HEXPROOF));
|
||||
}
|
||||
});
|
||||
return !targets.isEmpty();
|
||||
if (targets.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -244,15 +233,10 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
boolean alternativeConsiderations = hasFloatMana || willDiscardNow || willDieNow || willRespondToStack || willCastAtEOT || willCastEarly;
|
||||
|
||||
if (!alternativeConsiderations) {
|
||||
if (combat == null ||
|
||||
game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
if (!alternativeConsiderations && (combat == null || game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) || (!combat.isAttacking(attachTarget) && !combat.isBlocking(attachTarget))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return combat.isAttacking(attachTarget) || combat.isBlocking(attachTarget);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -414,7 +398,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if (!c.isCreature() && !c.getType().hasSubtype("Vehicle") && !c.isTapped()) {
|
||||
// try to identify if this thing can actually tap
|
||||
for (SpellAbility ab : c.getAllSpellAbilities()) {
|
||||
if (ab.getPayCosts().hasTapCost()) {
|
||||
if (ab.getPayCosts() != null && ab.getPayCosts().hasTapCost()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -464,7 +448,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
*/
|
||||
private static Player attachToPlayerAIPreferences(final Player aiPlayer, final SpellAbility sa,
|
||||
final boolean mandatory) {
|
||||
List<Player> targetable = new ArrayList<>();
|
||||
List<Player> targetable = new ArrayList<Player>();
|
||||
for (final Player player : aiPlayer.getGame().getPlayers()) {
|
||||
if (sa.canTarget(player)) {
|
||||
targetable.add(player);
|
||||
@@ -576,7 +560,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
|
||||
if (sa.isAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -854,7 +838,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
* @return the card
|
||||
*/
|
||||
private static Card attachAICursePreference(final SpellAbility sa, final List<Card> list, final boolean mandatory,
|
||||
final Card attachSource, final Player ai) {
|
||||
final Card attachSource) {
|
||||
// AI For choosing a Card to Curse of.
|
||||
|
||||
// TODO Figure out some way to combine The "gathering of data" from
|
||||
@@ -868,7 +852,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
int totToughness = 0;
|
||||
int totPower = 0;
|
||||
final List<String> keywords = new ArrayList<>();
|
||||
final List<String> keywords = new ArrayList<String>();
|
||||
|
||||
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
||||
final Map<String, String> stabMap = stAbility.getMapParams();
|
||||
@@ -888,11 +872,15 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
String kws = stabMap.get("AddKeyword");
|
||||
if (kws != null) {
|
||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
||||
for (final String kw : kws.split(" & ")) {
|
||||
keywords.add(kw);
|
||||
}
|
||||
}
|
||||
kws = stabMap.get("AddHiddenKeyword");
|
||||
if (kws != null) {
|
||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
||||
for (final String kw : kws.split(" & ")) {
|
||||
keywords.add(kw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -915,7 +903,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
Card c = null;
|
||||
if (prefList == null || prefList.isEmpty()) {
|
||||
prefList = new ArrayList<>(list);
|
||||
prefList = new ArrayList<Card>(list);
|
||||
} else {
|
||||
c = ComputerUtilCard.getBestAI(prefList);
|
||||
if (c != null) {
|
||||
@@ -946,24 +934,6 @@ public class AttachAi extends SpellAbilityAi {
|
||||
);
|
||||
}
|
||||
|
||||
// If this is already attached and there's a sac cost, make sure we attach to something that's
|
||||
// seriously better than whatever the attachment is currently attached to (e.g. Bound by Moonsilver)
|
||||
if (sa.getHostCard().getAttachedTo() != null && sa.getHostCard().getAttachedTo().isCreature()
|
||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
||||
final int oldEvalRating = ComputerUtilCard.evaluateCreature(sa.getHostCard().getAttachedTo());
|
||||
final int threshold = ai.isAI() ? ((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD) : Integer.MAX_VALUE;
|
||||
prefList = CardLists.filter(prefList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
if (!card.isCreature()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ComputerUtilCard.evaluateCreature(card) >= oldEvalRating + threshold;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
c = ComputerUtilCard.getBestAI(prefList);
|
||||
|
||||
if (c == null) {
|
||||
@@ -987,13 +957,13 @@ public class AttachAi extends SpellAbilityAi {
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final Card card = sa.getHostCard();
|
||||
// Check if there are any valid targets
|
||||
List<GameObject> targets = new ArrayList<>();
|
||||
List<GameObject> targets = new ArrayList<GameObject>();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt == null) {
|
||||
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
} else {
|
||||
AttachAi.attachPreference(sa, tgt, mandatory);
|
||||
targets = sa.getTargets();
|
||||
targets = sa.getTargets().getTargets();
|
||||
}
|
||||
|
||||
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
||||
@@ -1010,7 +980,9 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
// don't equip creatures that don't gain anything
|
||||
return !card.hasSVar("NonStackingAttachEffect") || !newTarget.isEquippedBy(card.getName());
|
||||
if (card.hasSVar("NonStackingAttachEffect") && newTarget.isEquippedBy(card.getName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1065,7 +1037,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
Card c = null;
|
||||
List<Card> magnetList = null;
|
||||
String stCheck = null;
|
||||
if (attachSource.isAura() || sa.isBestow()) {
|
||||
if (attachSource.isAura() || sa.hasParam("Bestow")) {
|
||||
stCheck = "EnchantedBy";
|
||||
magnetList = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
@@ -1106,7 +1078,12 @@ public class AttachAi extends SpellAbilityAi {
|
||||
final Map<String, String> params = t.getMapParams();
|
||||
if ("Card.Self".equals(params.get("ValidCard"))
|
||||
&& "Battlefield".equals(params.get("Destination"))) {
|
||||
SpellAbility trigSa = t.ensureAbility();
|
||||
SpellAbility trigSa = null;
|
||||
if (t.hasParam("Execute") && attachSource.hasSVar(t.getParam("Execute"))) {
|
||||
trigSa = AbilityFactory.getAbility(attachSource.getSVar(params.get("Execute")), attachSource);
|
||||
} else if (t.getOverridingAbility() != null) {
|
||||
trigSa = t.getOverridingAbility();
|
||||
}
|
||||
if (trigSa != null && trigSa.getApi() == ApiType.DealDamage && "Enchanted".equals(trigSa.getParam("Defined"))) {
|
||||
for (Card target : list) {
|
||||
if (!target.getController().isOpponentOf(ai)) {
|
||||
@@ -1172,9 +1149,8 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
int totToughness = 0;
|
||||
int totPower = 0;
|
||||
final List<String> keywords = new ArrayList<>();
|
||||
final List<String> keywords = new ArrayList<String>();
|
||||
boolean grantingAbilities = false;
|
||||
boolean grantingExtraBlock = false;
|
||||
|
||||
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
||||
final Map<String, String> stabMap = stAbility.getMapParams();
|
||||
@@ -1193,15 +1169,18 @@ public class AttachAi extends SpellAbilityAi {
|
||||
totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), stAbility);
|
||||
|
||||
grantingAbilities |= stabMap.containsKey("AddAbility");
|
||||
grantingExtraBlock |= stabMap.containsKey("CanBlockAmount") || stabMap.containsKey("CanBlockAny");
|
||||
|
||||
String kws = stabMap.get("AddKeyword");
|
||||
if (kws != null) {
|
||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
||||
for (final String kw : kws.split(" & ")) {
|
||||
keywords.add(kw);
|
||||
}
|
||||
}
|
||||
kws = stabMap.get("AddHiddenKeyword");
|
||||
if (kws != null) {
|
||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
||||
for (final String kw : kws.split(" & ")) {
|
||||
keywords.add(kw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1223,26 +1202,19 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
//only add useful keywords unless P/T bonus is significant
|
||||
if (totToughness + totPower < 4 && (!keywords.isEmpty() || grantingExtraBlock)) {
|
||||
if (totToughness + totPower < 4 && !keywords.isEmpty()) {
|
||||
final int pow = totPower;
|
||||
final boolean extraBlock = grantingExtraBlock;
|
||||
prefList = CardLists.filter(prefList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (!keywords.isEmpty()) {
|
||||
for (final String keyword : keywords) {
|
||||
if (isUsefulAttachKeyword(keyword, c, sa, pow)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c.hasKeyword(Keyword.INFECT) && pow >= 2) {
|
||||
// consider +2 power a significant bonus on Infect creatures
|
||||
return true;
|
||||
}
|
||||
if (extraBlock && CombatUtil.canBlock(c, true) && !c.canBlockAny()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1374,7 +1346,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
CardCollection prefList = list;
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
prefList = ComputerUtil.filterAITgts(sa, aiPlayer, list, true);
|
||||
prefList = ComputerUtil.filterAITgts(sa, aiPlayer, (CardCollection)list, true);
|
||||
|
||||
Card c = attachGeneralAI(aiPlayer, sa, prefList, mandatory, attachSource, sa.getParam("AILogic"));
|
||||
|
||||
@@ -1382,7 +1354,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if (c != null && attachSource.isEquipment()
|
||||
&& attachSource.isEquipping()
|
||||
&& attachSource.getEquipping().getController() == aiPlayer) {
|
||||
if (c.equals(attachSource.getEquipping()) && !mandatory) {
|
||||
if (c.equals(attachSource.getEquipping())) {
|
||||
// Do not equip if equipping the same card already
|
||||
return null;
|
||||
}
|
||||
@@ -1393,13 +1365,13 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
boolean uselessCreature = ComputerUtilCard.isUselessCreature(aiPlayer, attachSource.getEquipping());
|
||||
|
||||
if (aic.getProperty(AiProps.MOVE_EQUIPMENT_TO_BETTER_CREATURES).equals("never") && !mandatory) {
|
||||
if (aic.getProperty(AiProps.MOVE_EQUIPMENT_TO_BETTER_CREATURES).equals("never")) {
|
||||
// Do not equip other creatures if the AI profile does not allow moving equipment around
|
||||
return null;
|
||||
} else if (aic.getProperty(AiProps.MOVE_EQUIPMENT_TO_BETTER_CREATURES).equals("from_useless_only")) {
|
||||
// Do not equip other creatures if the AI profile only allows moving equipment from useless creatures
|
||||
// and the equipped creature is still useful (not non-untapping+tapped and not set to can't attack/block)
|
||||
if (!uselessCreature && !mandatory) {
|
||||
if (!uselessCreature) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1415,13 +1387,13 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// avoid randomly moving the equipment back and forth between several creatures in one turn
|
||||
if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ATTACHED_THIS_TURN) && !mandatory) {
|
||||
if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ATTACHED_THIS_TURN)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// do not equip if the new creature is not significantly better than the previous one (evaluates at least better by evalT)
|
||||
int evalT = aic.getIntProperty(AiProps.MOVE_EQUIPMENT_CREATURE_EVAL_THRESHOLD);
|
||||
if (!decideMoveFromUseless && ComputerUtilCard.evaluateCreature(c) - ComputerUtilCard.evaluateCreature(attachSource.getEquipping()) < evalT && !mandatory) {
|
||||
if (!decideMoveFromUseless && ComputerUtilCard.evaluateCreature(c) - ComputerUtilCard.evaluateCreature(attachSource.getEquipping()) < evalT) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1484,7 +1456,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if ("GainControl".equals(logic)) {
|
||||
c = attachAIControlPreference(sa, prefList, mandatory, attachSource);
|
||||
} else if ("Curse".equals(logic)) {
|
||||
c = attachAICursePreference(sa, prefList, mandatory, attachSource, ai);
|
||||
c = attachAICursePreference(sa, prefList, mandatory, attachSource);
|
||||
} else if ("Pump".equals(logic)) {
|
||||
c = attachAIPumpPreference(ai, sa, prefList, mandatory, attachSource);
|
||||
} else if ("Curiosity".equals(logic)) {
|
||||
@@ -1578,51 +1550,86 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (evasive) {
|
||||
return card.getNetCombatDamage() + powerBonus > 0
|
||||
&& ComputerUtilCombat.canAttackNextTurn(card)
|
||||
&& canBeBlocked;
|
||||
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||
|| !ComputerUtilCombat.canAttackNextTurn(card)
|
||||
|| !canBeBlocked) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("Haste")) {
|
||||
return card.hasSickness() && ph.isPlayerTurn(sa.getActivatingPlayer()) && !card.isTapped()
|
||||
&& card.getNetCombatDamage() + powerBonus > 0
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& ComputerUtilCombat.canAttackNextTurn(card);
|
||||
if (!card.hasSickness() || !ph.isPlayerTurn(sa.getActivatingPlayer()) || card.isTapped()
|
||||
|| card.getNetCombatDamage() + powerBonus <= 0
|
||||
|| card.hasKeyword("CARDNAME can attack as though it had haste.")
|
||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
|| !ComputerUtilCombat.canAttackNextTurn(card)) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.endsWith("Indestructible")) {
|
||||
return true;
|
||||
} else if (keyword.endsWith("Deathtouch") || keyword.endsWith("Wither")) {
|
||||
return card.getNetCombatDamage() + powerBonus > 0
|
||||
&& ((canBeBlocked && ComputerUtilCombat.canAttackNextTurn(card))
|
||||
|| CombatUtil.canBlock(card, true));
|
||||
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||
|| ((!canBeBlocked || !ComputerUtilCombat.canAttackNextTurn(card))
|
||||
&& !CombatUtil.canBlock(card, true))) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("Double Strike") || keyword.equals("Lifelink")) {
|
||||
return card.getNetCombatDamage() + powerBonus > 0
|
||||
&& (ComputerUtilCombat.canAttackNextTurn(card) || CombatUtil.canBlock(card, true));
|
||||
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||
|| (!ComputerUtilCombat.canAttackNextTurn(card) && !CombatUtil.canBlock(card, true))) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("First Strike")) {
|
||||
return card.getNetCombatDamage() + powerBonus > 0 && !card.hasKeyword(Keyword.DOUBLE_STRIKE)
|
||||
&& (ComputerUtilCombat.canAttackNextTurn(card) || CombatUtil.canBlock(card, true));
|
||||
if (card.getNetCombatDamage() + powerBonus <= 0 || card.hasKeyword(Keyword.DOUBLE_STRIKE)
|
||||
|| (!ComputerUtilCombat.canAttackNextTurn(card) && !CombatUtil.canBlock(card, true))) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.startsWith("Flanking")) {
|
||||
return card.getNetCombatDamage() + powerBonus > 0
|
||||
&& ComputerUtilCombat.canAttackNextTurn(card)
|
||||
&& canBeBlocked;
|
||||
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||
|| !ComputerUtilCombat.canAttackNextTurn(card)
|
||||
|| !canBeBlocked) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.startsWith("Bushido")) {
|
||||
return (canBeBlocked && ComputerUtilCombat.canAttackNextTurn(card))
|
||||
|| CombatUtil.canBlock(card, true);
|
||||
if ((!canBeBlocked || !ComputerUtilCombat.canAttackNextTurn(card))
|
||||
&& !CombatUtil.canBlock(card, true)) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("Trample")) {
|
||||
return card.getNetCombatDamage() + powerBonus > 1
|
||||
&& canBeBlocked
|
||||
&& ComputerUtilCombat.canAttackNextTurn(card);
|
||||
if (card.getNetCombatDamage() + powerBonus <= 1
|
||||
|| !canBeBlocked
|
||||
|| !ComputerUtilCombat.canAttackNextTurn(card)) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("Infect")) {
|
||||
return card.getNetCombatDamage() + powerBonus > 0
|
||||
&& ComputerUtilCombat.canAttackNextTurn(card);
|
||||
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||
|| !ComputerUtilCombat.canAttackNextTurn(card)) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("Vigilance")) {
|
||||
return card.getNetCombatDamage() + powerBonus > 0
|
||||
&& ComputerUtilCombat.canAttackNextTurn(card)
|
||||
&& CombatUtil.canBlock(card, true);
|
||||
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||
|| !ComputerUtilCombat.canAttackNextTurn(card)
|
||||
|| !CombatUtil.canBlock(card, true)) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("Reach")) {
|
||||
return !card.hasKeyword(Keyword.FLYING) && CombatUtil.canBlock(card, true);
|
||||
if (card.hasKeyword(Keyword.FLYING) || !CombatUtil.canBlock(card, true)) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.endsWith("CARDNAME can block an additional creature each combat.")) {
|
||||
if (!CombatUtil.canBlock(card, true) || card.hasKeyword("CARDNAME can block any number of creatures.")
|
||||
|| card.hasKeyword("CARDNAME can block an additional ninety-nine creatures each combat.")) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("CARDNAME can attack as though it didn't have defender.")) {
|
||||
return card.hasKeyword(Keyword.DEFENDER) && card.getNetCombatDamage() + powerBonus > 0;
|
||||
if (!card.hasKeyword(Keyword.DEFENDER) || card.getNetCombatDamage() + powerBonus <= 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) {
|
||||
return !card.hasKeyword(Keyword.SHROUD) && !card.hasKeyword(Keyword.HEXPROOF);
|
||||
} else return !keyword.equals("Defender");
|
||||
if (card.hasKeyword(Keyword.SHROUD) || card.hasKeyword(Keyword.HEXPROOF)) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("Defender")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1643,11 +1650,17 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
if (keyword.endsWith("CARDNAME can't attack.") || keyword.equals("Defender")
|
||||
|| keyword.endsWith("CARDNAME can't attack or block.")) {
|
||||
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
|
||||
if (!ComputerUtilCombat.canAttackNextTurn(card) || card.getNetCombatDamage() < 1) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.endsWith("CARDNAME attacks each turn if able.") || keyword.endsWith("CARDNAME attacks each combat if able.")) {
|
||||
return ComputerUtilCombat.canAttackNextTurn(card) && CombatUtil.canBlock(card, true) && !ai.getCreaturesInPlay().isEmpty();
|
||||
if (!ComputerUtilCombat.canAttackNextTurn(card) || !CombatUtil.canBlock(card, true) || ai.getCreaturesInPlay().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.endsWith("CARDNAME can't block.") || keyword.contains("CantBlock")) {
|
||||
return CombatUtil.canBlock(card, true);
|
||||
if (!CombatUtil.canBlock(card, true)) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) {
|
||||
for (SpellAbility ability : card.getSpellAbilities()) {
|
||||
if (ability.isAbility()) {
|
||||
@@ -1656,12 +1669,18 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
return false;
|
||||
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")) {
|
||||
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
|
||||
if (!ComputerUtilCombat.canAttackNextTurn(card) || card.getNetCombatDamage() < 1) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.endsWith("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
||||
|| keyword.endsWith("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
||||
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 2;
|
||||
if (!ComputerUtilCombat.canAttackNextTurn(card) || card.getNetCombatDamage() < 2) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.endsWith("CARDNAME doesn't untap during your untap step.")) {
|
||||
return !card.isUntapped();
|
||||
if (card.isUntapped()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1685,8 +1704,12 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sa.getHostCard().isEquipment() && ComputerUtilCard.isUselessCreature(ai, c)) {
|
||||
// useless to equip a creature that can't attack or block.
|
||||
return !sa.getHostCard().isEquipment() || !ComputerUtilCard.isUselessCreature(ai, c);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Card doPumpOrCurseAILogic(final Player ai, final SpellAbility sa, final List<Card> list, final String type) {
|
||||
@@ -1732,12 +1755,12 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
return attachToCardAIPreferences(ai, sa, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
return attachToPlayerAIPreferences(ai, sa, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
@@ -16,7 +17,7 @@ public class BalanceAi extends SpellAbilityAi {
|
||||
|
||||
int diff = 0;
|
||||
// TODO Add support for multiplayer logic
|
||||
final Player opp = aiPlayer.getWeakestOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
||||
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public class BecomesBlockedAi extends SpellAbilityAi {
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
list = CardLists.getNotKeyword(list, Keyword.TRAMPLE);
|
||||
|
||||
while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) {
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
Card choice = null;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canTgtCreature()) {
|
||||
List<Card> list = CardLists.getTargetableCards(aiPlayer.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), sa);
|
||||
List<Card> list = CardLists.getTargetableCards(ComputerUtil.getOpponentFor(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
@@ -52,7 +50,7 @@ public final class BondAi extends SpellAbilityAi {
|
||||
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
return ComputerUtilCard.getBestCreatureAI(options);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ChangeCombatantsAi extends SpellAbilityAi {
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
// TODO: Extend this if possible for cards that have this as an activated ability
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory || canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (logic.equals("WeakestOppExceptCtrl")) {
|
||||
PlayerCollection targetableOpps = aiPlayer.getOpponents();
|
||||
targetableOpps.remove(sa.getHostCard().getController());
|
||||
if (targetableOpps.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
PlayerCollection targetableOpps = new PlayerCollection();
|
||||
for (GameEntity p : options) {
|
||||
if (p instanceof Player && !p.equals(sa.getHostCard().getController())) {
|
||||
Player pp = (Player)p;
|
||||
if (pp.isOpponentOf(ai)) {
|
||||
targetableOpps.add(pp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Player weakestTargetableOpp = targetableOpps.filter(PlayerPredicates.isTargetableBy(sa))
|
||||
.min(PlayerPredicates.compareByLife());
|
||||
|
||||
return (T)weakestTargetableOpp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.getTargets().size() != 0) {
|
||||
if (sa.getTargets().getNumTargeted() != 0) {
|
||||
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
||||
return true;
|
||||
}
|
||||
@@ -80,7 +80,7 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
||||
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
||||
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
||||
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
||||
&& topSa.getTargets().contains(aiPlayer)) {
|
||||
&& topSa.getTargets().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
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,55 +1,23 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ai.AiBlockController;
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialAiLogic;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.ai.*;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostPutCounter;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -58,7 +26,9 @@ import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ChangeZoneAi extends SpellAbilityAi {
|
||||
/*
|
||||
@@ -74,11 +44,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
Card host = sa.getHostCard();
|
||||
|
||||
if (host != null && host.hasSVar("AIPreferenceOverride")) {
|
||||
if (sa.getHostCard() != null && sa.getHostCard().hasSVar("AIPreferenceOverride")) {
|
||||
// currently used by SacAndUpgrade logic, might need simplification
|
||||
host.removeSVar("AIPreferenceOverride");
|
||||
sa.getHostCard().removeSVar("AIPreferenceOverride");
|
||||
}
|
||||
|
||||
if (aiLogic.equals("BeforeCombat")) {
|
||||
@@ -92,7 +60,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
} else if (aiLogic.equals("PriorityOptionalCost")) {
|
||||
boolean highPriority = false;
|
||||
// if we have more than one of these in hand, might not be worth waiting for optional cost payment on the additional copy
|
||||
highPriority |= CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals(host.getName())).size() > 1;
|
||||
highPriority |= CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals(sa.getHostCard().getName())).size() > 1;
|
||||
// if we are in danger in combat, no need to wait to pay the optional cost
|
||||
highPriority |= ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat());
|
||||
@@ -102,65 +70,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (aiLogic.equals("NoSameCreatureType")) {
|
||||
final List<ZoneType> origin = Lists.newArrayList();
|
||||
if (sa.hasParam("Origin")) {
|
||||
origin.addAll(ZoneType.listValueOf(sa.getParam("Origin")));
|
||||
} else if (sa.hasParam("TgtZone")) {
|
||||
origin.addAll(ZoneType.listValueOf(sa.getParam("TgtZone")));
|
||||
}
|
||||
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(origin),
|
||||
sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
|
||||
final List<String> creatureTypes = Lists.newArrayList();
|
||||
for (Card c : list) {
|
||||
creatureTypes.addAll(c.getType().getCreatureTypes());
|
||||
}
|
||||
|
||||
for (String type : creatureTypes) {
|
||||
int freq = Collections.frequency(creatureTypes, type);
|
||||
if (freq > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (aiLogic.equals("Pongify")) {
|
||||
return SpecialAiLogic.doPongifyLogic(ai, sa);
|
||||
} else if (aiLogic.equals("Ashiok")) {
|
||||
final int loyalty = host.getCurrentLoyalty() - 1;
|
||||
CardCollectionView choices = new CardCollection();
|
||||
for (int i = loyalty; i >= 0; i--) {
|
||||
sa.setXManaCostPaid(i);
|
||||
choices = ai.getGame().getCardsIn(ZoneType.listValueOf(sa.getParam("Origin")));
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("ChangeType"), host.getController(), host, sa);
|
||||
if (!choices.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (aiLogic.equals("BestCard")) {
|
||||
CardCollectionView choices = ai.getGame().getCardsIn(ZoneType.listValueOf(sa.getParam("Origin")));
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("ChangeType"), host.getController(), host, sa);
|
||||
if (!choices.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
} else if (aiLogic.startsWith("DiscardAllAndRetExiled")) {
|
||||
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(host)).size();
|
||||
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||
|
||||
// minimum card advantage unless the hand will be fully reloaded
|
||||
int minAdv = aiLogic.contains(".minAdv") ? Integer.parseInt(aiLogic.substring(aiLogic.indexOf(".minAdv") + 7)) : 0;
|
||||
|
||||
if (numExiledWithSrc > curHandSize) {
|
||||
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(host)) {
|
||||
// Try to gain some card advantage if the card will die anyway
|
||||
// TODO: ideally, should evaluate the hand value and not discard good hands to it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
|
||||
}
|
||||
|
||||
return super.checkAiLogic(ai, sa, aiLogic);
|
||||
@@ -187,25 +96,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return SpecialCardAi.LivingDeath.consider(aiPlayer, sa);
|
||||
} else if (aiLogic.equals("TheScarabGod")) {
|
||||
return SpecialCardAi.TheScarabGod.consider(aiPlayer, sa);
|
||||
} else if (aiLogic.equals("SorinVengefulBloodlord")) {
|
||||
return SpecialCardAi.SorinVengefulBloodlord.consider(aiPlayer, sa);
|
||||
} else if (aiLogic.equals("Intuition")) {
|
||||
// This logic only fills the multiple cards array, the decision to play is made
|
||||
// separately in hiddenOriginCanPlayAI later.
|
||||
multipleCardsToChoose = SpecialCardAi.Intuition.considerMultiple(aiPlayer, sa);
|
||||
} else if (aiLogic.equals("MazesEnd")) {
|
||||
return SpecialCardAi.MazesEnd.consider(aiPlayer, sa);
|
||||
} else if (aiLogic.equals("Pongify")) {
|
||||
return sa.isTargetNumberValid(); // Pre-targeted in checkAiLogic
|
||||
} else if (aiLogic.equals("Ashiok")) {
|
||||
return true; // If checkAiLogic returns true, then we should be good to go
|
||||
} else if (aiLogic.equals("BestCard")) {
|
||||
return true; // If checkAiLogic returns true, then we should be good to go
|
||||
} else if (aiLogic.startsWith("DiscardAllAndRetExiled")) {
|
||||
return true; // If checkAiLogic returns true, then we should be good to go
|
||||
}
|
||||
}
|
||||
|
||||
if (isHidden(sa)) {
|
||||
return hiddenOriginCanPlayAI(aiPlayer, sa);
|
||||
}
|
||||
@@ -306,7 +202,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
ZoneType origin = null;
|
||||
final Player opponent = ai.getWeakestOpponent();
|
||||
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||
|
||||
if (sa.hasParam("Origin")) {
|
||||
@@ -331,7 +227,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
for (final CostPart part : abCost.getCostParts()) {
|
||||
if (part instanceof CostDiscard) {
|
||||
CostDiscard cd = (CostDiscard) part;
|
||||
@@ -374,10 +270,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// don't play if the conditions aren't met, unless it would trigger a beneficial sub-condition
|
||||
if (!activateForCost && !sa.metConditions()) {
|
||||
if (!activateForCost && !sa.getConditions().areMet(sa)) {
|
||||
final AbilitySub abSub = sa.getSubAbility();
|
||||
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
|
||||
if (!abSub.metConditions()) {
|
||||
if (!abSub.getConditions().areMet(abSub)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -409,10 +305,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
String type = sa.getParam("ChangeType");
|
||||
if (type != null) {
|
||||
if (type.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
if (type.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setXManaCostPaid(xPay);
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
type = type.replace("X", Integer.toString(xPay));
|
||||
}
|
||||
}
|
||||
@@ -420,19 +316,16 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
for (final Player p : pDefined) {
|
||||
CardCollectionView list = p.getCardsIn(origin);
|
||||
|
||||
// remove cards that won't be seen if library can't be searched
|
||||
if (!ai.canSearchLibraryWith(sa, p)) {
|
||||
list = CardLists.filter(list, Predicates.not(CardPredicates.inZone(ZoneType.Library)));
|
||||
}
|
||||
|
||||
if (type != null && p == ai) {
|
||||
// AI only "knows" about his information
|
||||
list = CardLists.getValidCards(list, type, source.getController(), source, sa);
|
||||
list = CardLists.getValidCards(list, type, source.getController(), source);
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (c.getType().isLegendary()) {
|
||||
return !ai.isCardInPlay(c.getName());
|
||||
if (ai.isCardInPlay(c.getName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -440,7 +333,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
// TODO: prevent ai seaching its own library when Ob Nixilis, Unshackled is in play
|
||||
if (origin != null && origin.isKnown()) {
|
||||
list = CardLists.getValidCards(list, type, source.getController(), source, sa);
|
||||
list = CardLists.getValidCards(list, type, source.getController(), source);
|
||||
}
|
||||
|
||||
if (!activateForCost && list.isEmpty()) {
|
||||
@@ -454,11 +347,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
String num = sa.getParam("ChangeNum");
|
||||
if (num != null) {
|
||||
if (num.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
if (num.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
xPay = Math.min(xPay, list.size());
|
||||
sa.setXManaCostPaid(xPay);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,7 +407,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
// if putting cards from hand to library and parent is drawing cards
|
||||
// make sure this will actually do something:
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Player opp = aiPlayer.getWeakestOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
if (tgt != null && tgt.canTgtPlayer()) {
|
||||
boolean isCurse = sa.isCurse();
|
||||
if (isCurse && sa.canTarget(opp)) {
|
||||
@@ -544,6 +437,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
// Fetching should occur fairly often as it helps cast more spells, and
|
||||
// have access to more mana
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (sa.hasParam("AILogic")) {
|
||||
if (sa.getParam("AILogic").equals("Never")) {
|
||||
/*
|
||||
@@ -557,23 +452,23 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
List<ZoneType> origin = new ArrayList<>();
|
||||
List<ZoneType> origin = new ArrayList<ZoneType>();
|
||||
if (sa.hasParam("Origin")) {
|
||||
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
||||
}
|
||||
|
||||
// this works for hidden because the mana is paid first.
|
||||
final String type = sa.getParam("ChangeType");
|
||||
if (type != null && type.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
if (type != null && type.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setXManaCostPaid(xPay);
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
Iterable<Player> pDefined;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if ((tgt != null) && tgt.canTgtPlayer()) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (sa.isCurse()) {
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
@@ -632,7 +527,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
*/
|
||||
private static Card basicManaFixing(final Player ai, final List<Card> list) { // Search for a Basic Land
|
||||
final CardCollectionView combined = CardCollection.combine(ai.getCardsIn(ZoneType.Battlefield), ai.getCardsIn(ZoneType.Hand));
|
||||
final List<String> basics = new ArrayList<>();
|
||||
final List<String> basics = new ArrayList<String>();
|
||||
|
||||
// what types can I go get?
|
||||
for (final String name : MagicColor.Constant.BASIC_LANDS) {
|
||||
@@ -692,7 +587,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
*/
|
||||
private static Card chooseCreature(final Player ai, CardCollection list) {
|
||||
// Creating a new combat for testing purposes.
|
||||
final Player opponent = ai.getWeakestOpponent();
|
||||
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
Combat combat = new Combat(opponent);
|
||||
for (Card att : opponent.getCreaturesInPlay()) {
|
||||
combat.addAttacker(att, ai);
|
||||
@@ -776,8 +671,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// only use blink or bounce effects
|
||||
if (!(destination.equals(ZoneType.Exile)
|
||||
&& (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone || "DelayedBlink".equals(sa.getParam("AILogic"))))
|
||||
if (!(destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone))
|
||||
&& !destination.equals(ZoneType.Hand)) {
|
||||
return false;
|
||||
}
|
||||
@@ -811,7 +705,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
final AbilitySub subAb = sa.getSubAbility();
|
||||
return subAb == null || SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(ai, subAb);
|
||||
if (subAb != null && !SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(ai, subAb)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -915,6 +813,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
final AbilitySub abSub = sa.getSubAbility();
|
||||
@@ -928,19 +827,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
// X controls the minimum targets
|
||||
if ("X".equals(sa.getTargetRestrictions().getMinTargets()) && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
|
||||
// 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);
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(origin), tgt.getValidTgts(), ai, source, sa);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
||||
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, true);
|
||||
if (sa.hasParam("AITgtsOnlyBetterThanSelf")) {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
@@ -973,7 +864,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
//System.out.println("isPreferredTarget ok " + list);
|
||||
}
|
||||
|
||||
if (list.size() < sa.getMinTargets()) {
|
||||
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -998,7 +889,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// Combat bouncing
|
||||
if (sa.getMinTargets() <= 1) {
|
||||
if (tgt.getMinTargets(sa.getHostCard(), sa) <= 1) {
|
||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
Combat currCombat = game.getCombat();
|
||||
CardCollection attackers = currCombat.getAttackers();
|
||||
@@ -1011,7 +902,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
&& !currCombat.getBlockers(attacker).isEmpty()) {
|
||||
ComputerUtilCard.sortByEvaluateCreature(blockers);
|
||||
Combat combat = new Combat(ai);
|
||||
combat.addAttacker(attacker, ai.getWeakestOpponent());
|
||||
combat.addAttacker(attacker, ComputerUtil.getOpponentFor(ai));
|
||||
for (Card blocker : blockers) {
|
||||
combat.addBlocker(attacker, blocker);
|
||||
}
|
||||
@@ -1040,8 +931,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
// if it's blink or bounce, try to save my about to die stuff
|
||||
final boolean blink = (destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger
|
||||
|| "DelayedBlink".equals(sa.getParam("AILogic")) || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered"))));
|
||||
if ((destination.equals(ZoneType.Hand) || blink) && (sa.getMinTargets() <= 1)) {
|
||||
|| (subApi == ApiType.ChangeZone && subAffected.equals("Remembered"))));
|
||||
if ((destination.equals(ZoneType.Hand) || blink) && (tgt.getMinTargets(sa.getHostCard(), sa) <= 1)) {
|
||||
// save my about to die stuff
|
||||
Card tobounce = canBouncePermanent(ai, sa, list);
|
||||
if (tobounce != null) {
|
||||
@@ -1051,7 +942,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
sa.getTargets().add(tobounce);
|
||||
|
||||
boolean saheeliFelidarCombo = ComputerUtilAbility.getAbilitySourceName(sa).equals("Felidar Guardian")
|
||||
boolean saheeliFelidarCombo = sa.getHostCard().getName().equals("Felidar Guardian")
|
||||
&& tobounce.getName().equals("Saheeli Rai")
|
||||
&& 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;
|
||||
@@ -1071,7 +962,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
for (Card aura : c.getEnchantedBy()) {
|
||||
return aura.getController().isOpponentOf(ai);
|
||||
if (aura.getController().isOpponentOf(ai)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (blink) {
|
||||
return c.isToken();
|
||||
@@ -1146,7 +1041,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
if (destination.equals(ZoneType.Exile) || origin.contains(ZoneType.Battlefield)) {
|
||||
|
||||
// don't rush bouncing stuff when not going to attack
|
||||
if (!immediately && game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
if (!immediately && sa.getPayCosts() != null
|
||||
&& game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& game.getPhaseHandler().isPlayerTurn(ai)
|
||||
&& ai.getCreaturesInPlay().isEmpty()) {
|
||||
return false;
|
||||
@@ -1177,17 +1073,13 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
// Only care about combatants during combat
|
||||
if (game.getPhaseHandler().inCombat() && origin.contains(ZoneType.Battlefield)) {
|
||||
CardCollection newList = CardLists.getValidCards(list, "Card.attacking,Card.blocking", null, null, null);
|
||||
CardCollection newList = CardLists.getValidCards(list, "Card.attacking,Card.blocking", null, null);
|
||||
if (!newList.isEmpty() || !sa.isTrigger()) {
|
||||
list = newList;
|
||||
}
|
||||
}
|
||||
|
||||
boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.usesTargeting()
|
||||
&& sa.getMinTargets() == 0
|
||||
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class);
|
||||
|
||||
if (list.isEmpty() && !doWithoutTarget) {
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1195,12 +1087,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
// the Unless cost (for example, Erratic Portal)
|
||||
list.removeAll(getSafeTargetsIfUnlessCostPaid(ai, sa, list));
|
||||
|
||||
if (!mandatory && list.size() < sa.getTargetRestrictions().getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (!mandatory && list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// target loop
|
||||
while (sa.canAddMoreTarget()) {
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
// AI Targeting
|
||||
Card choice = null;
|
||||
|
||||
@@ -1229,7 +1121,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
//option to hold removal instead only applies for single targeted removal
|
||||
if (!immediately && sa.getMaxTargets() == 1) {
|
||||
if (!immediately && tgt.getMaxTargets(source, sa) == 1) {
|
||||
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, destination)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1265,21 +1157,17 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
if (choice == null) { // can't find anything left
|
||||
if (sa.getTargets().size() == 0 || !sa.isTargetNumberValid()) {
|
||||
if (sa.getTargets().getNumTargeted() == 0 || sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (!mandatory) {
|
||||
sa.resetTargets();
|
||||
}
|
||||
if (!doWithoutTarget) {
|
||||
return false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!sa.isTrigger() && !ComputerUtil.shouldCastLessThanMax(ai, source)) {
|
||||
boolean aiTgtsOK = false;
|
||||
if (sa.hasParam("AIMinTgts")) {
|
||||
int minTgts = Integer.parseInt(sa.getParam("AIMinTgts"));
|
||||
if (sa.getTargets().size() >= minTgts) {
|
||||
if (sa.getTargets().getNumTargeted() >= minTgts) {
|
||||
aiTgtsOK = true;
|
||||
}
|
||||
}
|
||||
@@ -1299,23 +1187,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
// if max power exceeded, do not choose this card (but keep looking for other options)
|
||||
if (sa.hasParam("MaxTotalTargetPower")) {
|
||||
if (choice.getNetPower() > sa.getTargetRestrictions().getMaxTotalPower(choice, sa) -sa.getTargets().getTotalTargetedPower()) {
|
||||
list.remove(choice);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// honor the Same Creature Type restriction
|
||||
if (sa.getTargetRestrictions().isWithSameCreatureType()) {
|
||||
Card firstTarget = sa.getTargetCard();
|
||||
if (firstTarget != null && !choice.sharesCreatureTypeWith(firstTarget)) {
|
||||
list.remove(choice);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
@@ -1354,7 +1225,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
// filter out untargetables
|
||||
CardCollectionView aiPermanents = CardLists
|
||||
.filterControlledBy(list, ai);
|
||||
CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS);
|
||||
|
||||
// Felidar Guardian + Saheeli Rai combo support
|
||||
if (sa.getHostCard().getName().equals("Felidar Guardian")) {
|
||||
@@ -1370,7 +1240,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
final List<GameObject> objects = ComputerUtil
|
||||
.predictThreatenedObjects(ai, sa);
|
||||
|
||||
final List<Card> threatenedTargets = new ArrayList<>();
|
||||
final List<Card> threatenedTargets = new ArrayList<Card>();
|
||||
|
||||
for (final Card c : aiPermanents) {
|
||||
if (objects.contains(c)) {
|
||||
@@ -1398,33 +1268,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reload planeswalkers
|
||||
else if (!aiPlaneswalkers.isEmpty() && (sa.getHostCard().isSorcery() || !game.getPhaseHandler().isPlayerTurn(ai))) {
|
||||
int maxLoyaltyToConsider = 2;
|
||||
int loyaltyDiff = 2;
|
||||
int chance = 30;
|
||||
if (ai.getController().isAI()) {
|
||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||
maxLoyaltyToConsider = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY);
|
||||
loyaltyDiff = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF);
|
||||
chance = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE);
|
||||
}
|
||||
if (MyRandom.percentTrue(chance)) {
|
||||
Collections.sort(aiPlaneswalkers, new Comparator<Card>() {
|
||||
@Override
|
||||
public int compare(final Card a, final Card b) {
|
||||
return a.getCounters(CounterEnumType.LOYALTY) - b.getCounters(CounterEnumType.LOYALTY);
|
||||
}
|
||||
});
|
||||
for (Card pw : aiPlaneswalkers) {
|
||||
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
||||
int freshLoyalty = Integer.valueOf(pw.getCurrentState().getBaseLoyalty());
|
||||
if (freshLoyalty - curLoyalty >= loyaltyDiff && curLoyalty <= maxLoyaltyToConsider) {
|
||||
return pw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1436,31 +1279,46 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), ai, source, sa);
|
||||
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(origin), tgt.getValidTgts(), ai, source, sa);
|
||||
|
||||
// Narrow down the list:
|
||||
if (origin.equals(ZoneType.Battlefield)) {
|
||||
// filter out untargetables
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
list.removeAll(sa.getTargets().getTargetCards());
|
||||
// if Destination is hand, either bounce opponents dangerous stuff
|
||||
// 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()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// target loop
|
||||
while (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
// AI Targeting
|
||||
Card choice = null;
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
Card mostExpensivePermanent = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false);
|
||||
if (mostExpensivePermanent.isCreature()
|
||||
&& (destination.equals(ZoneType.Battlefield) || tgt.getZone().contains(ZoneType.Battlefield))) {
|
||||
if (ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false).isCreature()
|
||||
&& (destination.equals(ZoneType.Battlefield) || origin.equals(ZoneType.Battlefield))) {
|
||||
// if a creature is most expensive take the best
|
||||
choice = ComputerUtilCard.getBestCreatureToBounceAI(list);
|
||||
} else if (destination.equals(ZoneType.Battlefield) || tgt.getZone().contains(ZoneType.Battlefield)) {
|
||||
choice = mostExpensivePermanent;
|
||||
} else if (destination.equals(ZoneType.Battlefield) || origin.equals(ZoneType.Battlefield)) {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false);
|
||||
} else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) {
|
||||
List<Card> nonLands = CardLists.getNotType(list, "Land");
|
||||
// Prefer to pull a creature, generally more useful for AI.
|
||||
@@ -1492,15 +1350,16 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
if (choice == null) { // can't find anything left
|
||||
if (sa.getTargets().size() == 0 || sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (sa.getTargets().getNumTargeted() == 0 || sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!ComputerUtil.shouldCastLessThanMax(ai, source)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
@@ -1521,14 +1380,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
* @return a boolean.
|
||||
*/
|
||||
private static boolean knownOriginTriggerAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if ("DeathgorgeScavenger".equals(logic)) {
|
||||
if ("DeathgorgeScavenger".equals(sa.getParam("AILogic"))) {
|
||||
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
||||
} else if ("ExtraplanarLens".equals(logic)) {
|
||||
} else if ("ExtraplanarLens".equals(sa.getParam("AILogic"))) {
|
||||
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
||||
} else if ("ExileCombatThreat".equals(logic)) {
|
||||
return doExileCombatThreatLogic(ai, sa);
|
||||
}
|
||||
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
@@ -1538,12 +1393,16 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
if (!list.isEmpty()) {
|
||||
final Card attachedTo = list.get(0);
|
||||
// This code is for the Dragon auras
|
||||
return !attachedTo.getController().isOpponentOf(ai);
|
||||
if (attachedTo.getController().isOpponentOf(ai)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (isPreferredTarget(ai, sa, mandatory, true)) {
|
||||
// do nothing
|
||||
} else return isUnpreferredTarget(ai, sa, mandatory);
|
||||
} else if (!isUnpreferredTarget(ai, sa, mandatory)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1556,20 +1415,16 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
String logic = sa.getParam("AILogic");
|
||||
if ("NeverBounceItself".equals(logic)) {
|
||||
Card source = sa.getHostCard();
|
||||
if (fetchList.contains(source) && (fetchList.size() > 1 || !sa.getRootAbility().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.
|
||||
fetchList.remove(source);
|
||||
}
|
||||
} else if ("WorstCard".equals(logic)) {
|
||||
return ComputerUtilCard.getWorstAI(fetchList);
|
||||
} else if ("BestCard".equals(logic)) {
|
||||
return ComputerUtilCard.getBestAI(fetchList); // generally also means the most expensive one or close to it
|
||||
} else if ("Mairsil".equals(logic)) {
|
||||
return SpecialCardAi.MairsilThePretender.considerCardFromList(fetchList);
|
||||
} else if ("SurvivalOfTheFittest".equals(logic)) {
|
||||
return SpecialCardAi.SurvivalOfTheFittest.considerCardToGet(decider, sa);
|
||||
} else if ("MazesEnd".equals(logic)) {
|
||||
return SpecialCardAi.MazesEnd.considerCardToGet(decider, sa);
|
||||
} else if ("Intuition".equals(logic)) {
|
||||
if (!multipleCardsToChoose.isEmpty()) {
|
||||
Card choice = multipleCardsToChoose.get(0);
|
||||
@@ -1597,7 +1452,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (c.getType().isLegendary()) {
|
||||
return !decider.isCardInPlay(c.getName());
|
||||
if (decider.isCardInPlay(c.getName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1606,7 +1463,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
fetchList = CardLists.filter(fetchList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !ComputerUtilCard.isCardRemAIDeck(c) && !ComputerUtilCard.isCardRemRandomDeck(c);
|
||||
if (ComputerUtilCard.isCardRemAIDeck(c) || ComputerUtilCard.isCardRemRandomDeck(c)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1617,14 +1477,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
if (player.isOpponentOf(decider)) {
|
||||
c = ComputerUtilCard.getBestAI(fetchList);
|
||||
} else {
|
||||
if (!sa.hasParam("Mandatory") && origin.contains(ZoneType.Battlefield) && sa.hasParam("ChangeNum")) {
|
||||
// exclude tokens, they won't come back, and enchanted stuff, since auras will go away
|
||||
fetchList = prefilterOwnListForBounceAnyNum(fetchList, decider);
|
||||
if (fetchList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
c = ComputerUtilCard.getWorstAI(fetchList);
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Temur Sabertooth")) {
|
||||
Card tobounce = canBouncePermanent(player, sa, fetchList);
|
||||
@@ -1693,57 +1545,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return c;
|
||||
}
|
||||
|
||||
private static CardCollection prefilterOwnListForBounceAnyNum(CardCollection fetchList, Player decider) {
|
||||
fetchList = CardLists.filter(fetchList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card card) {
|
||||
if (card.isToken()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (card.isCreature() && ComputerUtilCard.isUselessCreature(decider, card)) {
|
||||
return true;
|
||||
} else if (card.isEquipped()) {
|
||||
return false;
|
||||
} else if (card.isEnchanted()) {
|
||||
for (Card enc : card.getEnchantedBy()) {
|
||||
if (enc.getOwner().isOpponentOf(decider)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (card.hasCounters()) {
|
||||
if (card.isPlaneswalker()) {
|
||||
int maxLoyaltyToConsider = 2;
|
||||
int loyaltyDiff = 2;
|
||||
int chance = 30;
|
||||
if (decider.getController().isAI()) {
|
||||
AiController aic = ((PlayerControllerAi) decider.getController()).getAi();
|
||||
maxLoyaltyToConsider = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY);
|
||||
loyaltyDiff = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF);
|
||||
chance = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE);
|
||||
}
|
||||
if (MyRandom.percentTrue(chance)) {
|
||||
int curLoyalty = card.getCounters(CounterEnumType.LOYALTY);
|
||||
int freshLoyalty = Integer.valueOf(card.getCurrentState().getBaseLoyalty());
|
||||
if (freshLoyalty - curLoyalty >= loyaltyDiff && curLoyalty <= maxLoyaltyToConsider) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (card.isCreature() && card.getCounters(CounterEnumType.M1M1) > 0) {
|
||||
return true;
|
||||
}
|
||||
return false; // TODO: improve for other counters
|
||||
} else if (card.isAura()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return fetchList;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@@ -1755,7 +1556,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
// Called when looking for creature to attach aura or equipment
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
@@ -1764,7 +1565,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
// Currently only used by Curse of Misfortunes, so this branch should never get hit
|
||||
// But just in case it does, just select the first option
|
||||
return Iterables.getFirst(options, null);
|
||||
@@ -1774,7 +1575,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
Card source = sa.getHostCard();
|
||||
String definedSac = StringUtils.split(source.getSVar("AIPreference"), "$")[1];
|
||||
|
||||
CardCollection listToSac = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), definedSac, ai, source, sa);
|
||||
CardCollection listToSac = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.restriction(definedSac.split(","), ai, source, sa));
|
||||
listToSac.sort(Collections.reverseOrder(CardLists.CmcComparatorInv));
|
||||
|
||||
CardCollection listToRet = CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), Presets.CREATURES);
|
||||
@@ -1811,7 +1612,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
String definedGoal = sa.getParam("ChangeType");
|
||||
boolean anyCMC = !definedGoal.contains(".cmc");
|
||||
|
||||
CardCollection listToSac = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), definedSac, ai, source, sa);
|
||||
CardCollection listToSac = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.restriction(definedSac.split(","), ai, source, sa));
|
||||
listToSac.sort(!sacWorst ? CardLists.CmcComparatorInv : Collections.reverseOrder(CardLists.CmcComparatorInv));
|
||||
|
||||
for (Card sacCandidate : listToSac) {
|
||||
@@ -1825,19 +1626,21 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
curGoal = definedGoal.replace("X", String.format("%d", goalCMC));
|
||||
}
|
||||
|
||||
CardCollection listGoal = CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), curGoal, ai, source, sa);
|
||||
CardCollection listGoal = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.restriction(curGoal.split(","), ai, source, sa));
|
||||
|
||||
if (!anyCMC) {
|
||||
listGoal = CardLists.getValidCards(listGoal, curGoal, source.getController(), source, sa);
|
||||
listGoal = CardLists.getValidCards(listGoal, curGoal, source.getController(), source);
|
||||
} else {
|
||||
listGoal = CardLists.getValidCards(listGoal, curGoal + (curGoal.contains(".") ? "+" : ".") + "cmcGE" + goalCMC, source.getController(), source, sa);
|
||||
listGoal = CardLists.getValidCards(listGoal, curGoal + (curGoal.contains(".") ? "+" : ".") + "cmcGE" + goalCMC, source.getController(), source);
|
||||
}
|
||||
|
||||
listGoal = CardLists.filter(listGoal, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (c.getType().isLegendary()) {
|
||||
return !ai.isCardInPlay(c.getName());
|
||||
if (ai.isCardInPlay(c.getName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1927,9 +1730,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
public boolean doReturnCommanderLogic(SpellAbility sa, Player aiPlayer) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>)sa.getReplacingObject(AbilityKey.OriginalParams);
|
||||
SpellAbility causeSa = (SpellAbility)originalParams.get(AbilityKey.Cause);
|
||||
Map<String, Object> originalParams = (Map<String, Object>)sa.getReplacingObject("OriginalParams");
|
||||
SpellAbility causeSa = (SpellAbility)originalParams.get("Cause");
|
||||
SpellAbility causeSub = null;
|
||||
|
||||
// Squee, the Immortal: easier to recast it (the call below has to be "contains" since SA is an intrinsic effect)
|
||||
@@ -1944,66 +1746,26 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
&& "Battlefield".equals(causeSub.getParam("Destination"))) {
|
||||
// A blink effect implemented using ChangeZone API
|
||||
return false;
|
||||
} else // This is an intrinsic effect that blinks the card (e.g. Obzedat, Ghost Council), no need to
|
||||
// return the commander to the Command zone.
|
||||
if (subApi == ApiType.DelayedTrigger) {
|
||||
} else if (subApi == ApiType.DelayedTrigger) {
|
||||
SpellAbility exec = causeSub.getAdditionalAbility("Execute");
|
||||
if (exec != null && exec.getApi() == ApiType.ChangeZone) {
|
||||
if ("Exile".equals(exec.getParam("Origin")) && "Battlefield".equals(exec.getParam("Destination"))) {
|
||||
// A blink effect implemented using a delayed trigger
|
||||
return !"Exile".equals(exec.getParam("Origin")) || !"Battlefield".equals(exec.getParam("Destination"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (causeSa.getHostCard() != null && causeSa.getHostCard().equals((Card)sa.getReplacingObject("Card"))
|
||||
&& causeSa.getActivatingPlayer().equals(aiPlayer)) {
|
||||
// This is an intrinsic effect that blinks the card (e.g. Obzedat, Ghost Council), no need to
|
||||
// return the commander to the Command zone.
|
||||
return false;
|
||||
}
|
||||
} else return causeSa.getHostCard() == null || !causeSa.getHostCard().equals(sa.getReplacingObject(AbilityKey.Card))
|
||||
|| !causeSa.getActivatingPlayer().equals(aiPlayer);
|
||||
}
|
||||
|
||||
// Normally we want the commander back in Command zone to recast him later
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static CardCollection getSafeTargetsIfUnlessCostPaid(Player ai, SpellAbility sa, Iterable<Card> potentialTgts) {
|
||||
// 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.
|
||||
@@ -2019,9 +1781,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
int toPay = 0;
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
setPayX = true;
|
||||
// TODO use ComputerUtilCost.getMaxXValue if able
|
||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
} else {
|
||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||
@@ -2036,7 +1797,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (setPayX) {
|
||||
sa.setXManaCostPaid(toPay);
|
||||
source.setSVar("PayX", Integer.toString(toPay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,11 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiPlayerPredicates;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -34,6 +16,8 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
@@ -51,7 +35,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
boolean aiLogicAllowsDiscard = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("DiscardAll");
|
||||
|
||||
if (!aiLogicAllowsDiscard) {
|
||||
@@ -73,11 +57,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
CardCollectionView oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
|
||||
CardCollectionView computerType = ai.getCardsIn(origin);
|
||||
|
||||
// remove cards that won't be seen in AI's own library if it can't be searched
|
||||
if (!ai.canSearchLibraryWith(sa, ai)) {
|
||||
computerType = CardLists.filter(computerType, Predicates.not(CardPredicates.inZone(ZoneType.Library)));
|
||||
}
|
||||
|
||||
// Ugin check need to be done before filterListByType because of ChosenX
|
||||
// Ugin AI: always try to sweep before considering +1
|
||||
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
||||
return SpecialCardAi.UginTheSpiritDragon.considerPWAbilityPriority(ai, sa, origin, oppType, computerType);
|
||||
@@ -245,8 +225,25 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
&& !ComputerUtil.isPlayingReanimator(ai);
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Exile)) {
|
||||
// TODO: nothing to do here at the moment
|
||||
return false;
|
||||
String logic = sa.getParam("AILogic");
|
||||
|
||||
if (logic != null && logic.startsWith("DiscardAllAndRetExiled")) {
|
||||
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
|
||||
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||
|
||||
// minimum card advantage unless the hand will be fully reloaded
|
||||
int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0;
|
||||
|
||||
if (numExiledWithSrc > curHandSize) {
|
||||
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
|
||||
// Try to gain some card advantage if the card will die anyway
|
||||
// TODO: ideally, should evaluate the hand value and not discard good hands to it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Stack)) {
|
||||
// time stop can do something like this:
|
||||
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
|
||||
@@ -332,8 +329,11 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
return true;
|
||||
|
||||
// if AI creature is better than Human Creature
|
||||
return ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard
|
||||
.evaluateCreatureList(humanCards);
|
||||
if (ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard
|
||||
.evaluateCreatureList(humanCards)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -435,21 +435,29 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
if (sa.getParam("GainControl") != null) {
|
||||
// Check if the cards are valuable enough
|
||||
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
||||
return (ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
|
||||
.evaluateCreatureList(humanType)) >= 1;
|
||||
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
|
||||
.evaluateCreatureList(humanType)) < 1) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human
|
||||
// permanents are less valuable
|
||||
else return (ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
||||
.evaluatePermanentList(humanType)) >= 1;
|
||||
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
||||
.evaluatePermanentList(humanType)) < 1) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// don't activate if human gets more back than AI does
|
||||
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
|
||||
return ComputerUtilCard.evaluateCreatureList(computerType) > ComputerUtilCard
|
||||
.evaluateCreatureList(humanType);
|
||||
if (ComputerUtilCard.evaluateCreatureList(computerType) <= ComputerUtilCard
|
||||
.evaluateCreatureList(humanType)) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human
|
||||
// permanents are less valuable
|
||||
else return ComputerUtilCard.evaluatePermanentList(computerType) > ComputerUtilCard
|
||||
.evaluatePermanentList(humanType);
|
||||
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= ComputerUtilCard
|
||||
.evaluatePermanentList(humanType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -20,6 +10,8 @@ import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CharmAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
@@ -28,11 +20,8 @@ public class CharmAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
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;
|
||||
|
||||
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
|
||||
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
|
||||
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
|
||||
@@ -243,7 +232,7 @@ public class CharmAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents, Map<String, Object> params) {
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents) {
|
||||
return Aggregates.random(opponents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,15 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -20,6 +21,7 @@ import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -64,21 +66,27 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
}
|
||||
CardCollectionView choices = ai.getGame().getCardsIn(choiceZone);
|
||||
if (sa.hasParam("Choices")) {
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host, sa);
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||
}
|
||||
if (sa.hasParam("TargetControls")) {
|
||||
choices = CardLists.filterControlledBy(choices, ai.getOpponents());
|
||||
}
|
||||
if (aiLogic.equals("AtLeast1") || aiLogic.equals("OppPreferred")) {
|
||||
return !choices.isEmpty();
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
|
||||
return choices.size() >= 2;
|
||||
if (choices.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) {
|
||||
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
||||
|
||||
choices = CardLists.getValidCards(choices, filter, host.getController(), host, sa);
|
||||
return !choices.isEmpty();
|
||||
choices = CardLists.getValidCards(choices, filter, host.getController(), host);
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("Never")) {
|
||||
return false;
|
||||
} else if (aiLogic.equals("NeedsPrevention")) {
|
||||
@@ -96,12 +104,30 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
||||
}
|
||||
});
|
||||
return !choices.isEmpty();
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("Ashiok")) {
|
||||
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
|
||||
for (int i = loyalty; i >= 0; i--) {
|
||||
host.setSVar("ChosenX", "Number$" + i);
|
||||
choices = ai.getGame().getCardsIn(choiceZone);
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||
if (!choices.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("RandomNonLand")) {
|
||||
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
|
||||
if (CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("Duneblast")) {
|
||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||
CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay();
|
||||
CardCollection oppCreatures = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
||||
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
|
||||
|
||||
@@ -114,14 +140,10 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
aiCreatures.remove(chosen);
|
||||
int minGain = 200;
|
||||
|
||||
return (ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) < ComputerUtilCard
|
||||
.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()));
|
||||
if ((ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) >= ComputerUtilCard
|
||||
.evaluateCreatureList(oppCreatures)) {
|
||||
return false;
|
||||
}
|
||||
return !ownChoices.isEmpty();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -138,7 +160,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player ctrl = host.getController();
|
||||
final String logic = sa.getParam("AILogic");
|
||||
@@ -148,12 +170,6 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
} else if ("WorstCard".equals(logic)) {
|
||||
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")) {
|
||||
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
|
||||
options = CardLists.filter(options, Presets.UNTAPPED);
|
||||
@@ -172,7 +188,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
choice = null;
|
||||
}
|
||||
} else if ("RandomNonLand".equals(logic)) {
|
||||
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host, sa);
|
||||
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host);
|
||||
choice = Aggregates.random(options);
|
||||
} else if (logic.equals("Untap")) {
|
||||
final String filter = "Permanent.YouCtrl,Permanent.tapped";
|
||||
@@ -232,7 +248,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
for (SpellAbility sa : c.getAllSpellAbilities()) {
|
||||
if (sa.getPayCosts().hasTapCost()) {
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -275,8 +291,6 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
// Choose the best (hopefully the fattest, whatever) creature so that hopefully it won't die too easily
|
||||
choice = ComputerUtilCard.getBestAI(creatures);
|
||||
}
|
||||
} else if (logic.equals("NextTurnAttacker")) {
|
||||
choice = ComputerUtilCard.getBestCreatureToAttackNextTurnAI(ai, options);
|
||||
} else {
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.card.CardDb;
|
||||
import forge.card.CardRules;
|
||||
import forge.card.CardSplitType;
|
||||
@@ -27,6 +23,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
if (sa.hasParam("AILogic")) {
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
@@ -44,7 +41,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(ai.getWeakestOpponent());
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
} else {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
@@ -57,13 +54,13 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
// TODO - there is no AILogic implemented yet
|
||||
return mandatory;
|
||||
return false;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ package forge.ai.ability;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
@@ -22,6 +23,7 @@ public class ChooseColorAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
@@ -44,18 +46,22 @@ public class ChooseColorAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
// Set PayX here to maximum value.
|
||||
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
|
||||
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(x));
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("Addle".equals(sourceName)) {
|
||||
return !ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && !ai.getWeakestOpponent().getCardsIn(ZoneType.Hand).isEmpty();
|
||||
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (logic.equals("MostExcessOpponentControls")) {
|
||||
for (byte color : MagicColor.WUBRG) {
|
||||
CardCollectionView ailist = ai.getCardsIn(ZoneType.Battlefield);
|
||||
CardCollectionView opplist = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
|
||||
CardCollectionView opplist = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
ailist = CardLists.filter(ailist, CardPredicates.isColor(color));
|
||||
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));
|
||||
@@ -66,7 +72,9 @@ public class ChooseColorAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (logic.equals("MostProminentInComputerDeck")) {
|
||||
}
|
||||
|
||||
if (logic.equals("MostProminentInComputerDeck")) {
|
||||
if ("Astral Cornucopia".equals(sourceName)) {
|
||||
// activate in Main 2 hoping that the extra mana surplus will make a difference
|
||||
// if there are some nonland permanents in hand
|
||||
@@ -75,11 +83,6 @@ public class ChooseColorAi extends SpellAbilityAi {
|
||||
|
||||
return permanents.size() > 0 && ph.is(PhaseType.MAIN2, ai);
|
||||
}
|
||||
} else if (logic.equals("HighestDevotionToColor")) {
|
||||
// currently only works more or less reliably in Main2 to cast own spells
|
||||
if (!ph.is(PhaseType.MAIN2, ai)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ChooseCompanionAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
List<Card> cards = Lists.newArrayList(options);
|
||||
if (cards.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Collections.shuffle(cards);
|
||||
return cards.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@ public class ChooseDirectionAi extends SpellAbilityAi {
|
||||
CardCollection right = CardLists.filterControlledBy(all, game.getNextPlayerAfter(ai, Direction.Right));
|
||||
int leftValue = Aggregates.sum(left, CardPredicates.Accessors.fnGetCmc);
|
||||
int rightValue = Aggregates.sum(right, CardPredicates.Accessors.fnGetCmc);
|
||||
return aiValue <= leftValue && aiValue <= rightValue;
|
||||
if (aiValue > leftValue || aiValue > rightValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -46,6 +48,6 @@ public class ChooseDirectionAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory || canPlayAI(ai, sa);
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class ChooseEvenOddAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return false;
|
||||
}
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = aiPlayer.getWeakestOpponent();
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory || canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,11 +8,10 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.card.MagicColor;
|
||||
@@ -23,7 +22,7 @@ import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
@@ -32,7 +31,6 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbilityCantBeCast;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.collect.FCollection;
|
||||
@@ -52,12 +50,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if ("GideonBlackblade".equals(aiLogic)) {
|
||||
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
|
||||
} else if ("SoulEcho".equals(aiLogic)) {
|
||||
return doTriggerAINoCost(ai, sa, true);
|
||||
} else if ("Always".equals(aiLogic)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -72,12 +64,12 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return checkApiLogic(aiPlayer, sa);
|
||||
return canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
if ("CombustibleGearhulk".equals(sa.getParam("AILogic")) || "SoulEcho".equals(sa.getParam("AILogic"))) {
|
||||
if ("CombustibleGearhulk".equals(sa.getParam("AILogic"))) {
|
||||
for (final Player p : aiPlayer.getOpponents()) {
|
||||
if (p.canBeTargetedBy(sa)) {
|
||||
sa.resetTargets();
|
||||
@@ -103,8 +95,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
return spells.get(0);
|
||||
} else if ("Random".equals(logic)) {
|
||||
return Aggregates.random(spells);
|
||||
} else if ("GideonBlackblade".equals(logic)) {
|
||||
return SpecialCardAi.GideonBlackblade.chooseSpellAbility(player, sa, spells);
|
||||
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
|
||||
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() {
|
||||
@Override
|
||||
@@ -120,7 +110,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
Cost unless = new Cost(unlessCost, false);
|
||||
SpellAbility paycost = new SpellAbility.EmptySa(sa.getHostCard(), player);
|
||||
paycost.setPayCosts(unless);
|
||||
if (ComputerUtilCost.willPayUnlessCost(sp, player, unless, false, new FCollection<>(player))
|
||||
if (ComputerUtilCost.willPayUnlessCost(sp, player, unless, false, new FCollection<Player>(player))
|
||||
&& ComputerUtilCost.canPayCost(paycost, player)) {
|
||||
return sp;
|
||||
}
|
||||
@@ -172,10 +162,10 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
// FatespinnerSkipDraw,FatespinnerSkipMain,FatespinnerSkipCombat
|
||||
if (game.getReplacementHandler().wouldPhaseBeSkipped(player, "Draw")) {
|
||||
if (player.hasKeyword("Skip your draw step.")) {
|
||||
return skipDraw;
|
||||
}
|
||||
if (game.getReplacementHandler().wouldPhaseBeSkipped(player, "BeginCombat")) {
|
||||
if (player.hasKeyword("Skip your next combat phase.")) {
|
||||
return skipCombat;
|
||||
}
|
||||
|
||||
@@ -230,11 +220,12 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
return allow;
|
||||
}
|
||||
|
||||
SpellAbility firstSpell = imprinted.getFirstSpellAbility();
|
||||
// check if something would prevent it from casting
|
||||
if (firstSpell == null || StaticAbilityCantBeCast.cantBeCastAbility(firstSpell, imprinted, owner)) {
|
||||
//if Iona does prevent from casting, allow it to draw
|
||||
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
|
||||
if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
|
||||
return allow;
|
||||
}
|
||||
}
|
||||
|
||||
if (dmg == 0) {
|
||||
// If CMC = 0, mill it!
|
||||
@@ -260,7 +251,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1);
|
||||
|
||||
// check for something which might prevent the counters to be placed on host
|
||||
if (!host.canReceiveCounters(CounterEnumType.P1P1)) {
|
||||
if (!host.canReceiveCounters(CounterType.P1P1)) {
|
||||
return tokenSA;
|
||||
}
|
||||
|
||||
@@ -272,7 +263,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
// need a copy for one with extra +1/+1 counter boost,
|
||||
// without causing triggers to run
|
||||
final Card copy = CardUtil.getLKICopy(host);
|
||||
copy.setCounters(CounterEnumType.P1P1, copy.getCounters(CounterEnumType.P1P1) + n);
|
||||
copy.setCounters(CounterType.P1P1, copy.getCounters(CounterType.P1P1) + n);
|
||||
copy.setZone(host.getZone());
|
||||
|
||||
// if host would put into the battlefield attacking
|
||||
@@ -297,10 +288,10 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
// TODO check for trigger to turn token ETB into +1/+1 counter for host
|
||||
// TODO check for trigger to turn token ETB into damage or life loss for opponent
|
||||
// in this cases Token might be prefered even if they would not survive
|
||||
final Card tokenCard = TokenAi.spawnToken(player, tokenSA);
|
||||
final Card tokenCard = TokenAi.spawnToken(player, tokenSA, true);
|
||||
|
||||
// Token would not survive
|
||||
if (!tokenCard.isCreature() || tokenCard.getNetToughness() < 1) {
|
||||
if (tokenCard.getNetToughness() < 1) {
|
||||
return counterSA;
|
||||
}
|
||||
|
||||
@@ -344,10 +335,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
|
||||
int bestGuessDamage = totalCMC * 3 / revealedCards.size();
|
||||
return life <= bestGuessDamage ? spells.get(0) : spells.get(1);
|
||||
} else if ("SoulEcho".equals(logic)) {
|
||||
Player target = sa.getTargetingPlayer();
|
||||
int life = target.getLife();
|
||||
return life < 10 ? spells.get(0) : Aggregates.random(spells);
|
||||
} else if ("Pump".equals(logic) || "BestOption".equals(logic)) {
|
||||
List<SpellAbility> filtered = Lists.newArrayList();
|
||||
// filter first for the spells which can be done
|
||||
@@ -364,8 +351,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
} else if ("Riot".equals(logic)) {
|
||||
SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1);
|
||||
return preferHasteForRiot(sa, player) ? hasteSA : counterSA;
|
||||
} else if ("CrawlingBarrens".equals(logic)) {
|
||||
return SpecialCardAi.CrawlingBarrens.considerAnimating(player, sa, spells);
|
||||
}
|
||||
return spells.get(0); // return first choice if no logic found
|
||||
}
|
||||
@@ -384,7 +369,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
game.getAction().checkStaticAbilities(false);
|
||||
|
||||
// can't gain counters, use Haste
|
||||
if (!copy.canReceiveCounters(CounterEnumType.P1P1)) {
|
||||
if (!copy.canReceiveCounters(CounterType.P1P1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -407,7 +392,9 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
final Player opp = player.getWeakestOpponent();
|
||||
if (opp != null) {
|
||||
// TODO add predict Combat Damage?
|
||||
return opp.getLife() < copy.getNetPower();
|
||||
if (opp.getLife() < copy.getNetPower()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// haste might not be good enough?
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -16,7 +17,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = aiPlayer.getWeakestOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ChoosePlayerAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
@@ -29,7 +27,7 @@ public class ChoosePlayerAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices, Map<String, Object> params) {
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices) {
|
||||
Player chosen = null;
|
||||
if ("Curse".equals(sa.getParam("AILogic"))) {
|
||||
for (Player pc : choices) {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
@@ -46,7 +47,20 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (abCost != null) {
|
||||
if (!willPayCosts(ai, sa, abCost, source)) {
|
||||
// AI currently disabled for these costs
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -54,7 +68,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
@@ -84,14 +98,17 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
|
||||
return ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) > 0;
|
||||
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
||||
return false;
|
||||
}
|
||||
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
|
||||
if (sa.hasParam("Choices")) {
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host, sa);
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||
}
|
||||
final Combat combat = game.getCombat();
|
||||
choices = CardLists.filter(choices, new Predicate<Card>() {
|
||||
@@ -103,7 +120,9 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
|
||||
}
|
||||
});
|
||||
return !choices.isEmpty();
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +132,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
||||
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
@@ -141,7 +160,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
||||
Card bestCreature = ComputerUtilCard.getBestCreatureAI(permanentSources);
|
||||
if (bestCreature != null) {
|
||||
return bestCreature;
|
||||
}
|
||||
} else {
|
||||
// No optimal creature was found above, so try to broaden the choice.
|
||||
if (!Iterables.isEmpty(options)) {
|
||||
List<Card> oppCreatures = CardLists.filter(options, Predicates.and(CardPredicates.Presets.CREATURES,
|
||||
@@ -162,6 +181,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
||||
// source) in order to choose at least something, or the game will hang.
|
||||
return game.getStack().peekAbility().getHostCard();
|
||||
}
|
||||
}
|
||||
|
||||
// Should never get here
|
||||
System.err.println("Unexpected behavior: The AI was unable to choose anything for AF ChooseSource in "
|
||||
@@ -204,7 +224,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
||||
|
||||
private static List<GameObject> getTargets(final SpellAbility sa) {
|
||||
return sa.usesTargeting() && (!sa.hasParam("Defined"))
|
||||
? Lists.newArrayList(sa.getTargets())
|
||||
? Lists.newArrayList(sa.getTargets().getTargets())
|
||||
: AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
@@ -12,7 +9,6 @@ import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardType;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
@@ -24,6 +20,7 @@ import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import java.util.List;
|
||||
|
||||
public class ChooseTypeAi extends SpellAbilityAi {
|
||||
@Override
|
||||
@@ -34,8 +31,6 @@ public class ChooseTypeAi extends SpellAbilityAi {
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
|
||||
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);
|
||||
@@ -49,12 +44,28 @@ public class ChooseTypeAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
String chosenType = chooseType(sa, aiPlayer.getCardsIn(ZoneType.Battlefield));
|
||||
CardCollectionView otb = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||
List<String> valid = Lists.newArrayList(CardType.getAllCreatureTypes());
|
||||
|
||||
String chosenType = ComputerUtilCard.getMostProminentType(otb, valid);
|
||||
if (chosenType.isEmpty()) {
|
||||
return false;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
int maxX = ComputerUtilMana.determineLeftoverMana(sa, aiPlayer);
|
||||
if (!allChangeling) {
|
||||
// Still empty, probably no creatures on board
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int maxX = ComputerUtilMana.determineMaxAffordableX(aiPlayer, sa);
|
||||
int avgPower = 0;
|
||||
|
||||
// predict the opposition
|
||||
@@ -91,7 +102,7 @@ public class ChooseTypeAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (maxX > avgPower && maxX > maxOppPower && maxX >= maxOppToughness) {
|
||||
sa.setXManaCostPaid(maxX);
|
||||
sa.setSVar("PayX", String.valueOf(maxX));
|
||||
AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
|
||||
return true;
|
||||
}
|
||||
@@ -116,40 +127,4 @@ public class ChooseTypeAi extends SpellAbilityAi {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
@@ -58,7 +56,7 @@ public class ClashAi extends SpellAbilityAi {
|
||||
* forge.game.spellability.SpellAbility, java.lang.Iterable)
|
||||
*/
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
for (Player p : options) {
|
||||
if (p.getCardsIn(ZoneType.Library).size() == 0)
|
||||
return p;
|
||||
@@ -84,7 +82,7 @@ public class ClashAi extends SpellAbilityAi {
|
||||
|
||||
PlayerCollection players = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
// use chooseSinglePlayer function to the select player
|
||||
Player chosen = chooseSinglePlayer(ai, sa, players, null);
|
||||
Player chosen = chooseSinglePlayer(ai, sa, players);
|
||||
if (chosen != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(chosen);
|
||||
@@ -106,7 +104,7 @@ public class ClashAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
return sa.getTargets().size() > 0;
|
||||
return sa.getTargets().getNumTargeted() > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
@@ -15,12 +12,16 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CloneAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = source.getGame();
|
||||
|
||||
@@ -38,13 +39,27 @@ public class CloneAi extends SpellAbilityAi {
|
||||
// TODO - add some kind of check for during human turn to answer
|
||||
// "Can I use this to block something?"
|
||||
|
||||
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
||||
PhaseHandler phase = game.getPhaseHandler();
|
||||
// don't use instant speed clone abilities outside computers
|
||||
// Combat_Begin step
|
||||
if (!phase.is(PhaseType.COMBAT_BEGIN)
|
||||
&& phase.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PhaseHandler phase = game.getPhaseHandler();
|
||||
// don't use instant speed clone abilities outside humans
|
||||
// Combat_Declare_Attackers_InstantAbility step
|
||||
if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sa.usesTargeting()) {
|
||||
// don't activate during main2 unless this effect is permanent
|
||||
if (phase.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (null == tgt) {
|
||||
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||
|
||||
boolean bFlag = false;
|
||||
@@ -124,11 +139,11 @@ public class CloneAi extends SpellAbilityAi {
|
||||
private boolean cloneTgtAI(final SpellAbility sa) {
|
||||
// Specific logic for cards
|
||||
if ("CloneAttacker".equals(sa.getParam("AILogic"))) {
|
||||
CardCollection valid = CardLists.getValidCards(sa.getHostCard().getController().getCardsIn(ZoneType.Battlefield), sa.getParam("ValidTgts"), sa.getHostCard().getController(), sa.getHostCard(), sa);
|
||||
CardCollection valid = CardLists.getValidCards(sa.getHostCard().getController().getCardsIn(ZoneType.Battlefield), sa.getParam("ValidTgts"), sa.getHostCard().getController(), sa.getHostCard());
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(valid));
|
||||
return true;
|
||||
} else if ("CloneBestCreature".equals(sa.getParam("AILogic"))) {
|
||||
CardCollection valid = CardLists.getValidCards(sa.getHostCard().getController().getGame().getCardsIn(ZoneType.Battlefield), sa.getParam("ValidTgts"), sa.getHostCard().getController(), sa.getHostCard(), sa);
|
||||
CardCollection valid = CardLists.getValidCards(sa.getHostCard().getController().getGame().getCardsIn(ZoneType.Battlefield), sa.getParam("ValidTgts"), sa.getHostCard().getController(), sa.getHostCard());
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(valid));
|
||||
return true;
|
||||
}
|
||||
@@ -170,39 +185,20 @@ public class CloneAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||
Player targetedPlayer, Map<String, Object> params) {
|
||||
|
||||
Player targetedPlayer) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player ctrl = host.getController();
|
||||
|
||||
final Card cloneTarget = getCloneTarget(sa);
|
||||
final boolean isOpp = cloneTarget.getController().isOpponentOf(sa.getActivatingPlayer());
|
||||
|
||||
final boolean isVesuva = "Vesuva".equals(host.getName());
|
||||
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
|
||||
|
||||
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||
final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
||||
|
||||
// TODO: rewrite this block so that this is done somehow more elegantly
|
||||
if (canCloneLegendary) {
|
||||
filter = filter.replace(".nonLegendary+", ".").replace(".nonLegendary", "");
|
||||
}
|
||||
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||
if (!newOptions.isEmpty()) {
|
||||
options = newOptions;
|
||||
}
|
||||
|
||||
if (sa.hasParam("AiChoiceLogic")) {
|
||||
final String logic = sa.getParam("AiChoiceLogic");
|
||||
if ("BestOppCtrl".equals(logic)) {
|
||||
options = CardLists.filterControlledBy(options, ctrl.getOpponents());
|
||||
}
|
||||
}
|
||||
|
||||
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
|
||||
|
||||
Card choice = ComputerUtilCard.getBestAI(options);
|
||||
if (isVesuva && "Vesuva".equals(choice.getName())) {
|
||||
choice = null;
|
||||
}
|
||||
@@ -210,41 +206,4 @@ public class CloneAi extends SpellAbilityAi {
|
||||
return choice;
|
||||
}
|
||||
|
||||
protected Card getCloneTarget(final SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
Card tgtCard = host;
|
||||
if (sa.hasParam("CloneTarget")) {
|
||||
final List<Card> cloneTargets = AbilityUtils.getDefinedCards(host, sa.getParam("CloneTarget"), sa);
|
||||
if (!cloneTargets.isEmpty()) {
|
||||
tgtCard = cloneTargets.get(0);
|
||||
}
|
||||
} else if (sa.hasParam("Choices") && sa.usesTargeting()) {
|
||||
tgtCard = sa.getTargets().getFirstTargetedCard();
|
||||
}
|
||||
|
||||
return tgtCard;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player, forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
|
||||
*/
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
// don't use instant speed clone abilities outside computers
|
||||
// Combat_Begin step
|
||||
if (!ph.is(PhaseType.COMBAT_BEGIN)
|
||||
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't use instant speed clone abilities outside humans
|
||||
// Combat_Declare_Attackers_InstantAbility step
|
||||
if (!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.isPlayerTurn(ai) || ph.getCombat().getAttackers().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't activate during main2 unless this effect is permanent
|
||||
return !ph.is(PhaseType.MAIN2) || sa.hasParam("Permanent");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -29,7 +30,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
|
||||
CardCollection list =
|
||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
// AI won't try to grab cards that are filtered out of AI decks on
|
||||
// purpose
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@@ -81,12 +82,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
// for TrigTwoTargets logic, only get the opponents' cards for the first target
|
||||
CardCollectionView unfilteredList = "TrigTwoTargets".equals(sa.getParam("AILogic")) ?
|
||||
aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield) :
|
||||
aiPlayer.getGame().getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
CardCollection list = CardLists.getValidCards(unfilteredList,
|
||||
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
|
||||
tgt.getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||
|
||||
// only select the cards that can be targeted
|
||||
@@ -111,51 +107,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
||||
|
||||
// add best Target
|
||||
sa.getTargets().add(best);
|
||||
|
||||
// second target needed (the AI's own worst)
|
||||
if ("TrigTwoTargets".equals(sa.getParam("AILogic"))) {
|
||||
return doTrigTwoTargetsLogic(aiPlayer, sa, best);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean doTrigTwoTargetsLogic(Player ai, SpellAbility sa, Card bestFirstTgt) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final int creatureThreshold = 100; // TODO: make this configurable from the AI profile
|
||||
final int nonCreatureThreshold = 2;
|
||||
|
||||
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield),
|
||||
tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
|
||||
// only select the cards that can be targeted
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Card aiWorst = ComputerUtilCard.getWorstAI(list);
|
||||
if (aiWorst == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aiWorst != bestFirstTgt) {
|
||||
if (bestFirstTgt.isCreature() && aiWorst.isCreature()) {
|
||||
if ((ComputerUtilCard.evaluateCreature(bestFirstTgt) > ComputerUtilCard.evaluateCreature(aiWorst) + creatureThreshold) || sa.isMandatory()) {
|
||||
sa.getTargets().add(aiWorst);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// TODO: compare non-creatures by CMC - can be improved, at least shouldn't give control of things like the Power Nine
|
||||
if ((bestFirstTgt.getCMC() > aiWorst.getCMC() + nonCreatureThreshold) || sa.isMandatory()) {
|
||||
sa.getTargets().add(aiWorst);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sa.clearTargets();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -35,12 +34,12 @@ import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
|
||||
//AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx
|
||||
@@ -55,6 +54,8 @@ import forge.util.Aggregates;
|
||||
// (as a "&"-separated list; like Haste, Sacrifice CARDNAME at EOT, any standard keyword)
|
||||
// OppChoice - set to True if opponent chooses creature (for Preacher) - not implemented yet
|
||||
// Untap - set to True if target card should untap when control is taken
|
||||
// DestroyTgt - actions upon which the tgt should be destroyed. same list as LoseControl
|
||||
// NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -76,14 +77,16 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Game game = ai.getGame();
|
||||
final PlayerCollection opponents = ai.getOpponents();
|
||||
final FCollectionView<Player> opponents = ai.getOpponents();
|
||||
|
||||
// if Defined, then don't worry about targeting
|
||||
if (tgt == null) {
|
||||
if (sa.hasParam("AllValid")) {
|
||||
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), opponents);
|
||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||
return !tgtCards.isEmpty();
|
||||
if (tgtCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
@@ -93,21 +96,20 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
sa.setTargetingPlayer(targetingPlayer);
|
||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||
}
|
||||
|
||||
if (tgt.isRandomTarget()) {
|
||||
sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false)));
|
||||
}
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
List<Player> oppList = opponents.filter(PlayerPredicates.isTargetableBy(sa));
|
||||
List<Player> oppList = Lists
|
||||
.newArrayList(Iterables.filter(opponents, PlayerPredicates.isTargetableBy(sa)));
|
||||
|
||||
if (oppList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tgt.isRandomTarget()) {
|
||||
sa.getTargets().add(Aggregates.random(oppList));
|
||||
} else {
|
||||
sa.getTargets().add(oppList.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't steal something if I can't Attack without, or prevent it from
|
||||
// blocking at least
|
||||
@@ -197,11 +199,11 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
Card t = null;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
|
||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
@@ -245,7 +247,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (t != null) {
|
||||
sa.getTargets().add(t);
|
||||
@@ -268,10 +270,11 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -279,7 +282,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
|
||||
final Game game = ai.getGame();
|
||||
if (!sa.usesTargeting()) {
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
if (sa.hasParam("AllValid")) {
|
||||
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||
@@ -293,16 +296,19 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
|
||||
}
|
||||
|
||||
return !lose.contains("EOT")
|
||||
|| !game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
|
||||
if (lose.contains("EOT")
|
||||
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return this.canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpDrawbackAI()
|
||||
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
final List<Card> cards = Lists.newArrayList();
|
||||
for (Player p : options) {
|
||||
cards.addAll(p.getCreaturesInPlay());
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactory_GainControlVariant class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
|
||||
*/
|
||||
public class ControlGainVariantAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||
|
||||
String logic = sa.getParam("AILogic");
|
||||
|
||||
if ("GainControlOwns".equals(logic)) {
|
||||
List<Card> list = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
return crd.isCreature() && !crd.getController().equals(crd.getOwner());
|
||||
}
|
||||
});
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (final Card c : list) {
|
||||
if (ai.equals(c.getController())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
Iterable<Card> otherCtrl = CardLists.filter(options, Predicates.not(CardPredicates.isController(ai)));
|
||||
if (Iterables.isEmpty(otherCtrl)) {
|
||||
return ComputerUtilCard.getWorstAI(options);
|
||||
} else {
|
||||
return ComputerUtilCard.getBestAI(otherCtrl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +1,13 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -32,11 +16,15 @@ import forge.game.player.PlayerCollection;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class CopyPermanentAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
// Card source = sa.getHostCard();
|
||||
// TODO - I'm sure someone can do this AI better
|
||||
Card source = sa.getHostCard();
|
||||
|
||||
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
@@ -50,11 +38,6 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
return ph.is(PhaseType.END_OF_TURN);
|
||||
} else if ("AtOppEOT".equals(aiLogic)) {
|
||||
return ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn() != aiPlayer;
|
||||
} else if ("DuplicatePerms".equals(aiLogic)) {
|
||||
final List<Card> valid = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||
if (valid.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
|
||||
@@ -63,7 +46,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
|
||||
if (sa.hasParam("Defined")) {
|
||||
// If there needs to be an imprinted card, don't activate the ability if nothing was imprinted yet (e.g. Mimic Vat)
|
||||
if (sa.getParam("Defined").equals("Imprinted.ExiledWithSource") && source.getImprintedCards().isEmpty()) {
|
||||
if (sa.getParam("Defined").equals("Imprinted.ExiledWithSource") && sa.getHostCard().getImprintedCards().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -77,32 +60,9 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
|
||||
if (sa.usesTargeting() && sa.hasParam("TargetingPlayer")) {
|
||||
sa.resetTargets();
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
sa.setTargetingPlayer(targetingPlayer);
|
||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||
} else if (sa.getTargetRestrictions() != null && sa.getTargetRestrictions().canTgtPlayer()) {
|
||||
if (!sa.isCurse()) {
|
||||
if (sa.canTarget(aiPlayer)) {
|
||||
sa.getTargets().add(aiPlayer);
|
||||
return true;
|
||||
} else {
|
||||
for (Player p : aiPlayer.getTeamMates(true)) {
|
||||
if (sa.canTarget(p)) {
|
||||
sa.getTargets().add(p);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (Player p : aiPlayer.getOpponents()) {
|
||||
if (sa.canTarget(p)) {
|
||||
sa.getTargets().add(p);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return this.doTriggerAINoCost(aiPlayer, sa, false);
|
||||
}
|
||||
@@ -114,7 +74,6 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
final Game game = host.getGame();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final boolean canCopyLegendary = sa.hasParam("NonLegendary");
|
||||
|
||||
|
||||
// ////
|
||||
@@ -143,7 +102,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
// target loop
|
||||
while (sa.canAddMoreTarget()) {
|
||||
if (list.isEmpty()) {
|
||||
if (!sa.isTargetNumberValid() || (sa.getTargets().size() == 0)) {
|
||||
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
@@ -155,7 +114,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return (!c.getType().isLegendary() || canCopyLegendary) || !c.getController().equals(aiPlayer);
|
||||
return !c.getType().isLegendary() || !c.getController().equals(aiPlayer);
|
||||
}
|
||||
});
|
||||
Card choice;
|
||||
@@ -170,7 +129,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if (!sa.isTargetNumberValid() || (sa.getTargets().size() == 0)) {
|
||||
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
@@ -184,7 +143,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
} else if (sa.hasParam("Choices")) {
|
||||
// only check for options, does not select there
|
||||
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host, sa);
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host);
|
||||
Collection<Card> betterChoices = getBetterOptions(aiPlayer, sa, choices, !mandatory);
|
||||
if (betterChoices.isEmpty()) {
|
||||
return mandatory;
|
||||
@@ -193,13 +152,6 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
// if no targeting, it should always be ok
|
||||
}
|
||||
|
||||
if ("TriggeredCardController".equals(sa.getParam("Controller"))) {
|
||||
Card trigCard = (Card)sa.getTriggeringObject(AbilityKey.Card);
|
||||
if (!mandatory && trigCard != null && trigCard.getController().isOpponentOf(aiPlayer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -216,7 +168,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
// Select a card to attach to
|
||||
CardCollection betterOptions = getBetterOptions(ai, sa, options, isOptional);
|
||||
if (!betterOptions.isEmpty()) {
|
||||
@@ -228,14 +180,13 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
private CardCollection getBetterOptions(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player ctrl = host.getController();
|
||||
final boolean canCopyLegendary = sa.hasParam("NonLegendary");
|
||||
final String filter = canCopyLegendary ? "Permanent" : "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||
// TODO add filter to not select Legendary from Other Player when ai already have a Legendary with that name
|
||||
return CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
|
||||
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
||||
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilityActivated;
|
||||
import forge.game.spellability.Spell;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CopySpellAbilityAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
@@ -34,8 +29,8 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
||||
|
||||
final SpellAbility top = game.getStack().peekAbility();
|
||||
if (top != null
|
||||
&& top.getPayCosts().getCostMana() != null
|
||||
&& sa.getPayCosts().getCostMana() != null
|
||||
&& top.getPayCosts() != null && top.getPayCosts().getCostMana() != null
|
||||
&& sa.getPayCosts() != null && sa.getPayCosts().getCostMana() != null
|
||||
&& top.getPayCosts().getCostMana().getMana().getCMC() >= sa.getPayCosts().getCostMana().getMana().getCMC() + diff) {
|
||||
// The copied spell has a significantly higher CMC than the copy spell, consider copying
|
||||
chance = 100;
|
||||
@@ -68,7 +63,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (top.isWrapper() || top.isActivatedAbility()) {
|
||||
if (top.isWrapper() || !(top instanceof SpellAbility || top instanceof AbilityActivated)) {
|
||||
// Shouldn't even try with triggered or wrapped abilities at this time, will crash
|
||||
return false;
|
||||
} else if (top.getApi() == ApiType.CopySpellAbility) {
|
||||
@@ -96,7 +91,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
||||
AiPlayDecision decision = AiPlayDecision.CantPlaySa;
|
||||
if (top instanceof Spell) {
|
||||
decision = ((PlayerControllerAi) aiPlayer.getController()).getAi().canPlayFromEffectAI((Spell) topCopy, true, true);
|
||||
} else if (top.isActivatedAbility() && top.getActivatingPlayer().equals(aiPlayer)
|
||||
} else if (top instanceof AbilityActivated && top.getActivatingPlayer().equals(aiPlayer)
|
||||
&& logic.contains("CopyActivatedAbilities")) {
|
||||
decision = AiPlayDecision.WillPlay; // FIXME: we activated it once, why not again? Or bad idea?
|
||||
}
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
@@ -29,6 +16,11 @@ import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public class CounterAi extends SpellAbilityAi {
|
||||
|
||||
@@ -111,9 +103,8 @@ public class CounterAi extends SpellAbilityAi {
|
||||
|
||||
int toPay = 0;
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
setPayX = true;
|
||||
// TODO use ComputerUtilCost.getMaxXValue
|
||||
toPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), usableManaSources + 1);
|
||||
} else {
|
||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||
@@ -132,7 +123,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (setPayX) {
|
||||
sa.setXManaCostPaid(toPay);
|
||||
source.setSVar("PayX", Integer.toString(toPay));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +267,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
|
||||
int toPay = 0;
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
setPayX = true;
|
||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
} else {
|
||||
@@ -298,7 +289,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (setPayX) {
|
||||
sa.setXManaCostPaid(toPay);
|
||||
source.setSVar("PayX", Integer.toString(toPay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.util.Aggregates;
|
||||
@@ -98,12 +97,10 @@ public abstract class CountersAi {
|
||||
final CardCollection boon = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.getCounters(CounterEnumType.DIVINITY) == 0;
|
||||
return c.getCounters(CounterType.DIVINITY) == 0;
|
||||
}
|
||||
});
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon, null, false);
|
||||
} else if (CounterType.get(type).isKeywordCounter()) {
|
||||
choice = ComputerUtilCard.getBestCreatureAI(CardLists.getNotKeyword(list, type));
|
||||
} else {
|
||||
// The AI really should put counters on cards that can use it.
|
||||
// Charge counters on things with Charge abilities, etc. Expand
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -26,6 +17,9 @@ import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CountersMoveAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
@@ -48,14 +42,14 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final Card host = sa.getHostCard();
|
||||
final String type = sa.getParam("CounterType");
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.getType(type);
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cType != null && cType.is(CounterEnumType.P1P1) && sa.hasParam("Source")) {
|
||||
if (CounterType.P1P1.equals(cType) && sa.hasParam("Source")) {
|
||||
int amount = calcAmount(sa, cType);
|
||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||
@@ -96,9 +90,11 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// for Simic Fluxmage and other
|
||||
return ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN);
|
||||
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (cType != null && cType.is(CounterEnumType.P1P1) && sa.hasParam("Defined")) {
|
||||
} else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
|
||||
// something like Cyptoplast Root-kin
|
||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
@@ -111,7 +107,9 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
}
|
||||
// Make sure that removing the last counter doesn't kill the creature
|
||||
if ("Self".equals(sa.getParam("Source"))) {
|
||||
return host == null || host.getNetToughness() - 1 > 0;
|
||||
if (host != null && host.getNetToughness() - 1 <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -121,7 +119,6 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
|
||||
if (!moveTgtAI(ai, sa) && !mandatory) {
|
||||
return false;
|
||||
@@ -149,7 +146,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
final String type = sa.getParam("CounterType");
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.getType(type);
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||
|
||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
||||
@@ -196,7 +193,9 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
|
||||
// check for some specific AI preferences
|
||||
if ("DontMoveCounterIfLethal".equals(sa.getParam("AILogic"))) {
|
||||
return !cType.is(CounterEnumType.P1P1) || src.getNetToughness() - src.getTempToughnessBoost() - 1 > 0;
|
||||
if (cType == CounterType.P1P1 && src.getNetToughness() - src.getTempToughnessBoost() - 1 <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// no target
|
||||
@@ -208,7 +207,9 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
return moveTgtAI(ai, sa);
|
||||
if (!moveTgtAI(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -241,7 +242,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
final String type = sa.getParam("CounterType");
|
||||
final CounterType cType = "Any".equals(type) || "All".equals(type) ? null : CounterType.getType(type);
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||
|
||||
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
@@ -285,8 +286,11 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
|
||||
// do not steal a P1P1 from Undying if it would die
|
||||
// this way
|
||||
if (cType != null && cType.is(CounterEnumType.P1P1) && srcCardCpy.getNetToughness() <= 0) {
|
||||
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken();
|
||||
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||
if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -328,13 +332,13 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// try to remove P1P1 from undying or evolve
|
||||
if (cType != null && cType.is(CounterEnumType.P1P1)) {
|
||||
if (CounterType.P1P1.equals(cType)) {
|
||||
if (card.hasKeyword(Keyword.UNDYING) || card.hasKeyword(Keyword.EVOLVE)
|
||||
|| card.hasKeyword(Keyword.ADAPT)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (cType != null && cType.is(CounterEnumType.M1M1) && card.hasKeyword(Keyword.PERSIST)) {
|
||||
if (CounterType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -389,10 +393,10 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (cType != null) {
|
||||
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
|
||||
if (CounterType.P1P1.equals(cType) && card.hasKeyword(Keyword.UNDYING)) {
|
||||
return false;
|
||||
}
|
||||
if (cType.is(CounterEnumType.M1M1) && card.hasKeyword(Keyword.PERSIST)) {
|
||||
if (CounterType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -400,7 +404,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -459,7 +463,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
// or for source -> multiple defined
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||
Player targetedPlayer, Map<String, Object> params) {
|
||||
Player targetedPlayer) {
|
||||
if (sa.hasParam("AiLogic")) {
|
||||
String logic = sa.getParam("AiLogic");
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -49,7 +48,6 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
||||
if (!c.canReceiveCounters(counterType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
for (Map.Entry<CounterType, Integer> e : c.getCounters().entrySet()) {
|
||||
// has negative counter it would double
|
||||
@@ -78,7 +76,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final CounterType counterType = getCounterType(sa);
|
||||
|
||||
if (counterType != null && !counterType.is(CounterEnumType.P1P1)) {
|
||||
if (!CounterType.P1P1.equals(counterType) && counterType != null) {
|
||||
if (!sa.hasParam("ActivationPhases")) {
|
||||
// Don't use non P1P1/M1M1 counters before main 2 if possible
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
@@ -98,7 +96,10 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return !sa.usesTargeting() || setTargets(ai, sa) || mandatory;
|
||||
if (sa.usesTargeting() && !setTargets(ai, sa) && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private CounterType getCounterType(SpellAbility sa) {
|
||||
@@ -148,15 +149,15 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
||||
if (!aiList.isEmpty()) {
|
||||
// counter type list to check
|
||||
// first loyalty, then P1P!, then Charge Counter
|
||||
List<CounterEnumType> typeList = Lists.newArrayList(CounterEnumType.LOYALTY, CounterEnumType.P1P1, CounterEnumType.CHARGE);
|
||||
for (CounterEnumType type : typeList) {
|
||||
List<CounterType> typeList = Lists.newArrayList(CounterType.LOYALTY, CounterType.P1P1, CounterType.CHARGE);
|
||||
for (CounterType type : typeList) {
|
||||
// enough targets
|
||||
if (!sa.canAddMoreTarget()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (counterType == null || counterType.is(type)) {
|
||||
addTargetsByCounterType(ai, sa, aiList, CounterType.get(type));
|
||||
if (counterType == null || counterType == type) {
|
||||
addTargetsByCounterType(ai, sa, aiList, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,7 +166,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
||||
if (!oppList.isEmpty()) {
|
||||
// not enough targets
|
||||
if (sa.canAddMoreTarget()) {
|
||||
final CounterType type = CounterType.get(CounterEnumType.M1M1);
|
||||
final CounterType type = CounterType.M1M1;
|
||||
if (counterType == null || counterType == type) {
|
||||
addTargetsByCounterType(ai, sa, oppList, type);
|
||||
}
|
||||
@@ -173,7 +174,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// targeting does failed
|
||||
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
|
||||
if (!sa.isTargetNumberValid() || sa.getTargets().getNumTargeted() == 0) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -33,20 +27,16 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
||||
|
||||
for (final Player p : allies) {
|
||||
// player has experience or energy counter
|
||||
if (p.getCounters(CounterEnumType.EXPERIENCE) + p.getCounters(CounterEnumType.ENERGY) >= 1) {
|
||||
if (p.getCounters(CounterType.EXPERIENCE) + p.getCounters(CounterType.ENERGY) >= 1) {
|
||||
allyExpOrEnergy = true;
|
||||
}
|
||||
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
if (!crd.hasCounters()) {
|
||||
if (crd.hasCounters()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (crd.isPlaneswalker()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// iterate only over existing counters
|
||||
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||
@@ -66,11 +56,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
||||
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
if (!crd.hasCounters()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (crd.isPlaneswalker()) {
|
||||
if (crd.hasCounters()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -86,7 +72,10 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
|
||||
return !cperms.isEmpty() || !hperms.isEmpty() || opponentPoison || allyExpOrEnergy;
|
||||
if (cperms.isEmpty() && hperms.isEmpty() && !opponentPoison && !allyExpOrEnergy) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,70 +92,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
if ("Always".equals(sa.getParam("AILogic"))) {
|
||||
return true;
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
return checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.ai.SpellAbilityAi#chooseSingleEntity(forge.game.player.Player, forge.game.spellability.SpellAbility, java.util.Collection, boolean, forge.game.player.Player)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
// Proliferate is always optional for all, no need to select best
|
||||
|
||||
final CounterType poison = CounterType.get(CounterEnumType.POISON);
|
||||
|
||||
// because countertype can't be chosen anymore, only look for posion counters
|
||||
for (final Player p : Iterables.filter(options, Player.class)) {
|
||||
if (p.isOpponentOf(ai)) {
|
||||
if (p.getCounters(poison) > 0 && p.canReceiveCounters(poison)) {
|
||||
return (T)p;
|
||||
}
|
||||
} else {
|
||||
if (p.getCounters(poison) <= 5 || p.canReceiveCounters(poison)) {
|
||||
return (T)p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final Card c : Iterables.filter(options, Card.class)) {
|
||||
// AI planeswalker always, opponent planeswalkers never
|
||||
if (c.isPlaneswalker()) {
|
||||
if (c.getController().isOpponentOf(ai)) {
|
||||
continue;
|
||||
} else {
|
||||
return (T)c;
|
||||
}
|
||||
}
|
||||
|
||||
final Card lki = CardUtil.getLKICopy(c);
|
||||
// update all the counters there
|
||||
boolean hasNegative = false;
|
||||
for (final CounterType ct : c.getCounters().keySet()) {
|
||||
hasNegative = hasNegative || ComputerUtil.isNegativeCounter(ct, c);
|
||||
lki.setCounters(ct, lki.getCounters(ct) + 1);
|
||||
}
|
||||
|
||||
// TODO need more logic there?
|
||||
// it tries to evaluate the creatures
|
||||
if (c.isCreature()) {
|
||||
if (c.getController().isOpponentOf(ai) ==
|
||||
(ComputerUtilCard.evaluateCreature(lki, true, false)
|
||||
< ComputerUtilCard.evaluateCreature(c, true, false))) {
|
||||
return (T)c;
|
||||
}
|
||||
} else {
|
||||
if (!c.getController().isOpponentOf(ai) && !hasNegative) {
|
||||
return (T)c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,19 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialAiLogic;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostPutCounter;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.cost.CostSacrifice;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -51,6 +27,10 @@ import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CountersPutAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
@@ -76,17 +56,17 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
|
||||
final CounterType counterType = remCounter.counter;
|
||||
if (counterType.getName().equals(type) && !aiLogic.startsWith("MoveCounter")) {
|
||||
if (counterType.name().equals(type) && !aiLogic.startsWith("MoveCounter")) {
|
||||
return false;
|
||||
}
|
||||
if (!part.payCostFromSource()) {
|
||||
if (counterType.is(CounterEnumType.P1P1)) {
|
||||
if (counterType.equals(CounterType.P1P1)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// don't kill the creature
|
||||
if (counterType.is(CounterEnumType.P1P1) && source.getLethalDamage() <= 1) {
|
||||
if (counterType.equals(CounterType.P1P1) && source.getLethalDamage() <= 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -129,11 +109,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
int maxLevel = Integer.parseInt(sa.getParam("MaxLevel"));
|
||||
return source.getCounters(CounterEnumType.LEVEL) < maxLevel;
|
||||
}
|
||||
|
||||
if ("CrawlingBarrens".equals(sa.getParam("AILogic"))) {
|
||||
return true;
|
||||
return source.getCounters(CounterType.LEVEL) < maxLevel;
|
||||
}
|
||||
|
||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||
@@ -150,7 +126,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
Card choice = null;
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
final boolean divided = sa.isDividedAsYouChoose();
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
|
||||
@@ -170,7 +146,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
if (abTgt.canTgtPlayer()) {
|
||||
// try to kill opponent with Poison
|
||||
PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
PlayerCollection poisonList = oppList.filter(PlayerPredicates.hasCounter(CounterEnumType.POISON, 9));
|
||||
PlayerCollection poisonList = oppList.filter(PlayerPredicates.hasCounter(CounterType.POISON, 9));
|
||||
if (!poisonList.isEmpty()) {
|
||||
sa.getTargets().add(poisonList.max(PlayerPredicates.compareByLife()));
|
||||
return true;
|
||||
@@ -181,13 +157,13 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
// try to kill creature with -1/-1 counters if it can
|
||||
// receive counters, execpt it has undying
|
||||
CardCollection oppCreat = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
|
||||
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
||||
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterType.M1M1));
|
||||
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
|
||||
|
||||
oppCreatM1 = CardLists.filter(oppCreatM1, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
return input.getNetToughness() <= 1 && input.canReceiveCounters(CounterType.get(CounterEnumType.M1M1));
|
||||
return input.getNetToughness() <= 1 && input.canReceiveCounters(CounterType.M1M1);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -244,10 +220,6 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
|
||||
if ("Never".equals(logic)) {
|
||||
return false;
|
||||
} else if ("AlwaysWithNoTgt".equals(logic)) {
|
||||
return true;
|
||||
} else if ("AristocratCounters".equals(logic)) {
|
||||
return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa);
|
||||
} else if ("PayEnergy".equals(logic)) {
|
||||
return true;
|
||||
} else if ("PayEnergyConservatively".equals(logic)) {
|
||||
@@ -268,7 +240,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
int totBlkPower = Aggregates.sum(blocked, CardPredicates.Accessors.fnGetNetPower);
|
||||
int totBlkToughness = Aggregates.min(blocked, CardPredicates.Accessors.fnGetNetToughness);
|
||||
|
||||
int numActivations = ai.getCounters(CounterEnumType.ENERGY) / sa.getPayCosts().getCostEnergy().convertAmount();
|
||||
int numActivations = ai.getCounters(CounterType.ENERGY) / sa.getPayCosts().getCostEnergy().convertAmount();
|
||||
if (sa.getHostCard().getNetToughness() + numActivations > totBlkPower
|
||||
|| sa.getHostCard().getNetPower() + numActivations >= totBlkToughness) {
|
||||
return true;
|
||||
@@ -283,7 +255,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
|
||||
return true;
|
||||
}
|
||||
} else if (ai.getCounters(CounterEnumType.ENERGY) > ComputerUtilCard.getMaxSAEnergyCostOnBattlefield(ai) + sa.getPayCosts().getCostEnergy().convertAmount()) {
|
||||
} else if (ai.getCounters(CounterType.ENERGY) > ComputerUtilCard.getMaxSAEnergyCostOnBattlefield(ai) + sa.getPayCosts().getCostEnergy().convertAmount()) {
|
||||
// outside of combat, this logic only works if the relevant AI profile option is enabled
|
||||
// and if there is enough energy saved
|
||||
if (!onlyInCombat) {
|
||||
@@ -312,11 +284,9 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
} else if (logic.startsWith("MoveCounter")) {
|
||||
return doMoveCounterLogic(ai, sa, ph);
|
||||
} else if (logic.equals("CrawlingBarrens")) {
|
||||
return SpecialCardAi.CrawlingBarrens.consider(ai, sa);
|
||||
}
|
||||
|
||||
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
||||
if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -341,20 +311,13 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("Adapt") && source.getCounters(CounterType.P1P1) > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO handle proper calculation of X values based on Cost
|
||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
|
||||
if (sa.hasParam("Adapt")) {
|
||||
Game game = ai.getGame();
|
||||
Combat combat = game.getCombat();
|
||||
|
||||
if (!source.canReceiveCounters(CounterType.get(CounterEnumType.P1P1)) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
||||
return false;
|
||||
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return doCombatAdaptLogic(source, amount, combat);
|
||||
}
|
||||
}
|
||||
|
||||
if ("Fight".equals(logic)) {
|
||||
int nPump = 0;
|
||||
if (type.equals("P1P1")) {
|
||||
@@ -364,14 +327,14 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (amountStr.equals("X")) {
|
||||
if (sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
if (source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// By default, set PayX here to maximum value (used for most SAs of this type).
|
||||
amount = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
if (isClockwork) {
|
||||
// Clockwork Avian and other similar cards: do not tap all mana for X,
|
||||
// instead only rewind to max allowed value when the power gets low enough.
|
||||
int curCtrs = source.getCounters(CounterEnumType.P1P0);
|
||||
int curCtrs = source.getCounters(CounterType.P1P0);
|
||||
int maxCtrs = Integer.parseInt(sa.getParam("MaxFromEffect"));
|
||||
|
||||
// This will "rewind" clockwork cards when they fall to 50% power or below, consider improving
|
||||
@@ -385,7 +348,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
sa.setXManaCostPaid(amount);
|
||||
source.setSVar("PayX", Integer.toString(amount));
|
||||
} else if ("ExiledCreatureFromGraveCMC".equals(logic)) {
|
||||
// e.g. Necropolis
|
||||
amount = Aggregates.max(CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES), CardPredicates.Accessors.fnGetCmc);
|
||||
@@ -424,15 +387,16 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (!ai.getGame().getStack().isEmpty() && !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
// only evaluates case where all tokens are placed on a single target
|
||||
if (sa.usesTargeting() && sa.getMinTargets() < 2) {
|
||||
if (sa.usesTargeting() && abTgt.getMinTargets(source, sa) < 2) {
|
||||
if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) {
|
||||
Card c = sa.getTargets().getFirstTargetedCard();
|
||||
if (sa.getTargets().size() > 1) {
|
||||
if (sa.getTargets().getNumTargeted() > 1) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
sa.addDividedAllocation(sa.getTargetCard(), amount);
|
||||
abTgt.addDividedAllocation(sa.getTargetCard(), amount);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -460,7 +424,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
if (sacSelf && c.equals(source)) {
|
||||
return false;
|
||||
}
|
||||
return sa.canTarget(c) && c.canReceiveCounters(CounterType.getType(type));
|
||||
return sa.canTarget(c) && c.canReceiveCounters(CounterType.valueOf(type));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -479,14 +443,16 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
// but try to do it in Main 2 then so that the AI has a chance to play creatures first.
|
||||
if (list.isEmpty()
|
||||
&& sa.hasParam("Planeswalker")
|
||||
&& sa.getPayCosts() != null
|
||||
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
|
||||
&& sa.isTargetNumberValid()
|
||||
&& sa.getTargets().size() == 0
|
||||
&& sa.getTargets().getNumTargeted() == 0
|
||||
&& ai.getGame().getPhaseHandler().is(PhaseType.MAIN2, ai)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sourceName.equals("Abzan Charm")) {
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
// specific AI for instant with distribute two +1/+1 counters
|
||||
ComputerUtilCard.sortByEvaluateCreature(list);
|
||||
// maximise the number of targets
|
||||
@@ -494,13 +460,13 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
int left = amount;
|
||||
for (Card c : list) {
|
||||
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, i, i,
|
||||
Lists.newArrayList())) {
|
||||
Lists.<String>newArrayList())) {
|
||||
sa.getTargets().add(c);
|
||||
sa.addDividedAllocation(c, i);
|
||||
abTgt.addDividedAllocation(c, i);
|
||||
left -= i;
|
||||
}
|
||||
if (left < i || sa.getTargets().size() == sa.getMaxTargets()) {
|
||||
sa.addDividedAllocation(sa.getTargets().getFirstTargetedCard(), left + i);
|
||||
if (left < i || sa.getTargets().getNumTargeted() == abTgt.getMaxTargets(source, sa)) {
|
||||
abTgt.addDividedAllocation(sa.getTargets().getFirstTargetedCard(), left + i);
|
||||
left = 0;
|
||||
break;
|
||||
}
|
||||
@@ -516,7 +482,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
// target loop
|
||||
while (sa.canAddMoreTarget()) {
|
||||
if (list.isEmpty()) {
|
||||
if (!sa.isTargetNumberValid() || (sa.getTargets().size() == 0)) {
|
||||
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
@@ -531,7 +497,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
for (Card c : list) {
|
||||
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, amount, amount,
|
||||
Lists.newArrayList())) {
|
||||
Lists.<String>newArrayList())) {
|
||||
choice = c;
|
||||
break;
|
||||
}
|
||||
@@ -554,7 +520,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
|
||||
if (!sa.isTargetNumberValid() || sa.getTargets().getNumTargeted() == 0) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
@@ -566,7 +532,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(choice, amount);
|
||||
sa.getTargetRestrictions().addDividedAllocation(choice, amount);
|
||||
break;
|
||||
}
|
||||
choice = null;
|
||||
@@ -582,7 +548,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int currCounters = cards.get(0).getCounters(CounterType.get(type));
|
||||
final int currCounters = cards.get(0).getCounters(CounterType.valueOf(type));
|
||||
// each non +1/+1 counter on the card is a 10% chance of not
|
||||
// activating this ability.
|
||||
|
||||
@@ -633,7 +599,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
final boolean divided = sa.isDividedAsYouChoose();
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
|
||||
final boolean isMandatoryTrigger = (sa.isTrigger() && !sa.isOptionalTrigger())
|
||||
@@ -661,7 +627,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if (!sa.isTargetNumberValid()
|
||||
|| sa.getTargets().size() == 0) {
|
||||
|| sa.getTargets().getNumTargeted() == 0) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
@@ -685,7 +651,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if ((!sa.isTargetNumberValid())
|
||||
|| (sa.getTargets().size() == 0)) {
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
@@ -696,7 +662,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(choice, amount);
|
||||
sa.getTargetRestrictions().addDividedAllocation(choice, amount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -707,14 +673,13 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
final Card source = sa.getHostCard();
|
||||
// boolean chance = true;
|
||||
boolean preferred = true;
|
||||
CardCollection list;
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
final boolean divided = sa.isDividedAsYouChoose();
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
int left = amount;
|
||||
|
||||
@@ -723,14 +688,14 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
list = new CardCollection(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa));
|
||||
|
||||
if (amountStr.equals("X")
|
||||
&& root.getXManaCostPaid() != null /* SubAbility on something that already had set PayX, e.g. Endless One ETB counters */
|
||||
&& sa.hasParam(amountStr) && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
&& !source.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") )) {
|
||||
|
||||
// detect if there's more than one X in the cost (Hangarback Walker, Walking Ballista, etc.)
|
||||
SpellAbility testSa = sa;
|
||||
int countX = 0;
|
||||
int nonXGlyphs = 0;
|
||||
while (testSa != null && countX == 0) {
|
||||
while (testSa != null && testSa.getPayCosts() != null && countX == 0) {
|
||||
countX = testSa.getPayCosts().getTotalMana().countX();
|
||||
nonXGlyphs = testSa.getPayCosts().getTotalMana().getGlyphCount() - countX;
|
||||
testSa = testSa.getSubAbility();
|
||||
@@ -742,7 +707,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// Spend all remaining mana to add X counters (eg. Hero of Leina Tower)
|
||||
int payX = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
int payX = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
// Account for the possible presence of additional glyphs in cost (e.g. Mikaeus, the Lunarch; Primordial Hydra)
|
||||
payX -= nonXGlyphs;
|
||||
@@ -750,7 +715,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
// Account for the multiple X in cost
|
||||
if (countX > 1) { payX /= countX; }
|
||||
|
||||
root.setXManaCostPaid(payX);
|
||||
source.setSVar("PayX", Integer.toString(payX));
|
||||
}
|
||||
|
||||
if (!mandatory) {
|
||||
@@ -758,21 +723,6 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
// put a counter?
|
||||
// things like Powder Keg, which are way too complex for the AI
|
||||
}
|
||||
} else if (sa.getTargetRestrictions().canOnlyTgtOpponent() && !sa.getTargetRestrictions().canTgtCreature()) {
|
||||
// can only target opponent
|
||||
List<Player> playerList = Lists.newArrayList(Iterables.filter(
|
||||
sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
|
||||
|
||||
if (playerList.isEmpty() && mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// try to choose player with less creatures
|
||||
Player choice = Collections.min(playerList, PlayerPredicates.compareByZoneSize(ZoneType.Battlefield, CardPredicates.Presets.CREATURES));
|
||||
|
||||
if (choice != null) {
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
} else {
|
||||
if (sa.isCurse()) {
|
||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
@@ -842,11 +792,12 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
if (choice != null && divided) {
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
int alloc = Math.max(amount / totalTargets, 1);
|
||||
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
||||
sa.addDividedAllocation(choice, left);
|
||||
if (sa.getTargets().getNumTargeted() == Math.min(totalTargets, abTgt.getMaxTargets(sa.getHostCard(), sa)) - 1) {
|
||||
abTgt.addDividedAllocation(choice, left);
|
||||
} else {
|
||||
sa.addDividedAllocation(choice, alloc);
|
||||
abTgt.addDividedAllocation(choice, alloc);
|
||||
left -= alloc;
|
||||
}
|
||||
}
|
||||
@@ -913,7 +864,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
// used by Tribute, select player with lowest Life
|
||||
// TODO add more logic using TributeAILogic
|
||||
List<Player> list = Lists.newArrayList(options);
|
||||
@@ -921,20 +872,16 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
protected Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
// Bolster does use this
|
||||
// TODO need more or less logic there?
|
||||
final CounterType m1m1 = CounterType.get(CounterEnumType.M1M1);
|
||||
final CounterType p1p1 = CounterType.get(CounterEnumType.P1P1);
|
||||
|
||||
// no logic if there is no options or no to choice
|
||||
if (!isOptional && Iterables.size(options) <= 1) {
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
final CounterType type = params.containsKey("CounterType") ? (CounterType)params.get("CounterType")
|
||||
: CounterType.getType(sa.getParam("CounterType"));
|
||||
|
||||
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
|
||||
@@ -951,7 +898,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
return false;
|
||||
if (ComputerUtilCard.isUselessCreature(ai, input))
|
||||
return false;
|
||||
if (type.is(CounterEnumType.M1M1) && amount >= input.getNetToughness())
|
||||
if (CounterType.M1M1.equals(type) && amount >= input.getNetToughness())
|
||||
return true;
|
||||
return ComputerUtil.isNegativeCounter(type, input);
|
||||
}
|
||||
@@ -975,20 +922,6 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
|
||||
CardCollection filtered = mine;
|
||||
|
||||
// Try to filter out keywords that we already have on cards
|
||||
if (type.isKeywordCounter()) {
|
||||
Keyword kw = Keyword.smartValueOf(type.getName());
|
||||
final CardCollection doNotHaveKeyword = CardLists.filter(filtered, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return !card.hasKeyword(kw) && card.canBeTargetedBy(sa) && sa.canTarget(card);
|
||||
}
|
||||
});
|
||||
|
||||
if (doNotHaveKeyword.size() > 0)
|
||||
filtered = doNotHaveKeyword;
|
||||
}
|
||||
|
||||
final CardCollection notUseless = CardLists.filter(filtered, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
@@ -1003,26 +936,26 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// some special logic to reload Persist/Undying
|
||||
if (p1p1.equals(type)) {
|
||||
if (CounterType.P1P1.equals(type)) {
|
||||
final CardCollection persist = CardLists.filter(filtered, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
if (!input.hasKeyword(Keyword.PERSIST))
|
||||
return false;
|
||||
return input.getCounters(m1m1) <= amount;
|
||||
return input.getCounters(CounterType.M1M1) <= amount;
|
||||
}
|
||||
});
|
||||
|
||||
if (!persist.isEmpty()) {
|
||||
filtered = persist;
|
||||
}
|
||||
} else if (m1m1.equals(type)) {
|
||||
} else if (CounterType.M1M1.equals(type)) {
|
||||
final CardCollection undying = CardLists.filter(filtered, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
if (!input.hasKeyword(Keyword.UNDYING))
|
||||
return false;
|
||||
return input.getCounters(p1p1) <= amount && input.getNetToughness() > amount;
|
||||
return input.getCounters(CounterType.P1P1) <= amount && input.getNetToughness() > amount;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1045,8 +978,8 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
if (e instanceof Card) {
|
||||
Card c = (Card) e;
|
||||
if (c.getController().isOpponentOf(ai)) {
|
||||
if (options.contains(CounterType.get(CounterEnumType.M1M1)) && !c.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
if (options.contains(CounterType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterType.M1M1;
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (ComputerUtil.isNegativeCounter(type, c)) {
|
||||
@@ -1055,7 +988,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
for (CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, c) && !ComputerUtil.isUselessCounter(type, c)) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, c) && !ComputerUtil.isUselessCounter(type)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -1063,12 +996,12 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
} else if (e instanceof Player) {
|
||||
Player p = (Player) e;
|
||||
if (p.isOpponentOf(ai)) {
|
||||
if (options.contains(CounterType.get(CounterEnumType.POISON))) {
|
||||
return CounterType.get(CounterEnumType.POISON);
|
||||
if (options.contains(CounterType.POISON)) {
|
||||
return CounterType.POISON;
|
||||
}
|
||||
} else {
|
||||
if (options.contains(CounterType.get(CounterEnumType.EXPERIENCE))) {
|
||||
return CounterType.get(CounterEnumType.EXPERIENCE);
|
||||
if (options.contains(CounterType.EXPERIENCE)) {
|
||||
return CounterType.EXPERIENCE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1116,39 +1049,4 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean doCombatAdaptLogic(Card source, int amount, Combat combat) {
|
||||
if (combat.isAttacking(source)) {
|
||||
if (!combat.isBlocked(source)) {
|
||||
return true;
|
||||
} else {
|
||||
for (Card blockedBy : combat.getBlockers(source)) {
|
||||
if (blockedBy.getNetToughness() > source.getNetPower()
|
||||
&& blockedBy.getNetToughness() <= source.getNetPower() + amount) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int totBlkPower = Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
|
||||
if (source.getNetToughness() <= totBlkPower
|
||||
&& source.getNetToughness() + amount > totBlkPower) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (combat.isBlocking(source)) {
|
||||
for (Card blocked : combat.getAttackersBlockedBy(source)) {
|
||||
if (blocked.getNetToughness() > source.getNetPower()
|
||||
&& blocked.getNetToughness() <= source.getNetPower() + amount) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int totAtkPower = Aggregates.sum(combat.getAttackersBlockedBy(source), CardPredicates.Accessors.fnGetNetPower);
|
||||
if (source.getNetToughness() <= totAtkPower
|
||||
&& source.getNetToughness() + amount > totAtkPower) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
@@ -21,6 +20,8 @@ import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CountersPutAllAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
@@ -38,13 +39,8 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
final boolean curse = sa.isCurse();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if ("OwnCreatsAndOtherPWs".equals(sa.getParam("AILogic"))) {
|
||||
hList = CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), "Creature.YouCtrl,Planeswalker.YouCtrl+Other", source.getController(), source, sa);
|
||||
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), "Creature.YouCtrl,Planeswalker.YouCtrl+Other", source.getController(), source, sa);
|
||||
} else {
|
||||
hList = CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
||||
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
||||
}
|
||||
hList = CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
||||
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
@@ -52,7 +48,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -72,7 +68,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (tgt != null) {
|
||||
Player pl = curse ? ai.getWeakestOpponent() : ai;
|
||||
Player pl = curse ? ComputerUtil.getOpponentFor(ai) : ai;
|
||||
sa.getTargets().add(pl);
|
||||
|
||||
hList = CardLists.filterControlledBy(hList, pl);
|
||||
@@ -82,10 +78,10 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
// TODO improve X value to don't overpay when extra mana won't do
|
||||
// anything more useful
|
||||
final int amount;
|
||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
amount = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setXManaCostPaid(amount);
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(amount));
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
@@ -120,7 +116,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
//Check for cards that could profit from the ability
|
||||
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
||||
if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
|
||||
&& sa.getPayCosts().hasTapCost()
|
||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
|
||||
&& sa instanceof AbilitySub
|
||||
&& (!phase.getNextTurn().equals(ai)
|
||||
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
|
||||
@@ -153,7 +149,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return player.getCreaturesInPlay().size() >= player.getWeakestOpponent().getCreaturesInPlay().size();
|
||||
return player.getCreaturesInPlay().size() >= ComputerUtil.getOpponentFor(player).getCreaturesInPlay().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -180,6 +176,6 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
return mandatory || canPlayAI(aiPlayer, sa);
|
||||
return mandatory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,27 +17,21 @@
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController.BinaryChoiceType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactory_PutOrRemoveCountersAi class.
|
||||
@@ -81,7 +75,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
|
||||
if (sa.hasParam("CounterType")) {
|
||||
// currently only Jhoira's Timebug
|
||||
final CounterType type = CounterType.getType(sa.getParam("CounterType"));
|
||||
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
|
||||
|
||||
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
|
||||
|
||||
@@ -106,7 +100,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = CardLists.filter(countersList,
|
||||
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterEnumType.ICE));
|
||||
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
sa.getTargets().add(depthsList.getFirst());
|
||||
@@ -119,7 +113,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
CardCollection planeswalkerList = CardLists.filter(
|
||||
CardLists.filterControlledBy(countersList, ai.getOpponents()),
|
||||
CardPredicates.Presets.PLANESWALKERS,
|
||||
CardPredicates.hasLessCounter(CounterEnumType.LOYALTY, amount));
|
||||
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||
@@ -129,7 +123,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
// do as M1M1 part
|
||||
CardCollection aiList = CardLists.filterControlledBy(countersList, ai);
|
||||
|
||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
||||
|
||||
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
|
||||
if (!aiPersistList.isEmpty()) {
|
||||
@@ -142,7 +136,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// do as P1P1 part
|
||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.P1P1));
|
||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
||||
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
|
||||
|
||||
if (!aiUndyingList.isEmpty()) {
|
||||
@@ -163,7 +157,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
} else if (!ComputerUtil.isUselessCounter(aType, best)) {
|
||||
} else if (!ComputerUtil.isUselessCounter(aType)) {
|
||||
// whould remove positive counter
|
||||
if (best.getCounters(aType) <= amount) {
|
||||
sa.getTargets().add(best);
|
||||
@@ -205,18 +199,18 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
Card tgt = (Card) params.get("Target");
|
||||
|
||||
// planeswalker has high priority for loyalty counters
|
||||
if (tgt.isPlaneswalker() && options.contains(CounterType.get(CounterEnumType.LOYALTY))) {
|
||||
return CounterType.get(CounterEnumType.LOYALTY);
|
||||
if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) {
|
||||
return CounterType.LOYALTY;
|
||||
}
|
||||
|
||||
if (tgt.getController().isOpponentOf(ai)) {
|
||||
// creatures with BaseToughness below or equal zero might be
|
||||
// killed if their counters are removed
|
||||
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
||||
if (options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||
return CounterType.get(CounterEnumType.P1P1);
|
||||
} else if (options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
if (options.contains(CounterType.P1P1)) {
|
||||
return CounterType.P1P1;
|
||||
} else if (options.contains(CounterType.M1M1)) {
|
||||
return CounterType.M1M1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,14 +222,14 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
// this counters are treat first to be removed
|
||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) {
|
||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
return CounterType.get(CounterEnumType.ICE);
|
||||
return CounterType.ICE;
|
||||
}
|
||||
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||
return CounterType.get(CounterEnumType.P1P1);
|
||||
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.P1P1)) {
|
||||
return CounterType.P1P1;
|
||||
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.M1M1)) {
|
||||
return CounterType.M1M1;
|
||||
}
|
||||
|
||||
// fallback logic, select positive counter to add more
|
||||
@@ -268,19 +262,19 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
if (tgt.getController().isOpponentOf(ai)) {
|
||||
if (type.is(CounterEnumType.LOYALTY) && tgt.isPlaneswalker()) {
|
||||
if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ComputerUtil.isNegativeCounter(type, tgt);
|
||||
} else {
|
||||
if (type.is(CounterEnumType.ICE) && "Dark Depths".equals(tgt.getName())) {
|
||||
if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
return false;
|
||||
}
|
||||
} else if (type.is(CounterEnumType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {
|
||||
} else if (type.equals(CounterType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {
|
||||
return false;
|
||||
} else if (type.is(CounterEnumType.P1P1) && tgt.hasKeyword(Keyword.UNDYING)) {
|
||||
} else if (type.equals(CounterType.P1P1) && tgt.hasKeyword(Keyword.UNDYING)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,14 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -28,15 +17,10 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class CountersRemoveAi extends SpellAbilityAi {
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Override
|
||||
protected boolean canPlayWithoutRestrict(final Player ai, final SpellAbility sa) {
|
||||
if ("Always".equals(sa.getParam("AILogic"))) {
|
||||
return true;
|
||||
}
|
||||
return super.canPlayWithoutRestrict(ai, sa);
|
||||
}
|
||||
public class CountersRemoveAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -89,7 +73,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (!type.matches("Any") && !type.matches("All")) {
|
||||
final int currCounters = sa.getHostCard().getCounters(CounterType.getType(type));
|
||||
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
|
||||
if (currCounters < 1) {
|
||||
return false;
|
||||
}
|
||||
@@ -116,7 +100,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
||||
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
|
||||
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
@@ -126,7 +110,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||
CardPredicates.hasCounter(CounterEnumType.ICE, 3));
|
||||
CardPredicates.hasCounter(CounterType.ICE, 3));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
sa.getTargets().add(depthsList.getFirst());
|
||||
@@ -139,7 +123,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
||||
|
||||
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS,
|
||||
CardPredicates.hasCounter(CounterEnumType.LOYALTY, 5));
|
||||
CardPredicates.hasCounter(CounterType.LOYALTY, 5));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||
@@ -150,8 +134,8 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
// variable amount for Hex Parasite
|
||||
int amount;
|
||||
boolean xPay = false;
|
||||
if (amountStr.equals("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
if (manaLeft == 0) {
|
||||
return false;
|
||||
@@ -166,15 +150,15 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||
CardPredicates.hasCounter(CounterEnumType.ICE));
|
||||
CardPredicates.hasCounter(CounterType.ICE));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
Card depth = depthsList.getFirst();
|
||||
int ice = depth.getCounters(CounterEnumType.ICE);
|
||||
int ice = depth.getCounters(CounterType.ICE);
|
||||
if (amount >= ice) {
|
||||
sa.getTargets().add(depth);
|
||||
if (xPay) {
|
||||
sa.setXManaCostPaid(ice);
|
||||
source.setSVar("PayX", Integer.toString(ice));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -187,13 +171,13 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
|
||||
CardCollection planeswalkerList = CardLists.filter(list,
|
||||
Predicates.and(CardPredicates.Presets.PLANESWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
|
||||
CardPredicates.hasLessCounter(CounterEnumType.LOYALTY, amount));
|
||||
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
|
||||
sa.getTargets().add(best);
|
||||
if (xPay) {
|
||||
sa.setXManaCostPaid(best.getCurrentLoyalty());
|
||||
source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -203,7 +187,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
// do as M1M1 part
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
|
||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
||||
|
||||
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
|
||||
if (!aiPersistList.isEmpty()) {
|
||||
@@ -216,20 +200,14 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// do as P1P1 part
|
||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasLessCounter(CounterEnumType.P1P1, amount));
|
||||
CardCollection aiUndyingList = CardLists.getKeyword(aiP1P1List, Keyword.UNDYING);
|
||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
||||
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
|
||||
|
||||
if (!aiUndyingList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiUndyingList));
|
||||
return true;
|
||||
aiP1P1List = aiUndyingList;
|
||||
}
|
||||
|
||||
// remove P1P1 counters from opposing creatures
|
||||
CardCollection oppP1P1List = CardLists.filter(list,
|
||||
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
|
||||
CardPredicates.hasCounter(CounterEnumType.P1P1));
|
||||
if (!oppP1P1List.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(oppP1P1List));
|
||||
if (!aiP1P1List.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -251,7 +229,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
// no special amount for that one yet
|
||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.M1M1, amount));
|
||||
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount));
|
||||
|
||||
CardCollection aiPersist = CardLists.getKeyword(aiList, Keyword.PERSIST);
|
||||
if (!aiPersist.isEmpty()) {
|
||||
@@ -270,7 +248,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
// no special amount for that one yet
|
||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
|
||||
list = CardLists.filter(list, CardPredicates.hasCounter(CounterEnumType.P1P1, amount));
|
||||
list = CardLists.filter(list, CardPredicates.hasCounter(CounterType.P1P1, amount));
|
||||
|
||||
// currently only logic for Bloodcrazed Hoplite, but add logic for
|
||||
// targeting ai creatures too
|
||||
@@ -304,8 +282,8 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
int amount;
|
||||
boolean xPay = false;
|
||||
// Timecrafting has X R
|
||||
if (amountStr.equals("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
if (manaLeft == 0) {
|
||||
return false;
|
||||
@@ -316,15 +294,15 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterEnumType.TIME, amount));
|
||||
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterType.TIME, amount));
|
||||
|
||||
if (!timeList.isEmpty()) {
|
||||
Card best = ComputerUtilCard.getBestAI(timeList);
|
||||
|
||||
int timeCount = best.getCounters(CounterEnumType.TIME);
|
||||
int timeCount = best.getCounters(CounterType.TIME);
|
||||
sa.getTargets().add(best);
|
||||
if (xPay) {
|
||||
sa.setXManaCostPaid(timeCount);
|
||||
source.setSVar("PayX", Integer.toString(timeCount));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -342,7 +320,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
CardCollection outlastCreats = CardLists.filter(list, CardPredicates.hasKeyword(Keyword.OUTLAST));
|
||||
if (!outlastCreats.isEmpty()) {
|
||||
// outlast cards often benefit from having +1/+1 counters, try not to remove last one
|
||||
CardCollection betterTargets = CardLists.filter(outlastCreats, CardPredicates.hasCounter(CounterEnumType.P1P1, 2));
|
||||
CardCollection betterTargets = CardLists.filter(outlastCreats, CardPredicates.hasCounter(CounterType.P1P1, 2));
|
||||
|
||||
if (!betterTargets.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(betterTargets));
|
||||
@@ -376,30 +354,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||
GameEntity target = (GameEntity) params.get("Target");
|
||||
CounterType type = (CounterType) params.get("CounterType");
|
||||
|
||||
if (target instanceof Card) {
|
||||
Card targetCard = (Card) target;
|
||||
if (targetCard.getController().isOpponentOf(player)) {
|
||||
return !ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
||||
} else {
|
||||
if (targetCard.hasKeyword(Keyword.UNDYING) && type.is(CounterEnumType.P1P1)
|
||||
&& targetCard.getCounters(CounterEnumType.P1P1) >= max) {
|
||||
return max;
|
||||
}
|
||||
|
||||
return ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
||||
}
|
||||
} else if (target instanceof Player) {
|
||||
Player targetPlayer = (Player) target;
|
||||
if (targetPlayer.isOpponentOf(player)) {
|
||||
return !type.is(CounterEnumType.POISON) ? max : min;
|
||||
} else {
|
||||
return type.is(CounterEnumType.POISON) ? max : min;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Auto-generated method stub
|
||||
return super.chooseNumber(player, sa, min, max, params);
|
||||
}
|
||||
|
||||
@@ -415,49 +370,30 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
return super.chooseCounterType(options, sa, params);
|
||||
}
|
||||
Player ai = sa.getActivatingPlayer();
|
||||
GameEntity target = (GameEntity) params.get("Target");
|
||||
Card target = (Card) params.get("Target");
|
||||
|
||||
if (target instanceof Card) {
|
||||
Card targetCard = (Card) target;
|
||||
if (targetCard.getController().isOpponentOf(ai)) {
|
||||
if (target.getController().isOpponentOf(ai)) {
|
||||
// if its a Planeswalker try to remove Loyality first
|
||||
if (targetCard.isPlaneswalker()) {
|
||||
return CounterType.get(CounterEnumType.LOYALTY);
|
||||
if (target.isPlaneswalker()) {
|
||||
return CounterType.LOYALTY;
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, target)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (options.contains(CounterType.get(CounterEnumType.M1M1)) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
} else if (options.contains(CounterType.get(CounterEnumType.P1P1)) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterType.get(CounterEnumType.P1P1);
|
||||
if (options.contains(CounterType.M1M1) && target.hasKeyword(Keyword.PERSIST)) {
|
||||
return CounterType.M1M1;
|
||||
} else if (options.contains(CounterType.P1P1) && target.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterType.M1M1;
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||
if (ComputerUtil.isNegativeCounter(type, target)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (target instanceof Player) {
|
||||
Player targetPlayer = (Player) target;
|
||||
if (targetPlayer.isOpponentOf(ai)) {
|
||||
for (CounterType type : options) {
|
||||
if (!type.is(CounterEnumType.POISON)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (CounterType type : options) {
|
||||
if (type.is(CounterEnumType.POISON)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.chooseCounterType(options, sa, params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
@@ -20,7 +20,7 @@ import forge.util.MyRandom;
|
||||
|
||||
public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
protected boolean avoidTargetP(final Player comp, final SpellAbility sa) {
|
||||
Player enemy = comp.getWeakestOpponent();
|
||||
Player enemy = ComputerUtil.getOpponentFor(comp);
|
||||
// Logic for cards that damage owner, like Fireslinger
|
||||
// Do not target a player if they aren't below 75% of our health.
|
||||
// Unless Lifelink will cancel the damage to us
|
||||
@@ -38,7 +38,9 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
}
|
||||
if ("SelfDamage".equals(sa.getParam("AILogic"))) {
|
||||
if (comp.getLife() * 0.75 < enemy.getLife()) {
|
||||
return !lifelink;
|
||||
if (!lifelink) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -52,7 +54,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention, final boolean noPlaneswalkerRedirection) {
|
||||
int restDamage = d;
|
||||
final Game game = comp.getGame();
|
||||
Player enemy = comp.getWeakestOpponent();
|
||||
Player enemy = ComputerUtil.getOpponentFor(comp);
|
||||
boolean dmgByCardsInHand = false;
|
||||
|
||||
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
|
||||
@@ -67,7 +69,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
if (!sa.canTarget(enemy)) {
|
||||
return false;
|
||||
}
|
||||
if (sa.getTargets() != null && sa.getTargets().contains(enemy)) {
|
||||
if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -51,9 +47,13 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
dmg = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||
}
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
x = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
}
|
||||
if (damage.equals("ChosenX")) {
|
||||
x = source.getCounters(CounterType.LOYALTY);
|
||||
}
|
||||
if (x == -1) {
|
||||
Player bestOpp = determineOppToKill(ai, sa, source, dmg);
|
||||
if (determineOppToKill(ai, sa, source, dmg) != null) {
|
||||
// we already know we can kill a player, so go for it
|
||||
return true;
|
||||
@@ -84,7 +84,10 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
|
||||
if (best_x > 0) {
|
||||
if (sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
sa.setXManaCostPaid(best_x);
|
||||
source.setSVar("PayX", Integer.toString(best_x));
|
||||
}
|
||||
if (damage.equals("ChosenX")) {
|
||||
source.setSVar("ChosenX", "Number$" + best_x);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -135,7 +138,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
int minGain = 200; // The minimum gain in destroyed creatures
|
||||
if (sa.getPayCosts().isReusuableResource()) {
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
|
||||
if (computerList.isEmpty()) {
|
||||
minGain = 10; // nothing to lose
|
||||
// no creatures to lose and player can be damaged
|
||||
@@ -196,13 +199,12 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
String validP = "";
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg;
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setXManaCostPaid(dmg);
|
||||
} else {
|
||||
dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
}
|
||||
|
||||
if (sa.hasParam("ValidPlayers")) {
|
||||
@@ -210,7 +212,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// Evaluate creatures getting killed
|
||||
Player enemy = ai.getWeakestOpponent();
|
||||
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
@@ -279,14 +281,12 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
String validP = "";
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg;
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setXManaCostPaid(dmg);
|
||||
} else {
|
||||
dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
}
|
||||
|
||||
if (sa.hasParam("ValidPlayers")) {
|
||||
@@ -294,7 +294,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// Evaluate creatures getting killed
|
||||
Player enemy = ai.getWeakestOpponent();
|
||||
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
@@ -1,39 +1,15 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
@@ -49,6 +25,12 @@ import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DamageDealAi extends DamageAiBase {
|
||||
@Override
|
||||
@@ -64,7 +46,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$ChosenNumber")) {
|
||||
int energy = ai.getCounters(CounterEnumType.ENERGY);
|
||||
int energy = ai.getCounters(CounterType.ENERGY);
|
||||
for (SpellAbility s : source.getSpellAbilities()) {
|
||||
if ("PayEnergy".equals(s.getParam("AILogic"))) {
|
||||
energy += AbilityUtils.calculateAmount(source, s.getParam("CounterNum"), sa);
|
||||
@@ -91,13 +73,16 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setXManaCostPaid(dmg);
|
||||
} else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.isInZone(ZoneType.Hand)) {
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
} else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.getZone().is(ZoneType.Hand)) {
|
||||
dmg--; // the card will be spent casting the spell, so actual damage is 1 less
|
||||
}
|
||||
}
|
||||
return this.damageTargetAI(ai, sa, dmg, true);
|
||||
if (!this.damageTargetAI(ai, sa, dmg, true)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,7 +97,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
|
||||
if (damage.equals("X")) {
|
||||
if (sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
|
||||
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
// 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()) {
|
||||
@@ -129,8 +114,8 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
|
||||
// Set PayX here to maximum value. It will be adjusted later depending on the target.
|
||||
sa.setXManaCostPaid(dmg);
|
||||
} else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) {
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
} else if (sa.getSVar(damage).contains("InYourHand") && source.getZone().is(ZoneType.Hand)) {
|
||||
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")) {
|
||||
// cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact
|
||||
@@ -173,15 +158,15 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ai.getCreaturesAttackedThisTurn().isEmpty()) {
|
||||
if (ai.getAttackedWithCreatureThisTurn()) {
|
||||
dmg = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
||||
}
|
||||
} else if ("WildHunt".equals(logic)) {
|
||||
// This dummy ability will just deal 0 damage, but holds the logic for the AI for Master of Wild Hunt
|
||||
List<Card> wolves = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), "Creature.Wolf+untapped+YouCtrl+Other", ai, source, sa);
|
||||
List<Card> wolves = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), "Creature.Wolf+untapped+YouCtrl+Other", ai, source);
|
||||
dmg = Aggregates.sum(wolves, CardPredicates.Accessors.fnGetNetPower);
|
||||
} else if ("Triskelion".equals(logic)) {
|
||||
final int n = source.getCounters(CounterEnumType.P1P1);
|
||||
final int n = source.getCounters(CounterType.P1P1);
|
||||
if (n > 0) {
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
/*
|
||||
@@ -214,14 +199,14 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
|
||||
if (sourceName.equals("Sorin, Grim Nemesis")) {
|
||||
int loyalty = source.getCounters(CounterEnumType.LOYALTY);
|
||||
int loyalty = source.getCounters(CounterType.LOYALTY);
|
||||
for (; loyalty > 0; loyalty--) {
|
||||
if (this.damageTargetAI(ai, sa, loyalty, false)) {
|
||||
dmg = ComputerUtilCombat.getEnoughDamageToKill(sa.getTargetCard(), loyalty, source, false, false);
|
||||
if (dmg > loyalty || dmg < 1) {
|
||||
continue; // in case the calculation gets messed up somewhere
|
||||
}
|
||||
sa.setXManaCostPaid(dmg);
|
||||
source.setSVar("ChosenX", "Number$" + dmg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -241,11 +226,11 @@ public class DamageDealAi extends DamageAiBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) {
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("DiscardLands".equals(sa.getParam("AILogic")) && !ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
||||
if ("DiscardLands".equals(sa.getParam("AILogic")) && !ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -282,30 +267,25 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
}
|
||||
|
||||
if ((damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) ||
|
||||
if ((damage.equals("X") && source.getSVar(damage).equals("Count$xPaid")) ||
|
||||
sourceName.equals("Crater's Claws")){
|
||||
// If I can kill my target by paying less mana, do it
|
||||
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.isDividedAsYouChoose()) {
|
||||
int actualPay = dmg;
|
||||
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) {
|
||||
int actualPay = 0;
|
||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||
final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||
if (adjDamage < actualPay) {
|
||||
if ((adjDamage > actualPay) && (adjDamage <= dmg)) {
|
||||
actualPay = adjDamage;
|
||||
}
|
||||
}
|
||||
if (sourceName.equals("Crater's Claws") && ai.hasFerocious()) {
|
||||
actualPay = actualPay > 2 ? actualPay - 2 : 0;
|
||||
}
|
||||
sa.setXManaCostPaid(actualPay);
|
||||
source.setSVar("PayX", Integer.toString(actualPay));
|
||||
}
|
||||
}
|
||||
|
||||
if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
||||
final int cmc = sa.getXManaCostPaid();
|
||||
return !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc)).isEmpty();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -449,19 +429,19 @@ public class DamageDealAi extends DamageAiBase {
|
||||
// As of right now, ranks planeswalkers by their Current Loyalty * 10 + Big buff if close to "Ultimate"
|
||||
int bestScore = 0;
|
||||
for (Card pw : pws) {
|
||||
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
||||
int curLoyalty = pw.getCounters(CounterType.LOYALTY);
|
||||
int pwScore = curLoyalty * 10;
|
||||
|
||||
for (SpellAbility sa : pw.getSpellAbilities()) {
|
||||
if (sa.hasParam("Ultimate")) {
|
||||
Integer loyaltyCost = 0;
|
||||
if (sa.hasParam("Ultimate") && sa.getPayCosts() != null) {
|
||||
int loyaltyCost = 0;
|
||||
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
|
||||
if (remLoyalty != null) {
|
||||
// if remLoyalty is null, generally there's an AddCounter<0/LOYALTY> cost, like for Gideon Jura.
|
||||
loyaltyCost = remLoyalty.convertAmount();
|
||||
}
|
||||
|
||||
if (loyaltyCost != null && loyaltyCost != 0 && loyaltyCost - curLoyalty <= 1) {
|
||||
if (loyaltyCost != 0 && loyaltyCost - curLoyalty <= 1) {
|
||||
// Will ultimate soon
|
||||
pwScore += 10000;
|
||||
}
|
||||
@@ -477,22 +457,6 @@ public class DamageDealAi extends DamageAiBase {
|
||||
return bestTgt;
|
||||
}
|
||||
|
||||
private Card getWorstPlaneswalkerToDamage(final List<Card> pws) {
|
||||
Card bestTgt = null;
|
||||
|
||||
int bestScore = Integer.MAX_VALUE;
|
||||
for (Card pw : pws) {
|
||||
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
||||
|
||||
if (curLoyalty < bestScore) {
|
||||
bestScore = curLoyalty;
|
||||
bestTgt = pw;
|
||||
}
|
||||
}
|
||||
|
||||
return bestTgt;
|
||||
}
|
||||
|
||||
|
||||
private List<Card> getTargetableCards(Player ai, SpellAbility sa, Player pl, TargetRestrictions tgt, Player activator, Card source, Game game) {
|
||||
List<Card> hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa);
|
||||
@@ -501,16 +465,18 @@ public class DamageDealAi extends DamageAiBase {
|
||||
hPlay = CardLists.filterControlledBy(hPlay, pl);
|
||||
}
|
||||
|
||||
final List<GameObject> objects = Lists.newArrayList(sa.getTargets());
|
||||
final List<GameObject> objects = Lists.newArrayList(sa.getTargets().getTargets());
|
||||
if (sa.hasParam("TargetUnique")) {
|
||||
objects.addAll(sa.getUniqueTargets());
|
||||
}
|
||||
for (final Object o : objects) {
|
||||
if (o instanceof Card) {
|
||||
final Card c = (Card) o;
|
||||
if (hPlay.contains(c)) {
|
||||
hPlay.remove(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
hPlay = CardLists.getTargetableCards(hPlay, sa);
|
||||
return hPlay;
|
||||
}
|
||||
@@ -564,11 +530,11 @@ public class DamageDealAi extends DamageAiBase {
|
||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||
final Game game = source.getGame();
|
||||
final PhaseHandler phase = game.getPhaseHandler();
|
||||
final boolean divided = sa.isDividedAsYouChoose();
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer");
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
Player enemy = ai.getWeakestOpponent();
|
||||
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
if ("PowerDmg".equals(logic)) {
|
||||
// check if it is better to target the player instead, the original target is already set in PumpAi.pumpTgtAI()
|
||||
@@ -577,13 +543,6 @@ public class DamageDealAi extends DamageAiBase {
|
||||
sa.getTargets().add(enemy);
|
||||
}
|
||||
return true;
|
||||
} else if ("DamageAfterPutCounter".equals(logic)
|
||||
&& sa.getParent() != null
|
||||
&& "P1P1".equals(sa.getParent().getParam("CounterType"))) {
|
||||
// assuming the SA parent is of PutCounter type. Perhaps it's possible to predict counter multipliers here somehow?
|
||||
final String amountStr = sa.getParent().getParamOrDefault("CounterNum", "1");
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
dmg += amount;
|
||||
}
|
||||
|
||||
// AssumeAtLeastOneTarget is used for cards with funky targeting implementation like Fight with Fire which would
|
||||
@@ -594,10 +553,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
|
||||
immediately |= ComputerUtil.playImmediately(ai, sa);
|
||||
|
||||
if (!(sa.getParent() != null && sa.getParent().isTargetNumberValid())) {
|
||||
sa.resetTargets();
|
||||
}
|
||||
|
||||
// target loop
|
||||
TargetChoices tcs = sa.getTargets();
|
||||
|
||||
@@ -630,7 +586,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (assignedDamage <= dmg
|
||||
&& humanCreature.getShieldCount() == 0 && !ComputerUtil.canRegenerate(humanCreature.getController(), humanCreature)) {
|
||||
tcs.add(humanCreature);
|
||||
sa.addDividedAllocation(humanCreature, assignedDamage);
|
||||
tgt.addDividedAllocation(humanCreature, assignedDamage);
|
||||
lastTgt = humanCreature;
|
||||
dmg -= assignedDamage;
|
||||
}
|
||||
@@ -642,7 +598,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
}
|
||||
if (dmg > 0 && lastTgt != null) {
|
||||
sa.addDividedAllocation(lastTgt, sa.getDividedValue(lastTgt) + dmg);
|
||||
tgt.addDividedAllocation(lastTgt, tgt.getDividedValue(lastTgt) + dmg);
|
||||
dmg = 0;
|
||||
return true;
|
||||
}
|
||||
@@ -652,20 +608,20 @@ public class DamageDealAi extends DamageAiBase {
|
||||
continue;
|
||||
}
|
||||
tcs.add(humanCreature);
|
||||
sa.addDividedAllocation(humanCreature, dmg);
|
||||
tgt.addDividedAllocation(humanCreature, dmg);
|
||||
dmg = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int totalTargetedSoFar = -1;
|
||||
while (sa.canAddMoreTarget()) {
|
||||
if (totalTargetedSoFar == tcs.size()) {
|
||||
while (tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
if (totalTargetedSoFar == tcs.getNumTargeted()) {
|
||||
// Avoid looping endlessly when choosing targets for cards with variable target number and type
|
||||
// like Jaya's Immolating Inferno
|
||||
break;
|
||||
}
|
||||
totalTargetedSoFar = tcs.size();
|
||||
totalTargetedSoFar = tcs.getNumTargeted();
|
||||
if (oppTargetsChoice && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) {
|
||||
// canPlayAI (sa activated by ai)
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
@@ -679,9 +635,10 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (c != null && !this.shouldTgtP(ai, sa, dmg, noPrevention, true)) {
|
||||
tcs.add(c);
|
||||
if (divided) {
|
||||
int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||
assignedDamage = Math.min(dmg, assignedDamage);
|
||||
sa.addDividedAllocation(c, assignedDamage);
|
||||
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||
if (assignedDamage <= dmg) {
|
||||
tgt.addDividedAllocation(c, assignedDamage);
|
||||
}
|
||||
dmg = dmg - assignedDamage;
|
||||
if (dmg <= 0) {
|
||||
break;
|
||||
@@ -697,13 +654,13 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (this.shouldTgtP(ai, sa, dmg, noPrevention)) {
|
||||
tcs.add(enemy);
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(enemy, dmg);
|
||||
tgt.addDividedAllocation(enemy, dmg);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ("RoundedDown".equals(sa.getParam("DivideEvenly"))) {
|
||||
dmg = dmg * sa.getTargets().size() / (sa.getTargets().size() +1);
|
||||
dmg = dmg * sa.getTargets().getNumTargeted() / (sa.getTargets().getNumTargeted() +1);
|
||||
}
|
||||
|
||||
// look for creature targets; currently also catches planeswalkers that can be killed immediately
|
||||
@@ -719,7 +676,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (divided) {
|
||||
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||
if (assignedDamage <= dmg) {
|
||||
sa.addDividedAllocation(c, assignedDamage);
|
||||
tgt.addDividedAllocation(c, assignedDamage);
|
||||
}
|
||||
dmg = dmg - assignedDamage;
|
||||
if (dmg <= 0) {
|
||||
@@ -739,7 +696,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
boolean freePing = immediately || abCost == null
|
||||
|| sa.getTargets().size() > 0;
|
||||
|| sa.getTargets().getNumTargeted() > 0;
|
||||
|
||||
if (!source.isSpell()) {
|
||||
if (phase.is(PhaseType.END_OF_TURN) && sa.isAbility() && abCost.isReusuableResource()) {
|
||||
@@ -748,7 +705,8 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
|
||||
if (phase.is(PhaseType.MAIN2) && sa.isAbility()) {
|
||||
if (sa.isPwAbility() || source.hasSVar("EndOfTurnLeavePlay"))
|
||||
if (sa.getRestrictions().isPwAbility()
|
||||
|| source.hasSVar("EndOfTurnLeavePlay"))
|
||||
freePing = true;
|
||||
}
|
||||
}
|
||||
@@ -756,7 +714,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (freePing && sa.canTarget(enemy) && (!avoidTargetP(ai, sa))) {
|
||||
tcs.add(enemy);
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(enemy, dmg);
|
||||
tgt.addDividedAllocation(enemy, dmg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -774,9 +732,9 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (divided) {
|
||||
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||
if (assignedDamage <= dmg) {
|
||||
sa.addDividedAllocation(c, assignedDamage);
|
||||
tgt.addDividedAllocation(c, assignedDamage);
|
||||
} else {
|
||||
sa.addDividedAllocation(c, dmg);
|
||||
tgt.addDividedAllocation(c, dmg);
|
||||
}
|
||||
dmg = dmg - assignedDamage;
|
||||
if (dmg <= 0) {
|
||||
@@ -787,35 +745,36 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
} else if ("OppAtTenLife".equals(logic)) {
|
||||
for (final Player p : ai.getOpponents()) {
|
||||
if (sa.canTarget(p) && p.getLife() == 10 && tcs.size() < tgt.getMaxTargets(source, sa)) {
|
||||
if (sa.canTarget(p) && p.getLife() == 10 && tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
tcs.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Improve Damage, we shouldn't just target the player just
|
||||
// because we can
|
||||
if (sa.canTarget(enemy) && tcs.size() < tgt.getMaxTargets(source, sa)) {
|
||||
if (sa.canTarget(enemy) && tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
||||
|| ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai))
|
||||
|| immediately || shouldTgtP(ai, sa, dmg, noPrevention)) &&
|
||||
|| sa.getPayCosts() == null || immediately
|
||||
|| this.shouldTgtP(ai, sa, dmg, noPrevention)) &&
|
||||
(!avoidTargetP(ai, sa))) {
|
||||
tcs.add(enemy);
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(enemy, dmg);
|
||||
tgt.addDividedAllocation(enemy, dmg);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// fell through all the choices, no targets left?
|
||||
if (tcs.size() < tgt.getMinTargets(source, sa) || tcs.size() == 0) {
|
||||
if (tcs.getNumTargeted() < tgt.getMinTargets(source, sa) || tcs.getNumTargeted() == 0) {
|
||||
if (!mandatory) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// If the trigger is mandatory, gotta choose my own stuff now
|
||||
return this.damageChooseRequiredTargets(ai, sa, tgt, dmg);
|
||||
return this.damageChooseRequiredTargets(ai, sa, tgt, dmg, mandatory);
|
||||
}
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
@@ -892,21 +851,24 @@ public class DamageDealAi extends DamageAiBase {
|
||||
* a {@link forge.game.spellability.TargetRestrictions} object.
|
||||
* @param dmg
|
||||
* a int.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean damageChooseRequiredTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, final int dmg) {
|
||||
private boolean damageChooseRequiredTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, final int dmg,
|
||||
final boolean mandatory) {
|
||||
// this is for Triggered targets that are mandatory
|
||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||
final boolean divided = sa.isDividedAsYouChoose();
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
while (sa.canAddMoreTarget()) {
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (tgt.canTgtPlaneswalker()) {
|
||||
final Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, ai, true);
|
||||
final Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, ai, mandatory);
|
||||
if (c != null) {
|
||||
sa.getTargets().add(c);
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(c, dmg);
|
||||
tgt.addDividedAllocation(c, dmg);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
@@ -915,11 +877,11 @@ public class DamageDealAi extends DamageAiBase {
|
||||
|
||||
// TODO: This currently also catches planeswalkers that can be killed (still necessary? Or can be removed?)
|
||||
if (tgt.canTgtCreature()) {
|
||||
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, ai, true);
|
||||
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, ai, mandatory);
|
||||
if (c != null) {
|
||||
sa.getTargets().add(c);
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(c, dmg);
|
||||
tgt.addDividedAllocation(c, dmg);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
@@ -929,33 +891,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (sa.canTarget(opp)) {
|
||||
if (sa.getTargets().add(opp)) {
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(opp, dmg);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// See if there's an indestructible target that can be used
|
||||
CardCollection indestructible = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
||||
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.Presets.PLANESWALKERS, CardPredicates.hasKeyword(Keyword.INDESTRUCTIBLE), CardPredicates.isTargetableBy(sa)));
|
||||
|
||||
if (!indestructible.isEmpty()) {
|
||||
Card c = ComputerUtilCard.getWorstPermanentAI(indestructible, false, false, false, false);
|
||||
sa.getTargets().add(c);
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(c, dmg);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (tgt.canTgtPlaneswalker()) {
|
||||
// Second pass for planeswalkers: choose AI's worst planeswalker
|
||||
final Card c = getWorstPlaneswalkerToDamage(CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.and(CardPredicates.Presets.PLANESWALKERS), CardPredicates.isTargetableBy(sa)));
|
||||
if (c != null) {
|
||||
sa.getTargets().add(c);
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(c, dmg);
|
||||
tgt.addDividedAllocation(opp, dmg);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
@@ -965,7 +901,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (sa.canTarget(ai)) {
|
||||
if (sa.getTargets().add(ai)) {
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(ai, dmg);
|
||||
tgt.addDividedAllocation(ai, dmg);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
@@ -993,19 +929,22 @@ public class DamageDealAi extends DamageAiBase {
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setXManaCostPaid(dmg);
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
}
|
||||
|
||||
if (!sa.usesTargeting()) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt == null) {
|
||||
// If it's not mandatory check a few things
|
||||
return mandatory || this.damageChooseNontargeted(ai, sa, dmg);
|
||||
if (!mandatory && !this.damageChooseNontargeted(ai, sa, dmg)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!this.damageChoosingTargets(ai, sa, sa.getTargetRestrictions(), dmg, mandatory, true) && !mandatory) {
|
||||
if (!this.damageChoosingTargets(ai, sa, tgt, dmg, mandatory, true) && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid") && !sa.isDividedAsYouChoose()) {
|
||||
if (damage.equals("X") && source.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) {
|
||||
// If I can kill my target by paying less mana, do it
|
||||
int actualPay = 0;
|
||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||
@@ -1021,7 +960,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
}
|
||||
|
||||
sa.setXManaCostPaid(actualPay);
|
||||
source.setSVar("PayX", Integer.toString(actualPay));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1081,7 +1020,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
saTgt.resetTargets();
|
||||
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
|
||||
|
||||
sa.setXManaCostPaid(dmg);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1128,7 +1067,8 @@ public class DamageDealAi extends DamageAiBase {
|
||||
continue;
|
||||
}
|
||||
// currently works only with cards that don't have additional costs (only mana is supported)
|
||||
if (ab.getPayCosts().hasNoManaCost() || ab.getPayCosts().hasOnlySpecificCostType(CostPartMana.class)) {
|
||||
if (ab.getPayCosts() != null
|
||||
&& (ab.getPayCosts().hasNoManaCost() || ab.getPayCosts().hasOnlySpecificCostType(CostPartMana.class))) {
|
||||
String dmgDef = "0";
|
||||
if (ab.getApi() == ApiType.DealDamage) {
|
||||
dmgDef = ab.getParamOrDefault("NumDmg", "0");
|
||||
@@ -1140,7 +1080,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
continue; // not a toughness debuff
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNumeric(dmgDef)) { // currently doesn't work for X and other dependent costs
|
||||
if (StringUtils.isNumeric(dmgDef) && ab.canPlay()) { // currently doesn't work for X and other dependent costs
|
||||
if (sa.usesTargeting() && ab.usesTargeting()) {
|
||||
// Ensure that the chained spell can target at least the same things (or more) as the current one
|
||||
TargetRestrictions tgtSa = sa.getTargetRestrictions();
|
||||
@@ -1152,7 +1092,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
|
||||
// FIXME: should it also check restrictions for targeting players?
|
||||
ManaCost costSa = sa.getPayCosts().getTotalMana();
|
||||
ManaCost costSa = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : ManaCost.NO_COST;
|
||||
ManaCost costAb = ab.getPayCosts().getTotalMana(); // checked for null above
|
||||
ManaCost total = ManaCost.combine(costSa, costAb);
|
||||
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
|
||||
|
||||
@@ -48,7 +48,7 @@ public class DamageEachAi extends DamageAiBase {
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
return mandatory || canPlayAI(ai, sa);
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -25,6 +19,9 @@ import forge.game.spellability.TargetChoices;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DamagePreventAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
@@ -36,7 +33,20 @@ public class DamagePreventAi extends SpellAbilityAi {
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
if (!willPayCosts(ai, sa, cost, hostCard)) {
|
||||
// temporarily disabled until better AI
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -90,7 +100,7 @@ public class DamagePreventAi extends SpellAbilityAi {
|
||||
tcs.add(ai);
|
||||
chance = true;
|
||||
}
|
||||
final List<Card> threatenedTargets = new ArrayList<>();
|
||||
final List<Card> threatenedTargets = new ArrayList<Card>();
|
||||
// filter AIs battlefield by what I can target
|
||||
List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard, sa);
|
||||
targetables = CardLists.getTargetableCards(targetables, sa);
|
||||
@@ -129,15 +139,15 @@ public class DamagePreventAi extends SpellAbilityAi {
|
||||
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||
|
||||
for (final Card c : combatants) {
|
||||
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.size() < tgt.getMaxTargets(hostCard, sa)) {
|
||||
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) {
|
||||
tcs.add(c);
|
||||
chance = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sa.usesTargeting() && sa.isDividedAsYouChoose() && !sa.getTargets().isEmpty()) {
|
||||
sa.addDividedAllocation(sa.getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||
if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
|
||||
tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||
}
|
||||
|
||||
return chance;
|
||||
@@ -169,11 +179,12 @@ public class DamagePreventAi extends SpellAbilityAi {
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
// filter AIs battlefield by what I can target
|
||||
final Game game = ai.getGame();
|
||||
CardCollectionView targetables = game.getCardsIn(ZoneType.Battlefield);
|
||||
targetables = CardLists.getTargetableCards(targetables, sa);
|
||||
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
|
||||
Card target = null;
|
||||
|
||||
@@ -204,8 +215,8 @@ public class DamagePreventAi extends SpellAbilityAi {
|
||||
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
|
||||
}
|
||||
sa.getTargets().add(target);
|
||||
if (sa.isDividedAsYouChoose()) {
|
||||
sa.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||
if (sa.hasParam("DividedAsYouChoose")) {
|
||||
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
@@ -20,7 +22,19 @@ public class DamagePreventAllAi extends SpellAbilityAi {
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
// temporarily disabled until better AI
|
||||
if (!willPayCosts(ai, sa, cost, hostCard)) {
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -25,6 +22,10 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class DebuffAi extends SpellAbilityAi {
|
||||
// *************************************************************************
|
||||
// ***************************** Debuff ************************************
|
||||
@@ -50,7 +51,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -67,7 +68,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (!sa.usesTargeting()) {
|
||||
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
|
||||
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
|
||||
@@ -93,7 +94,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
if (!sa.usesTargeting()) {
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
|
||||
// here?
|
||||
} else {
|
||||
@@ -124,7 +125,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
CardCollection list = getCurseCreatures(ai, sa, kws == null ? Lists.newArrayList() : kws);
|
||||
CardCollection list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
|
||||
// several uses here:
|
||||
@@ -138,12 +139,12 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
Card t = null;
|
||||
// boolean goodt = false;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
|
||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
if (mandatory) {
|
||||
return debuffMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
@@ -176,7 +177,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
* @return a CardCollection.
|
||||
*/
|
||||
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
||||
if (!list.isEmpty()) {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@@ -216,11 +217,11 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
list.remove(c);
|
||||
}
|
||||
|
||||
final CardCollection pref = CardLists.filterControlledBy(list, ai.getWeakestOpponent());
|
||||
final CardCollection pref = CardLists.filterControlledBy(list, ComputerUtil.getOpponentFor(ai));
|
||||
final CardCollection forced = CardLists.filterControlledBy(list, ai);
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) {
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
if (pref.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
@@ -237,7 +238,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
while (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (forced.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
@@ -256,7 +257,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
@@ -266,7 +267,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<>();
|
||||
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<String>();
|
||||
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (mandatory) {
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.card.mana.ManaCost;
|
||||
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.ability.AbilityFactory;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class DelayedTriggerAi extends SpellAbilityAi {
|
||||
|
||||
@@ -28,14 +18,16 @@ public class DelayedTriggerAi extends SpellAbilityAi {
|
||||
// TODO: improve ai
|
||||
return true;
|
||||
}
|
||||
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
||||
if (trigsa == null) {
|
||||
return false;
|
||||
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);
|
||||
return SpellApiToAi.Converter.get(((AbilitySub) trigsa).getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
||||
} else {
|
||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||
}
|
||||
@@ -43,11 +35,12 @@ public class DelayedTriggerAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
||||
if (trigsa == null) {
|
||||
return false;
|
||||
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);
|
||||
|
||||
@@ -60,95 +53,11 @@ public class DelayedTriggerAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
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 = sa.getAdditionalAbility("Execute");
|
||||
if (trigsa == null) {
|
||||
return false;
|
||||
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);
|
||||
|
||||
@@ -1,23 +1,10 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialAiLogic;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostSacrifice;
|
||||
@@ -26,6 +13,7 @@ import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class DestroyAi extends SpellAbilityAi {
|
||||
@@ -35,19 +23,89 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||
// 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();
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if ("MadSarkhanDragon".equals(aiLogic)) {
|
||||
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
|
||||
} else if (aiLogic.startsWith("MinLoyalty.")) {
|
||||
int minLoyalty = Integer.parseInt(aiLogic.substring(aiLogic.indexOf(".") + 1));
|
||||
if (source.getCounters(CounterEnumType.LOYALTY) < minLoyalty) {
|
||||
final boolean noRegen = sa.hasParam("NoRegen");
|
||||
final String logic = sa.getParam("AILogic");
|
||||
boolean hasXCost = false;
|
||||
|
||||
CardCollection list;
|
||||
|
||||
if (abCost != null) {
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("Polymorph".equals(aiLogic)) {
|
||||
CardCollection list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
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 : false;
|
||||
}
|
||||
|
||||
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.getAttackedWithCreatureThisTurn()) {
|
||||
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();
|
||||
if (sa.hasParam("TargetingPlayer")) {
|
||||
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);
|
||||
} else if (logic != null && logic.startsWith("MinLoyalty.")) {
|
||||
int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
||||
if (source.getCounters(CounterType.LOYALTY) < minLoyalty) {
|
||||
return false;
|
||||
}
|
||||
} else if ("Polymorph".equals(logic)) {
|
||||
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -66,103 +124,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
sa.getTargets().add(worst);
|
||||
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);
|
||||
if ("FatalPush".equals(logic)) {
|
||||
final int cmcMax = ai.hasRevolt() ? 4 : 2;
|
||||
@@ -170,7 +132,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
||||
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, true);
|
||||
|
||||
list = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
|
||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||
@@ -199,7 +161,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
//Check for undying
|
||||
return (!c.hasKeyword(Keyword.UNDYING) || c.getCounters(CounterEnumType.P1P1) > 0);
|
||||
return (!c.hasKeyword(Keyword.UNDYING) || c.getCounters(CounterType.P1P1) > 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -222,12 +184,33 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
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
|
||||
// TODO use can add more Targets
|
||||
while (sa.getTargets().size() < maxTargets) {
|
||||
while (sa.getTargets().getNumTargeted() < maxTargets) {
|
||||
if (list.isEmpty()) {
|
||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
@@ -246,6 +229,19 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ("Pongify".equals(logic)) {
|
||||
final Card token = TokenAi.spawnToken(choice.getController(), sa.getSubAbility());
|
||||
if (token == null) {
|
||||
return true; // becomes Terminate
|
||||
} else {
|
||||
if (source.getGame().getPhaseHandler().getPhase()
|
||||
.isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) || // prevent surprise combatant
|
||||
ComputerUtilCard.evaluateCreature(choice) < 1.5
|
||||
* ComputerUtilCard.evaluateCreature(token)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestLandAI(list);
|
||||
|
||||
@@ -259,14 +255,15 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||
}
|
||||
//option to hold removal instead only applies for single targeted removal
|
||||
if (!sa.isTrigger() && sa.getMaxTargets() == 1) {
|
||||
if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
|
||||
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
@@ -280,7 +277,6 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
SpellAbility sp = aura.getFirstSpellAbility();
|
||||
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
|
||||
&& aura.getController() != ai && sa.canTarget(aura)) {
|
||||
list.remove(choice);
|
||||
choice = aura;
|
||||
}
|
||||
}
|
||||
@@ -292,7 +288,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
} else if (sa.hasParam("Defined")) {
|
||||
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||
if ("WillSkipTurn".equals(logic) && (sa.getHostCard().getController().equals(ai)
|
||||
|| ai.getCreaturesInPlay().size() < ai.getWeakestOpponent().getCreaturesInPlay().size()
|
||||
|| ai.getCreaturesInPlay().size() < ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size()
|
||||
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|
||||
|| ai.getLife() <= 5)) {
|
||||
// Basic ai logic for Lethal Vapors
|
||||
@@ -310,19 +306,22 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
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");
|
||||
if (sa.usesTargeting()) {
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
|
||||
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
if (list.isEmpty() || list.size() < sa.getMinTargets()) {
|
||||
return false;
|
||||
}
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
|
||||
// Try to avoid targeting creatures that are dead on board
|
||||
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);
|
||||
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
|
||||
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
||||
@@ -343,7 +342,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
preferred = ComputerUtil.filterAITgts(sa, ai, preferred, true);
|
||||
preferred = ComputerUtil.filterAITgts(sa, ai, (CardCollection)preferred, true);
|
||||
|
||||
for (final Card c : preferred) {
|
||||
list.remove(c);
|
||||
@@ -353,9 +352,10 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (sa.canAddMoreTarget()) {
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
if (preferred.isEmpty()) {
|
||||
if (!sa.isMinTargetChosen()) {
|
||||
if (sa.getTargets().getNumTargeted() == 0
|
||||
|| sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (!mandatory) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
@@ -379,7 +379,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
while (sa.canAddMoreTarget()) {
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (list.isEmpty()) {
|
||||
break;
|
||||
} else {
|
||||
@@ -400,11 +400,16 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
return sa.isTargetNumberValid();
|
||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return mandatory;
|
||||
if (!mandatory) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLand, String logic) {
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.ai.AiBlockController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.MagicColor;
|
||||
import forge.ai.*;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.game.combat.Combat;
|
||||
|
||||
public class DestroyAllAi extends SpellAbilityAi {
|
||||
|
||||
@@ -45,7 +37,8 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return doMassRemovalLogic(aiPlayer, sa);
|
||||
//TODO: Check for bad outcome
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,7 +66,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
Player opponent = ai.getWeakestOpponent(); // TODO: how should this AI logic work for multiplayer and getOpponents()?
|
||||
Player opponent = ComputerUtil.getOpponentFor(ai); // TODO: how should this AI logic work for multiplayer and getOpponents()?
|
||||
|
||||
final int CREATURE_EVAL_THRESHOLD = 200;
|
||||
|
||||
@@ -86,10 +79,10 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
|
||||
if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setXManaCostPaid(xPay);
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
valid = valid.replace("X", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
@@ -114,14 +107,6 @@ 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 ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
||||
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
|
||||
@@ -186,6 +171,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.TextUtil;
|
||||
@@ -32,14 +20,10 @@ public class DigAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final Card host = sa.getHostCard();
|
||||
Player libraryOwner = ai;
|
||||
|
||||
if (!willPayCosts(ai, sa, sa.getPayCosts(), host)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
@@ -58,9 +42,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
if ("Never".equals(sa.getParam("AILogic"))) {
|
||||
return false;
|
||||
} else if ("AtOppEndOfTurn".equals(sa.getParam("AILogic"))) {
|
||||
if (!(game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN))) {
|
||||
return false;
|
||||
}
|
||||
return game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
|
||||
// don't deck yourself
|
||||
@@ -78,11 +60,9 @@ public class DigAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
final String num = sa.getParam("DigNum");
|
||||
final boolean payXLogic = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayX");
|
||||
if (num != null && (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) || payXLogic) {
|
||||
if (num != null && num.equals("X") && host.getSVar(num).equals("Count$xPaid")) {
|
||||
// By default, set PayX here to maximum value.
|
||||
SpellAbility root = sa.getRootAbility();
|
||||
if (root.getXManaCostPaid() == null) {
|
||||
if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) {
|
||||
int manaToSave = 0;
|
||||
|
||||
// Special logic that asks the AI to conserve a certain amount of mana when paying X
|
||||
@@ -90,11 +70,11 @@ public class DigAi extends SpellAbilityAi {
|
||||
manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
|
||||
}
|
||||
|
||||
int numCards = ComputerUtilCost.getMaxXValue(sa, ai) - manaToSave;
|
||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
|
||||
if (numCards <= 0) {
|
||||
return false;
|
||||
}
|
||||
root.setXManaCostPaid(numCards);
|
||||
host.setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +99,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
@@ -133,37 +112,18 @@ public class DigAi extends SpellAbilityAi {
|
||||
// Triggers that ask to pay {X} (e.g. Depala, Pilot Exemplar).
|
||||
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
|
||||
int manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
|
||||
int numCards = ComputerUtilCost.getMaxXValue(sa, ai) - manaToSave;
|
||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
|
||||
if (numCards <= 0) {
|
||||
return mandatory;
|
||||
}
|
||||
root.setXManaCostPaid(numCards);
|
||||
sa.getHostCard().setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer, Map<String, Object> params) {
|
||||
if ("DigForCreature".equals(sa.getParam("AILogic"))) {
|
||||
Card bestChoice = ComputerUtilCard.getBestCreatureAI(valid);
|
||||
if (bestChoice == null) {
|
||||
// no creatures, but maybe there's a morphable card that can be played as a creature?
|
||||
CardCollection morphs = CardLists.filter(valid, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return card.hasKeyword(Keyword.MORPH);
|
||||
}
|
||||
});
|
||||
if (!morphs.isEmpty()) {
|
||||
bestChoice = ComputerUtilCard.getBestAI(morphs);
|
||||
}
|
||||
}
|
||||
|
||||
// still nothing, so return the worst card since it'll be unplayable from exile (e.g. Vivien, Champion of the Wilds)
|
||||
return bestChoice != null ? bestChoice : ComputerUtilCard.getWorstAI(valid);
|
||||
}
|
||||
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) {
|
||||
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
|
||||
return ComputerUtilCard.getWorstPermanentAI(valid, false, true, false, false);
|
||||
} else {
|
||||
@@ -171,15 +131,6 @@ public class DigAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
// an opponent choose a card from
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
|
||||
public class DigMultipleAi extends SpellAbilityAi {
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
final Card host = sa.getHostCard();
|
||||
Player libraryOwner = ai;
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
} else {
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
libraryOwner = opp;
|
||||
}
|
||||
|
||||
// return false if nothing to dig into
|
||||
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("Never".equals(sa.getParam("AILogic"))) {
|
||||
return false;
|
||||
} else if ("AtOppEndOfTurn".equals(sa.getParam("AILogic"))) {
|
||||
if (!(game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// don't deck yourself
|
||||
if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) {
|
||||
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
|
||||
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't use draw abilities before main 2 if possible
|
||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|
||||
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
|
||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !ComputerUtil.preventRunAwayActivations(sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else if (mandatory && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
@@ -10,10 +9,13 @@ import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DigUntilAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
@@ -34,7 +36,7 @@ public class DigUntilAi extends SpellAbilityAi {
|
||||
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
Player libraryOwner = ai;
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
if ("DontMillSelf".equals(logic)) {
|
||||
// A card that digs for specific things and puts everything revealed before it into graveyard
|
||||
@@ -75,15 +77,14 @@ public class DigUntilAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
final String num = sa.getParam("Amount");
|
||||
if ((num != null) && num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) {
|
||||
if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
SpellAbility root = sa.getRootAbility();
|
||||
if (root.getXManaCostPaid() == null) {
|
||||
int numCards = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) {
|
||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
if (numCards <= 0) {
|
||||
return false;
|
||||
}
|
||||
root.setXManaCostPaid(numCards);
|
||||
source.setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
@@ -22,6 +15,8 @@ import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DiscardAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
@@ -32,11 +27,26 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
// temporarily disabled until better AI
|
||||
if (!willPayCosts(ai, sa, abCost, source)) {
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
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;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ("Chandra, Flamecaller".equals(sourceName)) {
|
||||
final int hand = ai.getCardsIn(ZoneType.Hand).size();
|
||||
return MyRandom.getRandom().nextFloat() < (1.0 / (1 + hand));
|
||||
@@ -46,7 +56,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
return SpecialCardAi.VolrathsShapeshifter.consider(ai, sa);
|
||||
}
|
||||
|
||||
final boolean humanHasHand = ai.getWeakestOpponent().getCardsIn(ZoneType.Hand).size() > 0;
|
||||
final boolean humanHasHand = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).size() > 0;
|
||||
|
||||
if (tgt != null) {
|
||||
if (!discardTargetAI(ai, sa)) {
|
||||
@@ -75,14 +85,14 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (sa.hasParam("NumCards")) {
|
||||
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.
|
||||
final int cardsToDiscard = Math.min(ComputerUtilCost.getMaxXValue(sa, ai), ai.getWeakestOpponent()
|
||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
|
||||
.getCardsIn(ZoneType.Hand).size());
|
||||
if (cardsToDiscard < 1) {
|
||||
return false;
|
||||
}
|
||||
sa.setXManaCostPaid(cardsToDiscard);
|
||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
} else {
|
||||
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
|
||||
return false;
|
||||
@@ -120,20 +130,12 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
// Don't use discard abilities before main 2 if possible
|
||||
// Don't use draw abilities before main 2 if possible
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases") && !aiLogic.startsWith("AnyPhase")) {
|
||||
&& !sa.hasParam("ActivationPhases")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aiLogic.equals("AnyPhaseIfFavored")) {
|
||||
if (ai.getGame().getCombat() != null) {
|
||||
if (ai.getCardsIn(ZoneType.Hand).size() < ai.getGame().getCombat().getDefenderPlayerByAttacker(source).getCardsIn(ZoneType.Hand).size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
@@ -148,11 +150,9 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
|
||||
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
|
||||
continue;
|
||||
} else if (!opp.canDiscardBy(sa)) { // e.g. Tamiyo, Collector of Tales
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
if (tgt != null) {
|
||||
if (sa.canTarget(opp)) {
|
||||
@@ -160,7 +160,6 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} // discardTargetAI()
|
||||
|
||||
@@ -170,7 +169,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (!discardTargetAI(ai, sa)) {
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
@@ -189,11 +188,11 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
}
|
||||
if ("X".equals(sa.getParam("RevealNumber")) && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
if ("X".equals(sa.getParam("RevealNumber")) && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int cardsToDiscard = Math.min(ComputerUtilCost.getMaxXValue(sa, ai), ai.getWeakestOpponent()
|
||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
|
||||
.getCardsIn(ZoneType.Hand).size());
|
||||
sa.setXManaCostPaid(cardsToDiscard);
|
||||
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
@@ -10,6 +9,8 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DrainManaAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
@@ -18,7 +19,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
if (tgt == null) {
|
||||
@@ -40,7 +41,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
@@ -51,9 +52,12 @@ public class DrainManaAi extends SpellAbilityAi {
|
||||
} else {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
return defined.contains(opp);
|
||||
if (!defined.contains(opp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
@@ -78,7 +82,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai.getWeakestOpponent());
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
|
||||
@@ -18,26 +18,15 @@
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiCostDecision;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostPayLife;
|
||||
import forge.game.cost.PaymentDecision;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -102,7 +91,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source,sa)) {
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
|
||||
AiCostDecision aiDecisions = new AiCostDecision(ai, sa);
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostDiscard) {
|
||||
@@ -118,7 +107,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -190,7 +179,8 @@ public class DrawAi extends SpellAbilityAi {
|
||||
|
||||
int numHand = ai.getCardsIn(ZoneType.Hand).size();
|
||||
if ("Jace, Vryn's Prodigy".equals(sourceName) && ai.getCardsIn(ZoneType.Graveyard).size() > 3) {
|
||||
return !ai.isCardInPlay("Jace, Telepath Unbound");
|
||||
return CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS,
|
||||
CardPredicates.isType("Jace")).size() <= 0;
|
||||
}
|
||||
if (source.isSpell() && ai.getCardsIn(ZoneType.Hand).contains(source)) {
|
||||
numHand--; // remember to count looter card if it is a spell in hand
|
||||
@@ -221,19 +211,14 @@ public class DrawAi extends SpellAbilityAi {
|
||||
|
||||
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean drawback = sa.getParent() != null;
|
||||
final Game game = ai.getGame();
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
final boolean considerPrimary = logic.equals("ConsiderPrimary");
|
||||
final boolean drawback = (sa.getParent() != null) && !considerPrimary;
|
||||
boolean assumeSafeX = false; // if true, the AI will assume that the X value has been set to a value that is safe to draw
|
||||
|
||||
int computerHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||
final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size();
|
||||
final int computerMaxHandSize = ai.getMaxHandSize();
|
||||
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
|
||||
final SpellAbility gainLife = sa.findSubAbilityByType(ApiType.GainLife);
|
||||
final SpellAbility loseLife = sa.findSubAbilityByType(ApiType.LoseLife);
|
||||
final SpellAbility getPoison = sa.findSubAbilityByType(ApiType.Poison);
|
||||
|
||||
@@ -250,33 +235,47 @@ public class DrawAi extends SpellAbilityAi {
|
||||
boolean xPaid = false;
|
||||
final String num = sa.getParam("NumCards");
|
||||
if (num != null && num.equals("X")) {
|
||||
if (sa.getSVar(num).equals("Count$xPaid")) {
|
||||
if (source.getSVar(num).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
if (drawback && root.getXManaCostPaid() != null) {
|
||||
numCards = root.getXManaCostPaid();
|
||||
if (drawback && !source.getSVar("PayX").equals("")) {
|
||||
numCards = Integer.parseInt(source.getSVar("PayX"));
|
||||
} else {
|
||||
numCards = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
// try not to overdraw
|
||||
int safeDraw = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
|
||||
if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent
|
||||
numCards = Math.min(numCards, safeDraw);
|
||||
numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
xPaid = true;
|
||||
}
|
||||
if (sa.getSVar(num).equals("Count$Converge")) {
|
||||
numCards = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||
}
|
||||
}
|
||||
|
||||
// assuming CostPayLife is the one with X
|
||||
if (num != null && num.equals("ChosenX")) {
|
||||
if (sa.getSVar("X").equals("XChoice")) {
|
||||
// Draw up to max hand size but leave at least 3 in library
|
||||
numCards = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
|
||||
|
||||
if (sa.getPayCosts() != null) {
|
||||
if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
|
||||
// [Necrologia, Pay X Life : Draw X Cards]
|
||||
// Don't draw more than what's "safe" and don't risk a near death experience
|
||||
boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO);
|
||||
while ((ComputerUtil.aiLifeInDanger(ai, aggroAI, numCards) && (numCards > 0))) {
|
||||
// Maybe would be better to check for "serious danger" and take more risk?
|
||||
while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
root.setXManaCostPaid(numCards);
|
||||
assumeSafeX = true;
|
||||
}
|
||||
xPaid = true;
|
||||
} else if (sa.getSVar(num).equals("Count$Converge")) {
|
||||
numCards = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||
sa.setSVar("ChosenX", Integer.toString(numCards));
|
||||
source.setSVar("ChosenX", Integer.toString(numCards));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,30 +334,16 @@ public class DrawAi extends SpellAbilityAi {
|
||||
// for drawing and losing life
|
||||
if (numCards >= oppA.getLife()) {
|
||||
if (xPaid) {
|
||||
root.setXManaCostPaid(oppA.getLife());
|
||||
source.setSVar("PayX", Integer.toString(oppA.getLife()));
|
||||
}
|
||||
sa.getTargets().add(oppA);
|
||||
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) {
|
||||
root.setXManaCostPaid(oppA.getLife());
|
||||
}
|
||||
sa.getTargets().add(oppA);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to make opponent lose to poison
|
||||
// currently only Caress of Phyrexia
|
||||
if (getPoison != null && oppA.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
if (getPoison != null && oppA.canReceiveCounters(CounterType.POISON)) {
|
||||
if (oppA.getPoisonCounters() + numCards > 9) {
|
||||
sa.getTargets().add(oppA);
|
||||
return true;
|
||||
@@ -402,14 +387,14 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (getPoison != null && ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
if (getPoison != null && ai.canReceiveCounters(CounterType.POISON)) {
|
||||
if (numCards + ai.getPoisonCounters() >= 8) {
|
||||
aiTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (xPaid) {
|
||||
root.setXManaCostPaid(numCards);
|
||||
source.setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,10 +402,10 @@ public class DrawAi extends SpellAbilityAi {
|
||||
if (computerHandSize + numCards > computerMaxHandSize && game.getPhaseHandler().isPlayerTurn(ai)) {
|
||||
if (xPaid) {
|
||||
numCards = computerMaxHandSize - computerHandSize;
|
||||
if (sa.getHostCard().isInZone(ZoneType.Hand)) {
|
||||
if (sa.getHostCard().getZone().is(ZoneType.Hand)) {
|
||||
numCards++; // the card will be spent
|
||||
}
|
||||
root.setXManaCostPaid(numCards);
|
||||
source.setSVar("PayX", Integer.toString(numCards));
|
||||
} else {
|
||||
// Don't draw too many cards and then risk discarding
|
||||
// cards at EOT
|
||||
@@ -461,7 +446,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// ally would lose because of poison
|
||||
if (getPoison != null && ally.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
if (getPoison != null && ally.canReceiveCounters(CounterType.POISON)) {
|
||||
if (ally.getPoisonCounters() + numCards > 9) {
|
||||
continue;
|
||||
}
|
||||
@@ -507,8 +492,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
|
||||
if ((computerHandSize + numCards > computerMaxHandSize)
|
||||
&& game.getPhaseHandler().isPlayerTurn(ai)
|
||||
&& !sa.isTrigger()
|
||||
&& !assumeSafeX) {
|
||||
&& !sa.isTrigger()) {
|
||||
// Don't draw too many cards and then risk discarding cards at
|
||||
// EOT
|
||||
if (!drawback) {
|
||||
@@ -522,10 +506,6 @@ public class DrawAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (!mandatory && !willPayCosts(ai, sa, sa.getPayCosts(), sa.getHostCard())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return targetAI(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,25 +6,15 @@ import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.ai.*;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -120,10 +110,89 @@ public class EffectAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("WillCastCreature") && ai.isAI()) {
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
SpellAbility saCreature = aic.predictSpellToCastInMain2(ApiType.PermanentCreature);
|
||||
randomReturn = saCreature != null && ComputerUtilMana.canPayManaCost(saCreature, ai, 0);
|
||||
} 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() != null ? sa.getPayCosts().getTotalMana() : ManaCost.NO_COST;
|
||||
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() != null ? ab.getPayCosts().getTotalMana() : ManaCost.NO_COST;
|
||||
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;
|
||||
} else if (logic.equals("Always")) {
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("Main1")) {
|
||||
@@ -198,7 +267,7 @@ public class EffectAi extends SpellAbilityAi {
|
||||
final SpellAbility saTop = game.getStack().peekAbility();
|
||||
final Card host = saTop.getHostCard();
|
||||
if (saTop.getActivatingPlayer() != ai // from opponent
|
||||
&& host.canDamagePrevented(false) // no prevent damage
|
||||
&& !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) // no prevent damage
|
||||
&& host != null && (host.isInstant() || host.isSorcery())
|
||||
&& !host.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")) { // valid target
|
||||
final ApiType type = saTop.getApi();
|
||||
@@ -214,7 +283,11 @@ public class EffectAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
final SpellAbility topStack = game.getStack().peekAbility();
|
||||
return topStack.getActivatingPlayer().isOpponentOf(ai) && topStack.getApi() == ApiType.GainLife;
|
||||
if (topStack.getActivatingPlayer().isOpponentOf(ai) && topStack.getApi() == ApiType.GainLife) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (logic.equals("Fight")) {
|
||||
return FightAi.canFightAi(ai, sa, 0, 0);
|
||||
} else if (logic.equals("Burn")) {
|
||||
@@ -228,9 +301,11 @@ public class EffectAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
if (logic.contains(":")) {
|
||||
String[] k = logic.split(":");
|
||||
String k[] = logic.split(":");
|
||||
Integer i = Integer.valueOf(k[1]);
|
||||
return ai.getCreaturesInPlay().size() >= i;
|
||||
if (ai.getCreaturesInPlay().size() < i) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (logic.equals("CastFromGraveThisTurn")) {
|
||||
@@ -239,16 +314,6 @@ public class EffectAi extends SpellAbilityAi {
|
||||
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
} else if (logic.equals("Bribe")) {
|
||||
Card host = sa.getHostCard();
|
||||
Combat combat = game.getCombat();
|
||||
if (combat != null && combat.isAttacking(host, ai) && !combat.isBlocked(host)
|
||||
&& game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& !AiCardMemory.isRememberedCard(ai, host, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN)) {
|
||||
AiCardMemory.rememberCard(ai, host, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN); // ideally needs once per combat or something
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else { //no AILogic
|
||||
return false;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user