mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 17:58:01 +00:00
Compare commits
7 Commits
untapRewor
...
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>
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -231,8 +231,3 @@ forge-gui/tools/oracleScript.log
|
|||||||
/release.properties
|
/release.properties
|
||||||
/target
|
/target
|
||||||
/test-output
|
/test-output
|
||||||
.settings
|
|
||||||
.classpath
|
|
||||||
.project
|
|
||||||
.vscode/settings.json
|
|
||||||
.vscode/launch.json
|
|
||||||
|
|||||||
@@ -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>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>1.6.30-SNAPSHOT</version>
|
<version>1.6.20-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-ai</artifactId>
|
<artifactId>forge-ai</artifactId>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
public enum AIOption {
|
public enum AIOption {
|
||||||
USE_SIMULATION
|
USE_SIMULATION;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ public class AiAttackController {
|
|||||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
this.defendingOpponent = choosePreferredDefenderPlayer();
|
||||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||||
this.myList = ai.getCreaturesInPlay();
|
this.myList = ai.getCreaturesInPlay();
|
||||||
this.attackers = new ArrayList<>();
|
this.attackers = new ArrayList<Card>();
|
||||||
for (Card c : myList) {
|
for (Card c : myList) {
|
||||||
if (CombatUtil.canAttack(c, this.defendingOpponent)) {
|
if (CombatUtil.canAttack(c, this.defendingOpponent)) {
|
||||||
attackers.add(c);
|
attackers.add(c);
|
||||||
@@ -95,7 +95,7 @@ public class AiAttackController {
|
|||||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
this.defendingOpponent = choosePreferredDefenderPlayer();
|
||||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||||
this.myList = ai.getCreaturesInPlay();
|
this.myList = ai.getCreaturesInPlay();
|
||||||
this.attackers = new ArrayList<>();
|
this.attackers = new ArrayList<Card>();
|
||||||
if (CombatUtil.canAttack(attacker, this.defendingOpponent)) {
|
if (CombatUtil.canAttack(attacker, this.defendingOpponent)) {
|
||||||
attackers.add(attacker);
|
attackers.add(attacker);
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,8 @@ public class AiAttackController {
|
|||||||
} // overloaded constructor to evaluate single specified attacker
|
} // overloaded constructor to evaluate single specified attacker
|
||||||
|
|
||||||
public static List<Card> getOpponentCreatures(final Player defender) {
|
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>() {
|
Predicate<Card> canAnimate = new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Card c) {
|
public boolean apply(Card c) {
|
||||||
@@ -150,7 +151,7 @@ public class AiAttackController {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public final static List<Card> sortAttackers(final List<Card> in) {
|
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)
|
// Cards with triggers should come first (for Battle Cry)
|
||||||
for (final Card attacker : in) {
|
for (final Card attacker : in) {
|
||||||
@@ -189,49 +190,15 @@ public class AiAttackController {
|
|||||||
if ((attacker.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) {
|
if ((attacker.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) {
|
||||||
return false;
|
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.getOverridingAbility();
|
|
||||||
if (sa == null && t.hasParam("Execute")) {
|
|
||||||
sa = AbilityFactory.getAbility(c, t.getParam("Execute"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa != null && 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"))) {
|
if ("TRUE".equals(attacker.getSVar("HasAttackEffect"))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Player opp = this.defendingOpponent;
|
final Player opp = this.defendingOpponent;
|
||||||
|
if (ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat, true) > 0) {
|
||||||
// Damage opponent if unblocked
|
return true;
|
||||||
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 */) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Poison opponent if unblocked
|
|
||||||
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) {
|
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -246,7 +213,7 @@ public class AiAttackController {
|
|||||||
final CardCollectionView controlledByCompy = ai.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES);
|
final CardCollectionView controlledByCompy = ai.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES);
|
||||||
for (final Card c : controlledByCompy) {
|
for (final Card c : controlledByCompy) {
|
||||||
for (final Trigger trigger : c.getTriggers()) {
|
for (final Trigger trigger : c.getTriggers()) {
|
||||||
if (ComputerUtilCombat.combatTriggerWillTrigger(attacker, null, trigger, combat, this.attackers)) {
|
if (ComputerUtilCombat.combatTriggerWillTrigger(attacker, null, trigger, combat)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,7 +222,7 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final static List<Card> getPossibleBlockers(final List<Card> blockers, final List<Card> attackers) {
|
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>() {
|
possibleBlockers = CardLists.filter(possibleBlockers, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
@@ -266,7 +233,7 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final static boolean canBlockAnAttacker(final Card c, final List<Card> attackers, final boolean nextTurn) {
|
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()) {
|
if (!c.isCreature()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -279,7 +246,7 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final static Card getCardCanBlockAnAttacker(final Card c, final List<Card> attackers, final boolean nextTurn) {
|
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()) {
|
if (!c.isCreature()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -294,9 +261,9 @@ public class AiAttackController {
|
|||||||
// this checks to make sure that the computer player doesn't lose when the human player attacks
|
// this checks to make sure that the computer player doesn't lose when the human player attacks
|
||||||
// this method is used by getAttackers()
|
// this method is used by getAttackers()
|
||||||
public final List<Card> notNeededAsBlockers(final Player ai, final List<Card> attackers) {
|
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;
|
int fixedBlockers = 0;
|
||||||
final List<Card> vigilantes = new ArrayList<>();
|
final List<Card> vigilantes = new ArrayList<Card>();
|
||||||
//check for time walks
|
//check for time walks
|
||||||
if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) {
|
if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) {
|
||||||
return attackers;
|
return attackers;
|
||||||
@@ -335,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>() {
|
opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
@@ -547,7 +514,8 @@ public class AiAttackController {
|
|||||||
remainingAttackers.removeAll(unblockedAttackers);
|
remainingAttackers.removeAll(unblockedAttackers);
|
||||||
|
|
||||||
for (Card blocker : this.blockers) {
|
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) {
|
for (Card attacker : this.attackers) {
|
||||||
if (CombatUtil.canBlock(attacker, blocker)) {
|
if (CombatUtil.canBlock(attacker, blocker)) {
|
||||||
remainingAttackers.remove(attacker);
|
remainingAttackers.remove(attacker);
|
||||||
@@ -563,19 +531,14 @@ public class AiAttackController {
|
|||||||
if (remainingAttackers.isEmpty() || maxBlockersAfterCrew == 0) {
|
if (remainingAttackers.isEmpty() || maxBlockersAfterCrew == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (blocker.hasKeyword("CARDNAME can block an additional creature each combat.")) {
|
||||||
int numExtraBlocks = blocker.canBlockAdditional();
|
blockedAttackers.add(remainingAttackers.get(0));
|
||||||
if (numExtraBlocks > 0) {
|
remainingAttackers.remove(0);
|
||||||
while (numExtraBlocks-- > 0 && !remainingAttackers.isEmpty()) {
|
maxBlockersAfterCrew--;
|
||||||
blockedAttackers.add(remainingAttackers.get(0));
|
if (remainingAttackers.isEmpty()) {
|
||||||
remainingAttackers.remove(0);
|
break;
|
||||||
maxBlockersAfterCrew--;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remainingAttackers.isEmpty()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
blockedAttackers.add(remainingAttackers.get(0));
|
blockedAttackers.add(remainingAttackers.get(0));
|
||||||
remainingAttackers.remove(0);
|
remainingAttackers.remove(0);
|
||||||
maxBlockersAfterCrew--;
|
maxBlockersAfterCrew--;
|
||||||
@@ -684,7 +647,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
// Determine who will be attacked
|
// Determine who will be attacked
|
||||||
GameEntity defender = this.chooseDefender(combat, bAssault);
|
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?
|
// TODO probably use AttackConstraints instead of only GlobalAttackRestrictions?
|
||||||
GlobalAttackRestrictions restrict = GlobalAttackRestrictions.getGlobalRestrictions(ai, combat.getDefenders());
|
GlobalAttackRestrictions restrict = GlobalAttackRestrictions.getGlobalRestrictions(ai, combat.getDefenders());
|
||||||
@@ -824,12 +787,12 @@ public class AiAttackController {
|
|||||||
int humanForcesForAttritionalAttack = 0;
|
int humanForcesForAttritionalAttack = 0;
|
||||||
|
|
||||||
// examine the potential forces
|
// examine the potential forces
|
||||||
final List<Card> nextTurnAttackers = new ArrayList<>();
|
final List<Card> nextTurnAttackers = new ArrayList<Card>();
|
||||||
int candidateCounterAttackDamage = 0;
|
int candidateCounterAttackDamage = 0;
|
||||||
|
|
||||||
final Player opp = this.defendingOpponent;
|
final Player opp = this.defendingOpponent;
|
||||||
// get the potential damage and strength of the AI forces
|
// 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;
|
int candidateUnblockedDamage = 0;
|
||||||
for (final Card pCard : this.myList) {
|
for (final Card pCard : this.myList) {
|
||||||
// if the creature can attack then it's a potential attacker this
|
// if the creature can attack then it's a potential attacker this
|
||||||
@@ -888,7 +851,7 @@ public class AiAttackController {
|
|||||||
final int outNumber = computerForces - humanForces;
|
final int outNumber = computerForces - humanForces;
|
||||||
|
|
||||||
for (Card blocker : this.blockers) {
|
for (Card blocker : this.blockers) {
|
||||||
if (blocker.canBlockAny()) {
|
if (blocker.hasKeyword("CARDNAME can block any number of creatures.")) {
|
||||||
aiLifeToPlayerDamageRatio--;
|
aiLifeToPlayerDamageRatio--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -911,7 +874,7 @@ public class AiAttackController {
|
|||||||
// get player life total
|
// get player life total
|
||||||
int humanLife = opp.getLife();
|
int humanLife = opp.getLife();
|
||||||
// get the list of attackers up to the first blocked one
|
// 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++) {
|
for (int x = 0; x < (this.attackers.size() - humanForces); x++) {
|
||||||
attritionalAttackers.add(this.attackers.get(x));
|
attritionalAttackers.add(this.attackers.get(x));
|
||||||
}
|
}
|
||||||
@@ -1024,7 +987,7 @@ public class AiAttackController {
|
|||||||
} // stay at home to block
|
} // stay at home to block
|
||||||
|
|
||||||
if ( LOG_AI_ATTACKS )
|
if ( LOG_AI_ATTACKS )
|
||||||
System.out.println(this.aiAggression + " = ai aggression");
|
System.out.println(String.valueOf(this.aiAggression) + " = ai aggression");
|
||||||
|
|
||||||
// ****************
|
// ****************
|
||||||
// Evaluation the end
|
// Evaluation the end
|
||||||
@@ -1156,21 +1119,8 @@ public class AiAttackController {
|
|||||||
// is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...)
|
// is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...)
|
||||||
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
|
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
|
||||||
|| "Blocked".equals(attacker.getSVar("HasAttackEffect"));
|
|| "Blocked".equals(attacker.getSVar("HasAttackEffect"));
|
||||||
|
|
||||||
// contains only the defender's blockers that can actually block the attacker
|
|
||||||
CardCollection validBlockers = CardLists.filter(defenders, new Predicate<Card>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(Card defender) {
|
|
||||||
return CombatUtil.canBlock(attacker, defender);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// 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
|
// total power of the defending creatures, used in predicting whether a gang block can kill the attacker
|
||||||
int defPower = CardLists.getTotalPower(validBlockers, true);
|
int defPower = CardLists.getTotalPower(defenders, true);
|
||||||
|
|
||||||
if (!hasCombatEffect) {
|
if (!hasCombatEffect) {
|
||||||
for (KeywordInterface inst : attacker.getKeywords()) {
|
for (KeywordInterface inst : attacker.getKeywords()) {
|
||||||
@@ -1187,9 +1137,10 @@ public class AiAttackController {
|
|||||||
// number of factors about the attacking
|
// number of factors about the attacking
|
||||||
// context that will be relevant to the attackers decision according to
|
// context that will be relevant to the attackers decision according to
|
||||||
// the selected strategy
|
// 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 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;
|
numberOfPossibleBlockers += 1;
|
||||||
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
|
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
|
||||||
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterType.P1P1) == 0)) {
|
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterType.P1P1) == 0)) {
|
||||||
@@ -1227,7 +1178,7 @@ public class AiAttackController {
|
|||||||
// - our creature will die for sure (chump attack)
|
// - our creature will die for sure (chump attack)
|
||||||
// - our attack will not do anything special (no attack/combat effect to proc)
|
// - 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
|
// - 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;
|
canKillAllDangerous = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1276,9 +1227,8 @@ public class AiAttackController {
|
|||||||
if (LOG_AI_ATTACKS)
|
if (LOG_AI_ATTACKS)
|
||||||
System.out.println(attacker.getName() + " = all out attacking");
|
System.out.println(attacker.getName() + " = all out attacking");
|
||||||
return true;
|
return true;
|
||||||
case 4: // expecting to at least trade with something, or can attack "for free", expecting no counterattack
|
case 4: // expecting to at least trade with something
|
||||||
if (canKillAll || (dangerousBlockersPresent && canKillAllDangerous && !canBeKilledByOne) || !canBeBlocked
|
if (canKillAll || (canKillAllDangerous && !canBeKilledByOne) || !canBeBlocked) {
|
||||||
|| (defPower == 0 && !ComputerUtilCombat.lifeInDanger(ai, combat))) {
|
|
||||||
if (LOG_AI_ATTACKS)
|
if (LOG_AI_ATTACKS)
|
||||||
System.out.println(attacker.getName() + " = attacking expecting to at least trade with something");
|
System.out.println(attacker.getName() + " = attacking expecting to at least trade with something");
|
||||||
return true;
|
return true;
|
||||||
@@ -1286,7 +1236,7 @@ public class AiAttackController {
|
|||||||
break;
|
break;
|
||||||
case 3: // expecting to at least kill a creature of equal value or not be blocked
|
case 3: // expecting to at least kill a creature of equal value or not be blocked
|
||||||
if ((canKillAll && isWorthLessThanAllKillers)
|
if ((canKillAll && isWorthLessThanAllKillers)
|
||||||
|| (((dangerousBlockersPresent && canKillAllDangerous) || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne)
|
|| ((canKillAllDangerous || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne)
|
||||||
|| !canBeBlocked) {
|
|| !canBeBlocked) {
|
||||||
if (LOG_AI_ATTACKS)
|
if (LOG_AI_ATTACKS)
|
||||||
System.out.println(attacker.getName() + " = attacking expecting to kill creature or cause damage, or is unblockable");
|
System.out.println(attacker.getName() + " = attacking expecting to kill creature or cause damage, or is unblockable");
|
||||||
@@ -1295,7 +1245,7 @@ public class AiAttackController {
|
|||||||
break;
|
break;
|
||||||
case 2: // attack expecting to attract a group block or destroying a single blocker and surviving
|
case 2: // attack expecting to attract a group block or destroying a single blocker and surviving
|
||||||
if (!canBeBlocked || ((canKillAll || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne &&
|
if (!canBeBlocked || ((canKillAll || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne &&
|
||||||
((dangerousBlockersPresent && canKillAllDangerous) || !canBeKilled))) {
|
(canKillAllDangerous || !canBeKilled))) {
|
||||||
if (LOG_AI_ATTACKS)
|
if (LOG_AI_ATTACKS)
|
||||||
System.out.println(attacker.getName() + " = attacking expecting to survive or attract group block");
|
System.out.println(attacker.getName() + " = attacking expecting to survive or attract group block");
|
||||||
return true;
|
return true;
|
||||||
@@ -1460,7 +1410,7 @@ public class AiAttackController {
|
|||||||
if (artifact != null) {
|
if (artifact != null) {
|
||||||
return artifact;
|
return artifact;
|
||||||
}
|
}
|
||||||
return null;//should never get here
|
return null; //should never get here
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doLightmineFieldAttackLogic(List<Card> attackersLeft, int numForcedAttackers, boolean playAggro) {
|
private void doLightmineFieldAttackLogic(List<Card> attackersLeft, int numForcedAttackers, boolean playAggro) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.google.common.base.Predicates;
|
|||||||
import forge.card.CardStateName;
|
import forge.card.CardStateName;
|
||||||
import forge.game.CardTraitBase;
|
import forge.game.CardTraitBase;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
|
import forge.game.GlobalRuleChange;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
@@ -147,7 +148,9 @@ public class AiBlockController {
|
|||||||
final CardCollection attackers = combat.getAttackersOf(defender);
|
final CardCollection attackers = combat.getAttackersOf(defender);
|
||||||
// Begin with the attackers that pose the biggest threat
|
// Begin with the attackers that pose the biggest threat
|
||||||
CardLists.sortByPowerDesc(attackers);
|
CardLists.sortByPowerDesc(attackers);
|
||||||
sortedAttackers.addAll(attackers);
|
for (final Card c : attackers) {
|
||||||
|
sortedAttackers.add(c);
|
||||||
|
}
|
||||||
} else if (defender instanceof Player && defender.equals(ai)) {
|
} else if (defender instanceof Player && defender.equals(ai)) {
|
||||||
firstAttacker = combat.getAttackersOf(defender);
|
firstAttacker = combat.getAttackersOf(defender);
|
||||||
}
|
}
|
||||||
@@ -160,7 +163,9 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// add creatures attacking the Player to the back of the list
|
// add creatures attacking the Player to the back of the list
|
||||||
sortedAttackers.addAll(firstAttacker);
|
for (final Card c : firstAttacker) {
|
||||||
|
sortedAttackers.add(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return sortedAttackers;
|
return sortedAttackers;
|
||||||
}
|
}
|
||||||
@@ -476,7 +481,8 @@ public class AiBlockController {
|
|||||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, secondBlocker, combat, false);
|
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, secondBlocker, combat, false);
|
||||||
|
|
||||||
List<Card> usableBlockersAsThird = new ArrayList<>(usableBlockers);
|
List<Card> usableBlockersAsThird = new ArrayList<>();
|
||||||
|
usableBlockersAsThird.addAll(usableBlockers);
|
||||||
usableBlockersAsThird.remove(secondBlocker);
|
usableBlockersAsThird.remove(secondBlocker);
|
||||||
|
|
||||||
// loop over the remaining blockers in search of a good third blocker candidate
|
// loop over the remaining blockers in search of a good third blocker candidate
|
||||||
@@ -767,12 +773,14 @@ public class AiBlockController {
|
|||||||
blockers.removeAll(combat.getBlockers(attacker));
|
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
|
// Don't add any blockers that won't kill the attacker because the damage would be prevented by a static effect
|
||||||
blockers = CardLists.filter(blockers, new Predicate<Card>() {
|
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) {
|
||||||
@Override
|
blockers = CardLists.filter(blockers, new Predicate<Card>() {
|
||||||
public boolean apply(Card blocker) {
|
@Override
|
||||||
return !ComputerUtilCombat.isCombatDamagePrevented(blocker, attacker, blocker.getNetCombatDamage());
|
public boolean apply(Card blocker) {
|
||||||
}
|
return !ComputerUtilCombat.isCombatDamagePrevented(blocker, attacker, blocker.getNetCombatDamage());
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Try to use safe blockers first
|
// Try to use safe blockers first
|
||||||
if (blockers.size() > 0) {
|
if (blockers.size() > 0) {
|
||||||
@@ -851,7 +859,7 @@ public class AiBlockController {
|
|||||||
damageToPW += ComputerUtilCombat.predictDamageTo((Card) def, pwatkr.getNetCombatDamage(), pwatkr, true);
|
damageToPW += ComputerUtilCombat.predictDamageTo((Card) def, pwatkr.getNetCombatDamage(), pwatkr, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((!onlyIfLethal && damageToPW > 0) || damageToPW >= def.getCounters(CounterType.LOYALTY)) {
|
if ((!onlyIfLethal && damageToPW > 0) || damageToPW >= ((Card) def).getCounters(CounterType.LOYALTY)) {
|
||||||
threatenedPWs.add((Card) def);
|
threatenedPWs.add((Card) def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -871,7 +879,7 @@ public class AiBlockController {
|
|||||||
if (!chumpPWDefenders.isEmpty()) {
|
if (!chumpPWDefenders.isEmpty()) {
|
||||||
for (final Card attacker : attackers) {
|
for (final Card attacker : attackers) {
|
||||||
GameEntity def = combat.getDefenderByAttacker(attacker);
|
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)) {
|
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
|
||||||
// don't bother trying to chump a trampling creature
|
// don't bother trying to chump a trampling creature
|
||||||
continue;
|
continue;
|
||||||
@@ -906,7 +914,7 @@ public class AiBlockController {
|
|||||||
pwDefenders.addAll(combat.getBlockers(pwAtk));
|
pwDefenders.addAll(combat.getBlockers(pwAtk));
|
||||||
} else {
|
} else {
|
||||||
isFullyBlocked = false;
|
isFullyBlocked = false;
|
||||||
damageToPW += ComputerUtilCombat.predictDamageTo(pw, pwAtk.getNetCombatDamage(), pwAtk, true);
|
damageToPW += ComputerUtilCombat.predictDamageTo((Card) pw, pwAtk.getNetCombatDamage(), pwAtk, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isFullyBlocked && damageToPW >= pw.getCounters(CounterType.LOYALTY)) {
|
if (!isFullyBlocked && damageToPW >= pw.getCounters(CounterType.LOYALTY)) {
|
||||||
@@ -1321,9 +1329,13 @@ public class AiBlockController {
|
|||||||
&& ((Card) combat.getDefenderByAttacker(attacker)).isPlaneswalker();
|
&& ((Card) combat.getDefenderByAttacker(attacker)).isPlaneswalker();
|
||||||
boolean wantToTradeDownToSavePW = chanceToTradeDownToSaveWalker > 0;
|
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
|
&& powerParityOrHigher
|
||||||
&& (creatureParityOrAllowedDiff || wantToTradeWithCreatInHand)
|
&& (creatureParityOrAllowedDiff || wantToTradeWithCreatInHand)
|
||||||
&& (MyRandom.percentTrue(chance) || wantToSavePlaneswalker);
|
&& (MyRandom.percentTrue(chance) || wantToSavePlaneswalker)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ public class AiCardMemory {
|
|||||||
|
|
||||||
Set<Card> memorySet = getMemorySet(set);
|
Set<Card> memorySet = getMemorySet(set);
|
||||||
|
|
||||||
return memorySet != null && memorySet.contains(c);
|
return memorySet == null ? false : memorySet.contains(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,15 +150,12 @@ public class AiCardMemory {
|
|||||||
*/
|
*/
|
||||||
public boolean isRememberedCardByName(String cardName, MemorySet set) {
|
public boolean isRememberedCardByName(String cardName, MemorySet set) {
|
||||||
Set<Card> memorySet = getMemorySet(set);
|
Set<Card> memorySet = getMemorySet(set);
|
||||||
|
Iterator<Card> it = memorySet.iterator();
|
||||||
|
|
||||||
if (memorySet != null) {
|
while (it.hasNext()) {
|
||||||
Iterator<Card> it = memorySet.iterator();
|
Card c = it.next();
|
||||||
|
if (c.getName().equals(cardName)) {
|
||||||
while (it.hasNext()) {
|
return true;
|
||||||
Card c = it.next();
|
|
||||||
if (c.getName().equals(cardName)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,15 +174,12 @@ public class AiCardMemory {
|
|||||||
*/
|
*/
|
||||||
public boolean isRememberedCardByName(String cardName, MemorySet set, Player owner) {
|
public boolean isRememberedCardByName(String cardName, MemorySet set, Player owner) {
|
||||||
Set<Card> memorySet = getMemorySet(set);
|
Set<Card> memorySet = getMemorySet(set);
|
||||||
|
Iterator<Card> it = memorySet.iterator();
|
||||||
|
|
||||||
if (memorySet != null) {
|
while (it.hasNext()) {
|
||||||
Iterator<Card> it = memorySet.iterator();
|
Card c = it.next();
|
||||||
|
if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
|
||||||
while (it.hasNext()) {
|
return true;
|
||||||
Card c = it.next();
|
|
||||||
if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,12 +197,7 @@ public class AiCardMemory {
|
|||||||
if (c == null)
|
if (c == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Set<Card> memorySet = getMemorySet(set);
|
getMemorySet(set).add(c);
|
||||||
|
|
||||||
if (memorySet != null) {
|
|
||||||
memorySet.add(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,12 +216,7 @@ public class AiCardMemory {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<Card> memorySet = getMemorySet(set);
|
getMemorySet(set).remove(c);
|
||||||
|
|
||||||
if (memorySet != null) {
|
|
||||||
memorySet.remove(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,15 +229,12 @@ public class AiCardMemory {
|
|||||||
*/
|
*/
|
||||||
public boolean forgetAnyCardWithName(String cardName, MemorySet set) {
|
public boolean forgetAnyCardWithName(String cardName, MemorySet set) {
|
||||||
Set<Card> memorySet = getMemorySet(set);
|
Set<Card> memorySet = getMemorySet(set);
|
||||||
|
Iterator<Card> it = memorySet.iterator();
|
||||||
|
|
||||||
if (memorySet != null) {
|
while (it.hasNext()) {
|
||||||
Iterator<Card> it = memorySet.iterator();
|
Card c = it.next();
|
||||||
|
if (c.getName().equals(cardName)) {
|
||||||
while (it.hasNext()) {
|
return forgetCard(c, set);
|
||||||
Card c = it.next();
|
|
||||||
if (c.getName().equals(cardName)) {
|
|
||||||
return forgetCard(c, set);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,18 +251,15 @@ public class AiCardMemory {
|
|||||||
*/
|
*/
|
||||||
public boolean forgetAnyCardWithName(String cardName, MemorySet set, Player owner) {
|
public boolean forgetAnyCardWithName(String cardName, MemorySet set, Player owner) {
|
||||||
Set<Card> memorySet = getMemorySet(set);
|
Set<Card> memorySet = getMemorySet(set);
|
||||||
|
Iterator<Card> it = memorySet.iterator();
|
||||||
|
|
||||||
if (memorySet != null) {
|
while (it.hasNext()) {
|
||||||
Iterator<Card> it = memorySet.iterator();
|
Card c = it.next();
|
||||||
|
if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
|
||||||
while (it.hasNext()) {
|
return forgetCard(c, set);
|
||||||
Card c = it.next();
|
|
||||||
if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
|
|
||||||
return forgetCard(c, set);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,16 +269,14 @@ public class AiCardMemory {
|
|||||||
* @return true, if the given memory set contains no remembered cards.
|
* @return true, if the given memory set contains no remembered cards.
|
||||||
*/
|
*/
|
||||||
public boolean isMemorySetEmpty(MemorySet set) {
|
public boolean isMemorySetEmpty(MemorySet set) {
|
||||||
return set == null || getMemorySet(set).isEmpty();
|
return getMemorySet(set).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the given memory set.
|
* Clears the given memory set.
|
||||||
*/
|
*/
|
||||||
public void clearMemorySet(MemorySet set) {
|
public void clearMemorySet(MemorySet set) {
|
||||||
if (set != null) {
|
getMemorySet(set).clear();
|
||||||
getMemorySet(set).clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,10 +18,12 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import com.esotericsoftware.minlog.Log;
|
import com.esotericsoftware.minlog.Log;
|
||||||
|
import com.google.common.base.Function;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import forge.ai.ability.ChangeZoneAi;
|
import forge.ai.ability.ChangeZoneAi;
|
||||||
import forge.ai.ability.ExploreAi;
|
import forge.ai.ability.ExploreAi;
|
||||||
import forge.ai.simulation.SpellAbilityPicker;
|
import forge.ai.simulation.SpellAbilityPicker;
|
||||||
@@ -48,7 +50,6 @@ import forge.game.player.Player;
|
|||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.replacement.ReplaceMoved;
|
import forge.game.replacement.ReplaceMoved;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementType;
|
|
||||||
import forge.game.spellability.*;
|
import forge.game.spellability.*;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
@@ -59,7 +60,6 @@ import forge.item.PaperCard;
|
|||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.Expressions;
|
import forge.util.Expressions;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.ComparatorUtil;
|
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
import io.sentry.Sentry;
|
import io.sentry.Sentry;
|
||||||
import io.sentry.event.BreadcrumbBuilder;
|
import io.sentry.event.BreadcrumbBuilder;
|
||||||
@@ -610,15 +610,7 @@ public class AiController {
|
|||||||
ComputerUtilAbility.getAvailableCards(game, player);
|
ComputerUtilAbility.getAvailableCards(game, player);
|
||||||
|
|
||||||
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player);
|
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player);
|
||||||
|
Collections.sort(all, saComparator); // put best spells first
|
||||||
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)) {
|
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
|
||||||
ApiType saApi = sa.getApi();
|
ApiType saApi = sa.getApi();
|
||||||
@@ -665,10 +657,10 @@ public class AiController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
AiCardMemory.MemorySet memSet = null;
|
AiCardMemory.MemorySet memSet;
|
||||||
if (phaseType == null && forNextSpell) {
|
if (phaseType == null && forNextSpell) {
|
||||||
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL;
|
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL;
|
||||||
} else if (phaseType != null) {
|
} else {
|
||||||
switch (phaseType) {
|
switch (phaseType) {
|
||||||
case MAIN2:
|
case MAIN2:
|
||||||
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2;
|
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2;
|
||||||
@@ -1020,7 +1012,7 @@ public class AiController {
|
|||||||
p += 9;
|
p += 9;
|
||||||
}
|
}
|
||||||
// sort planeswalker abilities with most costly first
|
// sort planeswalker abilities with most costly first
|
||||||
if (sa.isPwAbility()) {
|
if (sa.getRestrictions().isPwAbility()) {
|
||||||
final CostPart cost = sa.getPayCosts().getCostParts().get(0);
|
final CostPart cost = sa.getPayCosts().getCostParts().get(0);
|
||||||
if (cost instanceof CostRemoveCounter) {
|
if (cost instanceof CostRemoveCounter) {
|
||||||
p += cost.convertAmount() == null ? 1 : cost.convertAmount();
|
p += cost.convertAmount() == null ? 1 : cost.convertAmount();
|
||||||
@@ -1052,10 +1044,9 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa, final CardCollectionView exclude) {
|
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));
|
CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
||||||
hand.removeAll(exclude);
|
hand.removeAll(exclude);
|
||||||
if ((uTypes != null) && (sa != null) && !noFiltering) {
|
if ((uTypes != null) && (sa != null)) {
|
||||||
hand = CardLists.getValidCards(hand, uTypes, sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
hand = CardLists.getValidCards(hand, uTypes, sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||||
}
|
}
|
||||||
return getCardsToDiscard(numDiscard, numDiscard, hand, sa);
|
return getCardsToDiscard(numDiscard, numDiscard, hand, sa);
|
||||||
@@ -1075,14 +1066,6 @@ public class AiController {
|
|||||||
min = 1;
|
min = 1;
|
||||||
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) {
|
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) {
|
||||||
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
||||||
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
|
||||||
final int CMC = Integer.parseInt(sourceCard.getSVar("PayX"));
|
|
||||||
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(CMC));
|
|
||||||
if (discards.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return new CardCollection(ComputerUtilCard.getWorstAI(discards));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("AnyNumber")) {
|
if (sa.hasParam("AnyNumber")) {
|
||||||
@@ -1228,7 +1211,7 @@ public class AiController {
|
|||||||
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
ApiType api = sa.getApi();
|
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) {
|
if (api == null) {
|
||||||
String exMsg = String.format("AI confirmAction does not know what to decide about %s mode (api is null).",
|
String exMsg = String.format("AI confirmAction does not know what to decide about %s mode (api is null).",
|
||||||
mode);
|
mode);
|
||||||
@@ -1581,15 +1564,8 @@ public class AiController {
|
|||||||
if (all == null || all.isEmpty())
|
if (all == null || all.isEmpty())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
try {
|
Collections.sort(all, saComparator); // put best spells first
|
||||||
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)) {
|
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
|
||||||
// Don't add Counterspells to the "normal" playcard lookups
|
// Don't add Counterspells to the "normal" playcard lookups
|
||||||
if (skipCounter && sa.getApi() == ApiType.Counter) {
|
if (skipCounter && sa.getApi() == ApiType.Counter) {
|
||||||
@@ -1662,6 +1638,7 @@ public class AiController {
|
|||||||
// For non-converted triggers (such as Cumulative Upkeep) that don't have costs or targets to worry about
|
// For non-converted triggers (such as Cumulative Upkeep) that don't have costs or targets to worry about
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1677,14 +1654,14 @@ public class AiController {
|
|||||||
hostCard = game.getCardState(hostCard);
|
hostCard = game.getCardState(hostCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (effect.hasParam("AICheckSVar")) {
|
if (effect.getMapParams().containsKey("AICheckSVar")) {
|
||||||
System.out.println("aiShouldRun?" + sa);
|
System.out.println("aiShouldRun?" + sa);
|
||||||
final String svarToCheck = effect.getParam("AICheckSVar");
|
final String svarToCheck = effect.getMapParams().get("AICheckSVar");
|
||||||
String comparator = "GE";
|
String comparator = "GE";
|
||||||
int compareTo = 1;
|
int compareTo = 1;
|
||||||
|
|
||||||
if (effect.hasParam("AISVarCompare")) {
|
if (effect.getMapParams().containsKey("AISVarCompare")) {
|
||||||
final String fullCmp = effect.getParam("AISVarCompare");
|
final String fullCmp = effect.getMapParams().get("AISVarCompare");
|
||||||
comparator = fullCmp.substring(0, 2);
|
comparator = fullCmp.substring(0, 2);
|
||||||
final String strCmpTo = fullCmp.substring(2);
|
final String strCmpTo = fullCmp.substring(2);
|
||||||
try {
|
try {
|
||||||
@@ -1706,11 +1683,16 @@ public class AiController {
|
|||||||
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
||||||
}
|
}
|
||||||
System.out.println("aiShouldRun?" + left + comparator + compareTo);
|
System.out.println("aiShouldRun?" + left + comparator + compareTo);
|
||||||
return Expressions.compare(left, comparator, compareTo);
|
if (Expressions.compare(left, comparator, compareTo)) {
|
||||||
} else if (effect.hasParam("AICheckDredge")) {
|
return true;
|
||||||
|
}
|
||||||
|
} else if (effect.getMapParams().containsKey("AICheckDredge")) {
|
||||||
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
|
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) {
|
public List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand) {
|
||||||
@@ -1789,7 +1771,7 @@ public class AiController {
|
|||||||
+ MyRandom.getRandom().nextInt(3);
|
+ MyRandom.getRandom().nextInt(3);
|
||||||
return Math.max(remaining, min) / 2;
|
return Math.max(remaining, min) / 2;
|
||||||
} else if ("LowestLoseLife".equals(logic)) {
|
} else if ("LowestLoseLife".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, ComputerUtil.getOpponentFor(player).getLife())) + 1;
|
||||||
} else if ("HighestGetCounter".equals(logic)) {
|
} else if ("HighestGetCounter".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(3);
|
return MyRandom.getRandom().nextInt(3);
|
||||||
} else if (source.hasSVar("EnergyToPay")) {
|
} else if (source.hasSVar("EnergyToPay")) {
|
||||||
@@ -1802,6 +1784,75 @@ public class AiController {
|
|||||||
throw new UnsupportedOperationException("AI is not supposed to reach this code at the moment");
|
throw new UnsupportedOperationException("AI is not supposed to reach this code at the moment");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
public CardCollection chooseCardsForEffect(CardCollectionView pool, SpellAbility sa, int min, int max, boolean isOptional) {
|
||||||
if (sa == null || sa.getApi() == null) {
|
if (sa == null || sa.getApi() == null) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
@@ -1846,7 +1897,7 @@ public class AiController {
|
|||||||
// Special case for Bow to My Command which simulates a complex tap cost via ChooseCard
|
// 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
|
// TODO: consider enhancing support for tapXType<Any/...> in UnlessCost to get rid of this hack
|
||||||
if ("BowToMyCommand".equals(sa.getParam("AILogic"))) {
|
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
|
// Make sure that other opponents do not tap for an already abandoned scheme
|
||||||
result.clear();
|
result.clear();
|
||||||
break;
|
break;
|
||||||
@@ -2089,47 +2140,50 @@ public class AiController {
|
|||||||
// AI-specific restrictions specified as activation parameters in spell abilities
|
// AI-specific restrictions specified as activation parameters in spell abilities
|
||||||
|
|
||||||
if (sa.hasParam("AILifeThreshold")) {
|
if (sa.hasParam("AILifeThreshold")) {
|
||||||
return player.getLife() > Integer.parseInt(sa.getParam("AILifeThreshold"));
|
if (player.getLife() <= Integer.parseInt(sa.getParam("AILifeThreshold"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReplacementEffect chooseSingleReplacementEffect(List<ReplacementEffect> list) {
|
public ReplacementEffect chooseSingleReplacementEffect(List<ReplacementEffect> list,
|
||||||
|
Map<String, Object> runParams) {
|
||||||
// no need to choose anything
|
// no need to choose anything
|
||||||
if (list.size() <= 1) {
|
if (list.size() <= 1) {
|
||||||
return Iterables.getFirst(list, null);
|
return Iterables.getFirst(list, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplacementType mode = Iterables.getFirst(list, null).getMode();
|
if (runParams.containsKey("Event")) {
|
||||||
|
// replace lifegain effects
|
||||||
|
if ("GainLife".equals(runParams.get("Event"))) {
|
||||||
|
List<ReplacementEffect> noGain = filterListByAiLogic(list, "NoLife");
|
||||||
|
List<ReplacementEffect> loseLife = filterListByAiLogic(list, "LoseLife");
|
||||||
|
List<ReplacementEffect> doubleLife = filterListByAiLogic(list, "DoubleLife");
|
||||||
|
List<ReplacementEffect> lichDraw = filterListByAiLogic(list, "LichDraw");
|
||||||
|
|
||||||
// replace lifegain effects
|
if (!noGain.isEmpty()) {
|
||||||
if (mode.equals(ReplacementType.GainLife)) {
|
// no lifegain is better than lose life
|
||||||
List<ReplacementEffect> noGain = filterListByAiLogic(list, "NoLife");
|
return Iterables.getFirst(noGain, null);
|
||||||
List<ReplacementEffect> loseLife = filterListByAiLogic(list, "LoseLife");
|
} else if (!loseLife.isEmpty()) {
|
||||||
List<ReplacementEffect> doubleLife = filterListByAiLogic(list, "DoubleLife");
|
// lose life before double life to prevent lose double
|
||||||
List<ReplacementEffect> lichDraw = filterListByAiLogic(list, "LichDraw");
|
return Iterables.getFirst(loseLife, null);
|
||||||
|
} else if (!lichDraw.isEmpty()) {
|
||||||
|
// lich draw before double life to prevent to draw to much
|
||||||
|
return Iterables.getFirst(lichDraw, null);
|
||||||
|
} else if (!doubleLife.isEmpty()) {
|
||||||
|
// other than that, do double life
|
||||||
|
return Iterables.getFirst(doubleLife, null);
|
||||||
|
}
|
||||||
|
} else if ("DamageDone".equals(runParams.get("Event"))) {
|
||||||
|
List<ReplacementEffect> prevention = filterList(list, CardTraitPredicates.hasParam("Prevention"));
|
||||||
|
|
||||||
if (!noGain.isEmpty()) {
|
// TODO when Protection is done as ReplacementEffect do them
|
||||||
// no lifegain is better than lose life
|
// before normal prevention
|
||||||
return Iterables.getFirst(noGain, null);
|
if (!prevention.isEmpty()) {
|
||||||
} else if (!loseLife.isEmpty()) {
|
return Iterables.getFirst(prevention, null);
|
||||||
// lose life before double life to prevent lose double
|
}
|
||||||
return Iterables.getFirst(loseLife, null);
|
|
||||||
} else if (!lichDraw.isEmpty()) {
|
|
||||||
// lich draw before double life to prevent to draw to much
|
|
||||||
return Iterables.getFirst(lichDraw, null);
|
|
||||||
} else if (!doubleLife.isEmpty()) {
|
|
||||||
// other than that, do double life
|
|
||||||
return Iterables.getFirst(doubleLife, null);
|
|
||||||
}
|
|
||||||
} else if (mode.equals(ReplacementType.DamageDone)) {
|
|
||||||
List<ReplacementEffect> prevention = filterList(list, CardTraitPredicates.hasParam("Prevention"));
|
|
||||||
|
|
||||||
// TODO when Protection is done as ReplacementEffect do them
|
|
||||||
// before normal prevention
|
|
||||||
if (!prevention.isEmpty()) {
|
|
||||||
return Iterables.getFirst(prevention, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
@Override
|
@Override
|
||||||
public PaymentDecision visit(CostChooseCreatureType cost) {
|
public PaymentDecision visit(CostChooseCreatureType cost) {
|
||||||
String choice = player.getController().chooseSomeType("Creature", ability, CardType.getAllCreatureTypes(),
|
String choice = player.getController().chooseSomeType("Creature", ability, CardType.getAllCreatureTypes(),
|
||||||
Lists.newArrayList());
|
Lists.<String>newArrayList());
|
||||||
return PaymentDecision.type(choice);
|
return PaymentDecision.type(choice);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,7 +475,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
if (ability.getPayCosts().hasTapCost() && typeList.contains(ability.getHostCard())) {
|
if (ability.getPayCosts().hasTapCost() && typeList.contains(ability.getHostCard())) {
|
||||||
c--;
|
c--;
|
||||||
}
|
}
|
||||||
source.setSVar("ChosenX", "Number$" + c);
|
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
|
||||||
} else {
|
} else {
|
||||||
if (!isVehicle) {
|
if (!isVehicle) {
|
||||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||||
@@ -809,7 +809,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
final String sVar = ability.getSVar(amount);
|
final String sVar = ability.getSVar(amount);
|
||||||
if (sVar.equals("XChoice")) {
|
if (sVar.equals("XChoice")) {
|
||||||
c = AbilityUtils.calculateAmount(source, "ChosenX", ability);
|
c = AbilityUtils.calculateAmount(source, "ChosenX", ability);
|
||||||
source.setSVar("ChosenX", "Number$" + c);
|
source.setSVar("ChosenX", "Number$" + String.valueOf(c));
|
||||||
} else if (amount.equals("All")) {
|
} else if (amount.equals("All")) {
|
||||||
c = source.getCounters(cost.counter);
|
c = source.getCounters(cost.counter);
|
||||||
} else if (sVar.equals("Targeted$CardManaCost")) {
|
} else if (sVar.equals("Targeted$CardManaCost")) {
|
||||||
@@ -821,8 +821,6 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (sVar.equals("Count$xPaid")) {
|
|
||||||
c = AbilityUtils.calculateAmount(source, "PayX", null);
|
|
||||||
} else {
|
} else {
|
||||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||||
}
|
}
|
||||||
@@ -865,7 +863,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
}
|
}
|
||||||
typeList = CardLists.filter(typeList, Presets.TAPPED);
|
typeList = CardLists.filter(typeList, Presets.TAPPED);
|
||||||
c = typeList.size();
|
c = typeList.size();
|
||||||
source.setSVar("ChosenX", "Number$" + c);
|
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
|
||||||
} else {
|
} else {
|
||||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,5 +17,5 @@ public enum AiPlayDecision {
|
|||||||
WouldBecomeZeroToughnessCreature,
|
WouldBecomeZeroToughnessCreature,
|
||||||
WouldDestroyWorldEnchantment,
|
WouldDestroyWorldEnchantment,
|
||||||
BadEtbEffects,
|
BadEtbEffects,
|
||||||
CurseEffects
|
CurseEffects;
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ import java.util.Map;
|
|||||||
* @version $Id: AIProfile.java 20169 2013-03-08 08:24:17Z Agetian $
|
* @version $Id: AIProfile.java 20169 2013-03-08 08:24:17Z Agetian $
|
||||||
*/
|
*/
|
||||||
public class AiProfileUtil {
|
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 String AI_PROFILE_DIR;
|
||||||
private static final String AI_PROFILE_EXT = ".ai";
|
private static final String AI_PROFILE_EXT = ".ai";
|
||||||
@@ -74,7 +74,7 @@ public class AiProfileUtil {
|
|||||||
* @param profileName a profile to load.
|
* @param profileName a profile to load.
|
||||||
*/
|
*/
|
||||||
private static final Map<AiProps, String> loadProfile(final String profileName) {
|
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));
|
List<String> lines = FileUtil.readFile(buildFileName(profileName));
|
||||||
for (String line : lines) {
|
for (String line : lines) {
|
||||||
@@ -122,7 +122,7 @@ public class AiProfileUtil {
|
|||||||
*/
|
*/
|
||||||
public static List<String> getAvailableProfiles()
|
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 File dir = new File(AI_PROFILE_DIR);
|
||||||
final String[] children = dir.list();
|
final String[] children = dir.list();
|
||||||
@@ -146,7 +146,7 @@ public class AiProfileUtil {
|
|||||||
* available profiles including special random profile tags.
|
* available profiles including special random profile tags.
|
||||||
*/
|
*/
|
||||||
public static List<String> getProfilesDisplayList() {
|
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_MATCH);
|
||||||
availableProfiles.add(AI_PROFILE_RANDOM_DUEL);
|
availableProfiles.add(AI_PROFILE_RANDOM_DUEL);
|
||||||
availableProfiles.addAll(getAvailableProfiles());
|
availableProfiles.addAll(getAvailableProfiles());
|
||||||
|
|||||||
@@ -129,10 +129,7 @@ public enum AiProps { /** */
|
|||||||
FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS("true"),
|
FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS("true"),
|
||||||
FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY("1"),
|
FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY("1"),
|
||||||
FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT("5"),
|
FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT("5"),
|
||||||
FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("100"),
|
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"); /** */
|
|
||||||
// Experimental features, must be promoted or removed after extensive testing and, ideally, defaulting
|
// Experimental features, must be promoted or removed after extensive testing and, ideally, defaulting
|
||||||
// <-- There are no experimental options here -->
|
// <-- There are no experimental options here -->
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import forge.card.MagicColor;
|
|||||||
import forge.card.mana.ManaCostShard;
|
import forge.card.mana.ManaCostShard;
|
||||||
import forge.game.*;
|
import forge.game.*;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
import forge.game.ability.AbilityKey;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.ability.effects.CharmEffect;
|
import forge.game.ability.effects.CharmEffect;
|
||||||
@@ -44,12 +43,10 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementLayer;
|
import forge.game.replacement.ReplacementLayer;
|
||||||
import forge.game.replacement.ReplacementType;
|
|
||||||
import forge.game.spellability.*;
|
import forge.game.spellability.*;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.trigger.WrappedAbility;
|
|
||||||
import forge.game.zone.Zone;
|
import forge.game.zone.Zone;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
@@ -76,18 +73,19 @@ public class ComputerUtil {
|
|||||||
public static boolean handlePlayingSpellAbility(final Player ai, SpellAbility sa, final Game game, Runnable chooseTargets) {
|
public static boolean handlePlayingSpellAbility(final Player ai, SpellAbility sa, final Game game, Runnable chooseTargets) {
|
||||||
game.getStack().freezeStack();
|
game.getStack().freezeStack();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
source.setSplitStateToPlayAbility(sa);
|
|
||||||
|
|
||||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||||
sa = AbilityUtils.addSpliceEffects(sa);
|
if (source.getType().hasStringType("Arcane")) {
|
||||||
if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty() && ai.getController().isAI()) {
|
sa = AbilityUtils.addSpliceEffects(sa);
|
||||||
// we need to reconsider and retarget the SA after additional SAs have been added onto it via splice,
|
if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty() && ai.getController().isAI()) {
|
||||||
// otherwise the AI will fail to add the card to stack and that'll knock it out of the game
|
// we need to reconsider and retarget the SA after additional SAs have been added onto it via splice,
|
||||||
sa.resetTargets();
|
// otherwise the AI will fail to add the card to stack and that'll knock it out of the game
|
||||||
if (((PlayerControllerAi) ai.getController()).getAi().canPlaySa(sa) != AiPlayDecision.WillPlay) {
|
sa.resetTargets();
|
||||||
// for whatever reason the AI doesn't want to play the thing with the spliced subs anymore,
|
if (((PlayerControllerAi) ai.getController()).getAi().canPlaySa(sa) != AiPlayDecision.WillPlay) {
|
||||||
// proceeding past this point may result in an illegal play
|
// for whatever reason the AI doesn't want to play the thing with the spliced subs anymore,
|
||||||
return false;
|
// proceeding past this point may result in an illegal play
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,12 +95,10 @@ public class ComputerUtil {
|
|||||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.isCopied()) {
|
if (sa.isCopied()) {
|
||||||
sa.resetPaidHash();
|
sa.resetPaidHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
sa = GameActionUtil.addExtraKeywordCost(sa);
|
|
||||||
|
|
||||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||||
CharmEffect.makeChoices(sa);
|
CharmEffect.makeChoices(sa);
|
||||||
}
|
}
|
||||||
@@ -212,9 +208,9 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this is used for AI's counterspells
|
// this is used for AI's counterspells
|
||||||
public static final boolean playStack(SpellAbility sa, final Player ai, final Game game) {
|
public static final boolean playStack(final SpellAbility sa, final Player ai, final Game game) {
|
||||||
sa.setActivatingPlayer(ai);
|
sa.setActivatingPlayer(ai);
|
||||||
if (!ComputerUtilCost.canPayCost(sa, ai))
|
if (!ComputerUtilCost.canPayCost(sa, ai))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
@@ -224,9 +220,6 @@ public class ComputerUtil {
|
|||||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
sa = GameActionUtil.addExtraKeywordCost(sa);
|
|
||||||
|
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
if (cost == null) {
|
if (cost == null) {
|
||||||
ComputerUtilMana.payManaCost(ai, sa);
|
ComputerUtilMana.payManaCost(ai, sa);
|
||||||
@@ -256,15 +249,13 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final boolean playSpellAbilityWithoutPayingManaCost(final Player ai, final SpellAbility sa, final Game game) {
|
public static final boolean playSpellAbilityWithoutPayingManaCost(final Player ai, final SpellAbility sa, final Game game) {
|
||||||
SpellAbility newSA = sa.copyWithNoManaCost();
|
final SpellAbility newSA = sa.copyWithNoManaCost();
|
||||||
newSA.setActivatingPlayer(ai);
|
newSA.setActivatingPlayer(ai);
|
||||||
|
|
||||||
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) {
|
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
newSA = GameActionUtil.addExtraKeywordCost(newSA);
|
|
||||||
|
|
||||||
final Card source = newSA.getHostCard();
|
final Card source = newSA.getHostCard();
|
||||||
if (newSA.isSpell() && !source.isCopiedSpell()) {
|
if (newSA.isSpell() && !source.isCopiedSpell()) {
|
||||||
source.setCastSA(newSA);
|
source.setCastSA(newSA);
|
||||||
@@ -284,7 +275,7 @@ public class ComputerUtil {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final void playNoStack(final Player ai, SpellAbility sa, final Game game) {
|
public static final void playNoStack(final Player ai, final SpellAbility sa, final Game game) {
|
||||||
sa.setActivatingPlayer(ai);
|
sa.setActivatingPlayer(ai);
|
||||||
// TODO: We should really restrict what doesn't use the Stack
|
// TODO: We should really restrict what doesn't use the Stack
|
||||||
if (ComputerUtilCost.canPayCost(sa, ai)) {
|
if (ComputerUtilCost.canPayCost(sa, ai)) {
|
||||||
@@ -296,8 +287,6 @@ public class ComputerUtil {
|
|||||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
sa = GameActionUtil.addExtraKeywordCost(sa);
|
|
||||||
|
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
if (cost == null) {
|
if (cost == null) {
|
||||||
ComputerUtilMana.payManaCost(ai, sa);
|
ComputerUtilMana.payManaCost(ai, sa);
|
||||||
@@ -425,7 +414,7 @@ public class ComputerUtil {
|
|||||||
int mana = ComputerUtilMana.getAvailableManaEstimate(ai, false);
|
int mana = ComputerUtilMana.getAvailableManaEstimate(ai, false);
|
||||||
|
|
||||||
boolean cantAffordSoon = activate.getCMC() > mana + 1;
|
boolean cantAffordSoon = activate.getCMC() > mana + 1;
|
||||||
boolean wrongColor = !activate.determineColor().hasNoColorsExcept(ColorSet.fromNames(ComputerUtilCost.getAvailableManaColors(ai, ImmutableList.of())).getColor());
|
boolean wrongColor = !activate.determineColor().hasNoColorsExcept(ColorSet.fromNames(ComputerUtilCost.getAvailableManaColors(ai, ImmutableList.<Card>of())).getColor());
|
||||||
|
|
||||||
// Only do this for spells, not activated abilities
|
// Only do this for spells, not activated abilities
|
||||||
// We can't pay for this spell even if we play another land, or have wrong colors
|
// We can't pay for this spell even if we play another land, or have wrong colors
|
||||||
@@ -526,7 +515,7 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability));
|
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability));
|
||||||
|
|
||||||
if ((target != null) && target.getController() == ai) {
|
if ((target != null) && target.getController() == ai && typeList.contains(target)) {
|
||||||
typeList.remove(target); // don't sacrifice the card we're pumping
|
typeList.remove(target); // don't sacrifice the card we're pumping
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,7 +545,7 @@ public class ComputerUtil {
|
|||||||
final Card target, final int amount) {
|
final Card target, final int amount) {
|
||||||
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, null);
|
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, null);
|
||||||
|
|
||||||
if ((target != null) && target.getController() == ai) {
|
if ((target != null) && target.getController() == ai && typeList.contains(target)) {
|
||||||
typeList.remove(target); // don't exile the card we're pumping
|
typeList.remove(target); // don't exile the card we're pumping
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,7 +566,7 @@ public class ComputerUtil {
|
|||||||
final Card target, final int amount) {
|
final Card target, final int amount) {
|
||||||
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, null);
|
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, null);
|
||||||
|
|
||||||
if ((target != null) && target.getController() == ai) {
|
if ((target != null) && target.getController() == ai && typeList.contains(target)) {
|
||||||
typeList.remove(target); // don't move the card we're pumping
|
typeList.remove(target); // don't move the card we're pumping
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -706,7 +695,7 @@ public class ComputerUtil {
|
|||||||
public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount) {
|
public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount) {
|
||||||
final CardCollection typeList =
|
final CardCollection typeList =
|
||||||
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, null);
|
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, null);
|
||||||
if ((target != null) && target.getController() == ai) {
|
if ((target != null) && target.getController() == ai && typeList.contains(target)) {
|
||||||
// don't bounce the card we're pumping
|
// don't bounce the card we're pumping
|
||||||
typeList.remove(target);
|
typeList.remove(target);
|
||||||
}
|
}
|
||||||
@@ -756,7 +745,7 @@ public class ComputerUtil {
|
|||||||
if (source.hasParam("Exploit")) {
|
if (source.hasParam("Exploit")) {
|
||||||
for (Trigger t : host.getTriggers()) {
|
for (Trigger t : host.getTriggers()) {
|
||||||
if (t.getMode() == TriggerType.Exploited) {
|
if (t.getMode() == TriggerType.Exploited) {
|
||||||
final String execute = t.getParam("Execute");
|
final String execute = t.getMapParams().get("Execute");
|
||||||
if (execute == null) {
|
if (execute == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -796,11 +785,11 @@ public class ComputerUtil {
|
|||||||
if (c.hasSVar("SacMe") || ComputerUtilCard.evaluateCreature(c) < sacThreshold) {
|
if (c.hasSVar("SacMe") || ComputerUtilCard.evaluateCreature(c) < sacThreshold) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCard.hasActiveUndyingOrPersist(c)) {
|
if (ComputerUtilCard.hasActiveUndyingOrPersist(c)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -808,7 +797,7 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
final int max = Math.min(remaining.size(), amount);
|
final int max = Math.min(remaining.size(), amount);
|
||||||
|
|
||||||
if (exceptSelf && max < remaining.size()) {
|
if (exceptSelf) {
|
||||||
removedSelf = remaining.remove(source.getHostCard());
|
removedSelf = remaining.remove(source.getHostCard());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1237,7 +1226,7 @@ public class ComputerUtil {
|
|||||||
//Fill the graveyard for Threshold
|
//Fill the graveyard for Threshold
|
||||||
if (checkThreshold) {
|
if (checkThreshold) {
|
||||||
for (StaticAbility stAb : buffedCard.getStaticAbilities()) {
|
for (StaticAbility stAb : buffedCard.getStaticAbilities()) {
|
||||||
if ("Threshold".equals(stAb.getParam("Condition"))) {
|
if ("Threshold".equals(stAb.getMapParams().get("Condition"))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1265,9 +1254,9 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
// returns true if the AI should stop using the ability
|
// returns true if the AI should stop using the ability
|
||||||
public static boolean preventRunAwayActivations(final SpellAbility sa) {
|
public static boolean preventRunAwayActivations(final SpellAbility sa) {
|
||||||
int activations = sa.getActivationsThisTurn();
|
int activations = sa.getRestrictions().getNumberTurnActivations();
|
||||||
|
|
||||||
if (!sa.isIntrinsic()) {
|
if (sa.isTemporary()) {
|
||||||
return MyRandom.getRandom().nextFloat() >= .95; // Abilities created by static abilities have no memory
|
return MyRandom.getRandom().nextFloat() >= .95; // Abilities created by static abilities have no memory
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1516,7 +1505,7 @@ public class ComputerUtil {
|
|||||||
*/
|
*/
|
||||||
public static List<GameObject> predictThreatenedObjects(final Player ai, final SpellAbility sa, boolean top) {
|
public static List<GameObject> predictThreatenedObjects(final Player ai, final SpellAbility sa, boolean top) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final List<GameObject> objects = new ArrayList<>();
|
final List<GameObject> objects = new ArrayList<GameObject>();
|
||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
@@ -1526,9 +1515,6 @@ public class ComputerUtil {
|
|||||||
// iterate from top of stack to find SpellAbility, including sub-abilities,
|
// iterate from top of stack to find SpellAbility, including sub-abilities,
|
||||||
// that does not match "sa"
|
// that does not match "sa"
|
||||||
SpellAbility spell = si.getSpellAbility(true), sub = spell.getSubAbility();
|
SpellAbility spell = si.getSpellAbility(true), sub = spell.getSubAbility();
|
||||||
if (spell.isWrapper()) {
|
|
||||||
spell = ((WrappedAbility) spell).getWrappedAbility();
|
|
||||||
}
|
|
||||||
while (sub != null && sub != sa) {
|
while (sub != null && sub != sa) {
|
||||||
sub = sub.getSubAbility();
|
sub = sub.getSubAbility();
|
||||||
}
|
}
|
||||||
@@ -1545,8 +1531,8 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
private static Iterable<? extends GameObject> predictThreatenedObjects(final Player aiPlayer, final SpellAbility saviour,
|
private static Iterable<? extends GameObject> predictThreatenedObjects(final Player aiPlayer, final SpellAbility saviour,
|
||||||
final SpellAbility topStack) {
|
final SpellAbility topStack) {
|
||||||
Iterable<? extends GameObject> objects = new ArrayList<>();
|
Iterable<? extends GameObject> objects = new ArrayList<GameObject>();
|
||||||
final List<GameObject> threatened = new ArrayList<>();
|
final List<GameObject> threatened = new ArrayList<GameObject>();
|
||||||
ApiType saviourApi = saviour == null ? null : saviour.getApi();
|
ApiType saviourApi = saviour == null ? null : saviour.getApi();
|
||||||
int toughness = 0;
|
int toughness = 0;
|
||||||
boolean grantIndestructible = false;
|
boolean grantIndestructible = false;
|
||||||
@@ -1576,7 +1562,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
objects = topStack.getTargets().getTargets();
|
objects = topStack.getTargets().getTargets();
|
||||||
final List<GameObject> canBeTargeted = new ArrayList<>();
|
final List<GameObject> canBeTargeted = new ArrayList<GameObject>();
|
||||||
for (Object o : objects) {
|
for (Object o : objects) {
|
||||||
if (o instanceof Card) {
|
if (o instanceof Card) {
|
||||||
final Card c = (Card) o;
|
final Card c = (Card) o;
|
||||||
@@ -1599,7 +1585,7 @@ public class ComputerUtil {
|
|||||||
toughness = saviorWithSubs.hasParam("NumDef") ?
|
toughness = saviorWithSubs.hasParam("NumDef") ?
|
||||||
AbilityUtils.calculateAmount(saviorWithSubs.getHostCard(), saviorWithSubs.getParam("NumDef"), saviour) : 0;
|
AbilityUtils.calculateAmount(saviorWithSubs.getHostCard(), saviorWithSubs.getParam("NumDef"), saviour) : 0;
|
||||||
final List<String> keywords = saviorWithSubs.hasParam("KW") ?
|
final List<String> keywords = saviorWithSubs.hasParam("KW") ?
|
||||||
Arrays.asList(saviorWithSubs.getParam("KW").split(" & ")) : new ArrayList<>();
|
Arrays.asList(saviorWithSubs.getParam("KW").split(" & ")) : new ArrayList<String>();
|
||||||
if (keywords.contains("Indestructible")) {
|
if (keywords.contains("Indestructible")) {
|
||||||
grantIndestructible = true;
|
grantIndestructible = true;
|
||||||
}
|
}
|
||||||
@@ -1614,7 +1600,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (saviourApi == ApiType.PutCounter || saviourApi == ApiType.PutCounterAll) {
|
if (saviourApi == ApiType.PutCounter || saviourApi == ApiType.PutCounterAll) {
|
||||||
if (saviour != null && saviour.getParam("CounterType").equals("P1P1")) {
|
if (saviour.getParam("CounterType").equals("P1P1")) {
|
||||||
toughness = AbilityUtils.calculateAmount(saviour.getHostCard(), saviour.getParam("CounterNum"), saviour);
|
toughness = AbilityUtils.calculateAmount(saviour.getHostCard(), saviour.getParam("CounterNum"), saviour);
|
||||||
} else {
|
} else {
|
||||||
return threatened;
|
return threatened;
|
||||||
@@ -1632,7 +1618,7 @@ public class ComputerUtil {
|
|||||||
final SpellAbility sub = topStack.getSubAbility();
|
final SpellAbility sub = topStack.getSubAbility();
|
||||||
boolean noRegen = false;
|
boolean noRegen = false;
|
||||||
if (sub != null && sub.getApi() == ApiType.Pump) {
|
if (sub != null && sub.getApi() == ApiType.Pump) {
|
||||||
final List<String> keywords = sub.hasParam("KW") ? Arrays.asList(sub.getParam("KW").split(" & ")) : new ArrayList<>();
|
final List<String> keywords = sub.hasParam("KW") ? Arrays.asList(sub.getParam("KW").split(" & ")) : new ArrayList<String>();
|
||||||
for (String kw : keywords) {
|
for (String kw : keywords) {
|
||||||
if (kw.contains("can't be regenerated")) {
|
if (kw.contains("can't be regenerated")) {
|
||||||
noRegen = true;
|
noRegen = true;
|
||||||
@@ -1927,9 +1913,9 @@ public class ComputerUtil {
|
|||||||
if (predictThreatenedObjects(ai, null).contains(source)) {
|
if (predictThreatenedObjects(ai, null).contains(source)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (game.getPhaseHandler().inCombat() &&
|
if (game.getPhaseHandler().inCombat() &&
|
||||||
ComputerUtilCombat.combatantWouldBeDestroyed(ai, source, game.getCombat())) {
|
ComputerUtilCombat.combatantWouldBeDestroyed(ai, source, game.getCombat())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (zone.getZoneType() == ZoneType.Exile && sa.getMayPlay() != null) {
|
} else if (zone.getZoneType() == ZoneType.Exile && sa.getMayPlay() != null) {
|
||||||
// play cards in exile that can only be played that turn
|
// play cards in exile that can only be played that turn
|
||||||
@@ -1943,19 +1929,12 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static int scoreHand(CardCollectionView handList, Player ai, int cardsToReturn) {
|
public static int scoreHand(CardCollectionView handList, Player ai) {
|
||||||
// TODO Improve hand scoring in relation to cards to return.
|
|
||||||
// If final hand size is 5, score a hand based on what that 5 would be.
|
|
||||||
// Or if this is really really fast, determine what the 5 would be based on scoring
|
|
||||||
// All of the possibilities
|
|
||||||
|
|
||||||
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
int currentHandSize = handList.size();
|
|
||||||
int finalHandSize = currentHandSize - cardsToReturn;
|
|
||||||
|
|
||||||
// don't mulligan when already too low
|
// don't mulligan when already too low
|
||||||
if (finalHandSize < aic.getIntProperty(AiProps.MULLIGAN_THRESHOLD)) {
|
if (handList.size() < aic.getIntProperty(AiProps.MULLIGAN_THRESHOLD)) {
|
||||||
return finalHandSize;
|
return handList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollectionView library = ai.getZone(ZoneType.Library).getCards();
|
CardCollectionView library = ai.getZone(ZoneType.Library).getCards();
|
||||||
@@ -1963,14 +1942,17 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
// no land deck, can't do anything better
|
// no land deck, can't do anything better
|
||||||
if (landsInDeck == 0) {
|
if (landsInDeck == 0) {
|
||||||
return finalHandSize;
|
return handList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
final CardCollectionView lands = CardLists.filter(handList, new Predicate<Card>() {
|
final CardCollectionView lands = CardLists.filter(handList, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return c.getManaCost().getCMC() <= 0 && !c.hasSVar("NeedsToPlay")
|
if (c.getManaCost().getCMC() > 0 || c.hasSVar("NeedsToPlay")
|
||||||
&& (c.getType().isLand() || c.getType().isArtifact());
|
|| (!c.getType().isLand() && !c.getType().isArtifact())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1985,7 +1967,10 @@ public class ComputerUtil {
|
|||||||
final CardCollectionView castables = CardLists.filter(handList, new Predicate<Card>() {
|
final CardCollectionView castables = CardLists.filter(handList, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return c.getManaCost().getCMC() <= 0 || c.getManaCost().getCMC() > landSize;
|
if (c.getManaCost().getCMC() > 0 && c.getManaCost().getCMC() <= landSize) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2025,9 +2010,9 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Computer mulligans if there are no cards with converted mana cost of 0 in its hand
|
// Computer mulligans if there are no cards with converted mana cost of 0 in its hand
|
||||||
public static boolean wantMulligan(Player ai, int cardsToReturn) {
|
public static boolean wantMulligan(Player ai) {
|
||||||
final CardCollectionView handList = ai.getCardsIn(ZoneType.Hand);
|
final CardCollectionView handList = ai.getCardsIn(ZoneType.Hand);
|
||||||
return scoreHand(handList, ai, cardsToReturn) <= 0;
|
return scoreHand(handList, ai) <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CardCollection getPartialParisCandidates(Player ai) {
|
public static CardCollection getPartialParisCandidates(Player ai) {
|
||||||
@@ -2055,7 +2040,7 @@ public class ComputerUtil {
|
|||||||
//Too many lands!
|
//Too many lands!
|
||||||
//Init
|
//Init
|
||||||
int cntColors = MagicColor.WUBRG.length;
|
int cntColors = MagicColor.WUBRG.length;
|
||||||
List<CardCollection> numProducers = new ArrayList<>(cntColors);
|
List<CardCollection> numProducers = new ArrayList<CardCollection>(cntColors);
|
||||||
for (byte col : MagicColor.WUBRG) {
|
for (byte col : MagicColor.WUBRG) {
|
||||||
numProducers.add(col, new CardCollection());
|
numProducers.add(col, new CardCollection());
|
||||||
}
|
}
|
||||||
@@ -2182,7 +2167,10 @@ public class ComputerUtil {
|
|||||||
CardCollection goodChoices = CardLists.filter(validCards, new Predicate<Card>() {
|
CardCollection goodChoices = CardLists.filter(validCards, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return !c.hasSVar("DiscardMeByOpp") && !c.hasSVar("DiscardMe");
|
if (c.hasSVar("DiscardMeByOpp") || c.hasSVar("DiscardMe")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (goodChoices.isEmpty()) {
|
if (goodChoices.isEmpty()) {
|
||||||
@@ -2198,7 +2186,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Collections.sort(goodChoices, CardLists.TextLenComparator);
|
Collections.sort(goodChoices, CardLists.TextLenComparator);
|
||||||
|
|
||||||
CardLists.sortByCmcDesc(goodChoices);
|
CardLists.sortByCmcDesc(goodChoices);
|
||||||
@@ -2218,7 +2206,7 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
public static String chooseSomeType(Player ai, String kindOfType, String logic, List<String> invalidTypes) {
|
public static String chooseSomeType(Player ai, String kindOfType, String logic, List<String> invalidTypes) {
|
||||||
if (invalidTypes == null) {
|
if (invalidTypes == null) {
|
||||||
invalidTypes = ImmutableList.of();
|
invalidTypes = ImmutableList.<String>of();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
@@ -2285,7 +2273,8 @@ public class ComputerUtil {
|
|||||||
chosen = ComputerUtilCard.getMostProminentType(list, valid);
|
chosen = ComputerUtilCard.getMostProminentType(list, valid);
|
||||||
} else if (logic.equals("MostNeededType")) {
|
} else if (logic.equals("MostNeededType")) {
|
||||||
// Choose a type that is in the deck, but not in hand or on the battlefield
|
// Choose a type that is in the deck, but not in hand or on the battlefield
|
||||||
final List<String> basics = new ArrayList<>(CardType.Constant.BASIC_TYPES);
|
final List<String> basics = new ArrayList<String>();
|
||||||
|
basics.addAll(CardType.Constant.BASIC_TYPES);
|
||||||
CardCollectionView presentCards = CardCollection.combine(ai.getCardsIn(ZoneType.Battlefield), ai.getCardsIn(ZoneType.Hand));
|
CardCollectionView presentCards = CardCollection.combine(ai.getCardsIn(ZoneType.Battlefield), ai.getCardsIn(ZoneType.Hand));
|
||||||
CardCollectionView possibleCards = ai.getAllCards();
|
CardCollectionView possibleCards = ai.getAllCards();
|
||||||
|
|
||||||
@@ -2403,7 +2392,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
// if source is not on the battlefield anymore, choose +1/+1
|
// if source is not on the battlefield anymore, choose +1/+1
|
||||||
// ones
|
// ones
|
||||||
if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
|
if (!game.getCardState(source).getZone().is(ZoneType.Battlefield)) {
|
||||||
return opponent ? "Feather" : "Quill";
|
return opponent ? "Feather" : "Quill";
|
||||||
}
|
}
|
||||||
// if no hand cards, try to mill opponent
|
// if no hand cards, try to mill opponent
|
||||||
@@ -2435,7 +2424,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if source is not on the battlefield anymore
|
// if source is not on the battlefield anymore
|
||||||
if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
|
if (!game.getCardState(source).getZone().is(ZoneType.Battlefield)) {
|
||||||
return opponent ? "Strength" : "Numbers";
|
return opponent ? "Strength" : "Numbers";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2484,7 +2473,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if source is not on the battlefield anymore
|
// if source is not on the battlefield anymore
|
||||||
if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
|
if (!game.getCardState(source).getZone().is(ZoneType.Battlefield)) {
|
||||||
return opponent ? "Sprout" : "Harvest";
|
return opponent ? "Sprout" : "Harvest";
|
||||||
}
|
}
|
||||||
// TODO add Lifegain to +1/+1 counters trigger
|
// TODO add Lifegain to +1/+1 counters trigger
|
||||||
@@ -2538,7 +2527,8 @@ public class ComputerUtil {
|
|||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
if (c.getController() == ai) {
|
if (c.getController() == ai) {
|
||||||
return !c.getSVar("Targeting").equals("Dies") && !c.getSVar("Targeting").equals("Counter");
|
if (c.getSVar("Targeting").equals("Dies") || c.getSVar("Targeting").equals("Counter"))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2589,7 +2579,7 @@ public class ComputerUtil {
|
|||||||
int damage = 0;
|
int damage = 0;
|
||||||
final Game game = player.getGame();
|
final Game game = player.getGame();
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
final FCollection<Trigger> theTriggers = new FCollection<>();
|
final FCollection<Trigger> theTriggers = new FCollection<Trigger>();
|
||||||
|
|
||||||
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
theTriggers.addAll(c.getTriggers());
|
theTriggers.addAll(c.getTriggers());
|
||||||
@@ -2681,7 +2671,7 @@ public class ComputerUtil {
|
|||||||
public static int getDamageFromETB(final Player player, final Card permanent) {
|
public static int getDamageFromETB(final Player player, final Card permanent) {
|
||||||
int damage = 0;
|
int damage = 0;
|
||||||
final Game game = player.getGame();
|
final Game game = player.getGame();
|
||||||
final FCollection<Trigger> theTriggers = new FCollection<>();
|
final FCollection<Trigger> theTriggers = new FCollection<Trigger>();
|
||||||
|
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
@@ -2779,7 +2769,7 @@ public class ComputerUtil {
|
|||||||
// Iceberg does use Ice as Storage
|
// Iceberg does use Ice as Storage
|
||||||
|| (type == CounterType.ICE && !"Iceberg".equals(c.getName()))
|
|| (type == CounterType.ICE && !"Iceberg".equals(c.getName()))
|
||||||
// some lands does use Depletion as Storage Counter
|
// some lands does use Depletion as Storage Counter
|
||||||
|| (type == CounterType.DEPLETION && !c.canUntapPhaseController())
|
|| (type == CounterType.DEPLETION && c.hasKeyword("CARDNAME doesn't untap during your untap step."))
|
||||||
// treat Time Counters on suspended Cards as Bad,
|
// treat Time Counters on suspended Cards as Bad,
|
||||||
// and also on Chronozoa
|
// and also on Chronozoa
|
||||||
|| (type == CounterType.TIME && (!c.isInPlay() || "Chronozoa".equals(c.getName())))
|
|| (type == CounterType.TIME && (!c.isInPlay() || "Chronozoa".equals(c.getName())))
|
||||||
@@ -2850,14 +2840,14 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run any applicable replacement effects.
|
// Run any applicable replacement effects.
|
||||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(player);
|
final Map<String, Object> repParams = Maps.newHashMap();
|
||||||
repParams.put(AbilityKey.LifeGained, 1);
|
repParams.put("Event", "GainLife");
|
||||||
repParams.put(AbilityKey.Source, source);
|
repParams.put("Affected", player);
|
||||||
|
repParams.put("LifeGained", 1);
|
||||||
|
repParams.put("Source", source);
|
||||||
|
|
||||||
List<ReplacementEffect> list = player.getGame().getReplacementHandler().getReplacementList(
|
List<ReplacementEffect> list = player.getGame().getReplacementHandler().getReplacementList(repParams,
|
||||||
ReplacementType.GainLife,
|
ReplacementLayer.None);
|
||||||
repParams,
|
|
||||||
ReplacementLayer.Other);
|
|
||||||
|
|
||||||
if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "NoLife"))) {
|
if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "NoLife"))) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2866,6 +2856,7 @@ public class ComputerUtil {
|
|||||||
} else if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "LichDraw"))) {
|
} else if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "LichDraw"))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2880,15 +2871,14 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run any applicable replacement effects.
|
// Run any applicable replacement effects.
|
||||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(player);
|
final Map<String, Object> repParams = Maps.newHashMap();
|
||||||
repParams.put(AbilityKey.LifeGained, n);
|
repParams.put("Event", "GainLife");
|
||||||
repParams.put(AbilityKey.Source, source);
|
repParams.put("Affected", player);
|
||||||
|
repParams.put("LifeGained", n);
|
||||||
|
repParams.put("Source", source);
|
||||||
|
|
||||||
List<ReplacementEffect> list = player.getGame().getReplacementHandler().getReplacementList(
|
List<ReplacementEffect> list = player.getGame().getReplacementHandler().getReplacementList(repParams,
|
||||||
ReplacementType.GainLife,
|
ReplacementLayer.None);
|
||||||
repParams,
|
|
||||||
ReplacementLayer.Other
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "NoLife"))) {
|
if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "NoLife"))) {
|
||||||
// no life gain is not negative
|
// no life gain is not negative
|
||||||
@@ -2998,23 +2988,14 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
if (sa.hasParam("AITgts")) {
|
if (sa.hasParam("AITgts")) {
|
||||||
CardCollection list;
|
CardCollection list;
|
||||||
String aiTgts = sa.getParam("AITgts");
|
if (sa.getParam("AITgts").equals("BetterThanSource")) {
|
||||||
if (aiTgts.startsWith("BetterThan")) {
|
int value = ComputerUtilCard.evaluateCreature(source);
|
||||||
int value = 0;
|
if (source.isEnchanted()) {
|
||||||
if (aiTgts.endsWith("Source")) {
|
for (Card enc : source.getEnchantedBy()) {
|
||||||
value = ComputerUtilCard.evaluateCreature(source);
|
if (enc.getController().equals(ai)) {
|
||||||
if (source.isEnchanted()) {
|
value += 100; // is 100 per AI's own aura enough?
|
||||||
for (Card enc : source.getEnchantedBy()) {
|
|
||||||
if (enc.getController().equals(ai)) {
|
|
||||||
value += 100; // is 100 per AI's own aura enough?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (aiTgts.contains("EvalRating.")) {
|
|
||||||
value = AbilityUtils.calculateAmount(source, aiTgts.substring(aiTgts.indexOf(".") + 1), sa);
|
|
||||||
} else {
|
|
||||||
System.err.println("Warning: Unspecified AI target evaluation rating for SA " + sa);
|
|
||||||
value = ComputerUtilCard.evaluateCreature(source);
|
|
||||||
}
|
}
|
||||||
final int totalValue = value;
|
final int totalValue = value;
|
||||||
list = CardLists.filter(srcList, new Predicate<Card>() {
|
list = CardLists.filter(srcList, new Predicate<Card>() {
|
||||||
|
|||||||
@@ -95,15 +95,9 @@ public class ComputerUtilAbility {
|
|||||||
|
|
||||||
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
|
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
|
||||||
final List<SpellAbility> newAbilities = Lists.newArrayList();
|
final List<SpellAbility> newAbilities = Lists.newArrayList();
|
||||||
|
|
||||||
List<SpellAbility> originListWithAddCosts = Lists.newArrayList();
|
|
||||||
for (SpellAbility sa : originList) {
|
for (SpellAbility sa : originList) {
|
||||||
// If this spell has alternative additional costs, add them instead of the unmodified SA itself
|
|
||||||
sa.setActivatingPlayer(player);
|
sa.setActivatingPlayer(player);
|
||||||
originListWithAddCosts.addAll(GameActionUtil.getAdditionalCostSpell(sa));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (SpellAbility sa : originListWithAddCosts) {
|
|
||||||
// determine which alternative costs are cheaper than the original and prioritize them
|
// determine which alternative costs are cheaper than the original and prioritize them
|
||||||
List<SpellAbility> saAltCosts = GameActionUtil.getAlternativeCosts(sa, player);
|
List<SpellAbility> saAltCosts = GameActionUtil.getAlternativeCosts(sa, player);
|
||||||
List<SpellAbility> priorityAltSa = Lists.newArrayList();
|
List<SpellAbility> priorityAltSa = Lists.newArrayList();
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ public class ComputerUtilCard {
|
|||||||
Card cheapest = null;
|
Card cheapest = null;
|
||||||
|
|
||||||
for (Card c : all) {
|
for (Card c : all) {
|
||||||
if (cheapest == null || c.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
|
if (cheapest == null || cheapest.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
|
||||||
cheapest = c;
|
cheapest = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,7 +368,7 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasEnchantmants || hasArtifacts) {
|
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
|
@Override
|
||||||
public boolean apply(Card card) {
|
public boolean apply(Card card) {
|
||||||
return !card.hasSVar("DoNotDiscardIfAble");
|
return !card.hasSVar("DoNotDiscardIfAble");
|
||||||
@@ -521,7 +521,7 @@ public class ComputerUtilCard {
|
|||||||
*/
|
*/
|
||||||
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
|
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
|
||||||
AiBlockController aiBlk = new AiBlockController(ai);
|
AiBlockController aiBlk = new AiBlockController(ai);
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
Combat combat = new Combat(opp);
|
Combat combat = new Combat(opp);
|
||||||
//Use actual attackers if available, else consider all possible attackers
|
//Use actual attackers if available, else consider all possible attackers
|
||||||
Combat currentCombat = ai.getGame().getCombat();
|
Combat currentCombat = ai.getGame().getCombat();
|
||||||
@@ -564,7 +564,7 @@ public class ComputerUtilCard {
|
|||||||
AiBlockController aiBlk = new AiBlockController(ai);
|
AiBlockController aiBlk = new AiBlockController(ai);
|
||||||
Combat combat = new Combat(ai);
|
Combat combat = new Combat(ai);
|
||||||
combat.addAttacker(attacker, ai);
|
combat.addAttacker(attacker, ai);
|
||||||
final List<Card> attackers = new ArrayList<>();
|
final List<Card> attackers = new ArrayList<Card>();
|
||||||
attackers.add(attacker);
|
attackers.add(attacker);
|
||||||
aiBlk.assignBlockersGivenAttackers(combat, attackers);
|
aiBlk.assignBlockersGivenAttackers(combat, attackers);
|
||||||
return ComputerUtilCombat.attackerWouldBeDestroyed(ai, attacker, combat);
|
return ComputerUtilCombat.attackerWouldBeDestroyed(ai, attacker, combat);
|
||||||
@@ -788,7 +788,7 @@ public class ComputerUtilCard {
|
|||||||
|
|
||||||
public static List<String> getColorByProminence(final List<Card> list) {
|
public static List<String> getColorByProminence(final List<Card> list) {
|
||||||
int cntColors = MagicColor.WUBRG.length;
|
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++) {
|
for(int i = 0; i < cntColors; i++) {
|
||||||
map.add(MutablePair.of(MagicColor.WUBRG[i], 0));
|
map.add(MutablePair.of(MagicColor.WUBRG[i], 0));
|
||||||
}
|
}
|
||||||
@@ -809,7 +809,7 @@ public class ComputerUtilCard {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// will this part be once dropped?
|
// 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
|
for(Pair<Byte, Integer> idx : map) { // fetch color names in the same order
|
||||||
result.add(MagicColor.toLongString(idx.getKey()));
|
result.add(MagicColor.toLongString(idx.getKey()));
|
||||||
}
|
}
|
||||||
@@ -881,10 +881,10 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
public static List<String> chooseColor(SpellAbility sa, int min, int max, List<String> colorChoices) {
|
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();
|
Player ai = sa.getActivatingPlayer();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
final String logic = sa.getParam("AILogic");
|
final String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
@@ -926,7 +926,7 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
else if (logic.equals("MostProminentInComputerDeckButGreen")) {
|
else if (logic.equals("MostProminentInComputerDeckButGreen")) {
|
||||||
List<String> prominence = ComputerUtilCard.getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai));
|
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));
|
chosen.add(prominence.get(1));
|
||||||
} else {
|
} else {
|
||||||
chosen.add(prominence.get(0));
|
chosen.add(prominence.get(0));
|
||||||
@@ -974,7 +974,7 @@ public class ComputerUtilCard {
|
|||||||
public static boolean useRemovalNow(final SpellAbility sa, final Card c, final int dmg, ZoneType destination) {
|
public static boolean useRemovalNow(final SpellAbility sa, final Card c, final int dmg, ZoneType destination) {
|
||||||
final Player ai = sa.getActivatingPlayer();
|
final Player ai = sa.getActivatingPlayer();
|
||||||
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
final PhaseType phaseType = ph.getPhase();
|
final PhaseType phaseType = ph.getPhase();
|
||||||
@@ -1128,14 +1128,14 @@ public class ComputerUtilCard {
|
|||||||
// assume it either benefits the player or disrupts the opponent
|
// assume it either benefits the player or disrupts the opponent
|
||||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
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;
|
priority = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!priority) {
|
if (!priority) {
|
||||||
for (final Trigger t : c.getTriggers()) {
|
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
|
// has a triggered ability, could be benefitting the opponent or disrupting the AI
|
||||||
priority = true;
|
priority = true;
|
||||||
break;
|
break;
|
||||||
@@ -1213,7 +1213,6 @@ public class ComputerUtilCard {
|
|||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final PhaseHandler phase = game.getPhaseHandler();
|
final PhaseHandler phase = game.getPhaseHandler();
|
||||||
final Combat combat = phase.getCombat();
|
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 isBerserk = "Berserk".equals(sa.getParam("AILogic"));
|
||||||
final boolean loseCardAtEOT = "Sacrifice".equals(sa.getParam("AtEOT")) || "Exile".equals(sa.getParam("AtEOT"))
|
final boolean loseCardAtEOT = "Sacrifice".equals(sa.getParam("AtEOT")) || "Exile".equals(sa.getParam("AtEOT"))
|
||||||
|| "Destroy".equals(sa.getParam("AtEOT")) || "ExileCombat".equals(sa.getParam("AtEOT"));
|
|| "Destroy".equals(sa.getParam("AtEOT")) || "ExileCombat".equals(sa.getParam("AtEOT"));
|
||||||
@@ -1251,7 +1250,7 @@ public class ComputerUtilCard {
|
|||||||
// will the creature attack (only relevant for sorcery speed)?
|
// will the creature attack (only relevant for sorcery speed)?
|
||||||
if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& phase.isPlayerTurn(ai)
|
&& phase.isPlayerTurn(ai)
|
||||||
&& SpellAbilityAi.isSorcerySpeed(sa) || main1Preferred
|
&& SpellAbilityAi.isSorcerySpeed(sa)
|
||||||
&& power > 0
|
&& power > 0
|
||||||
&& ComputerUtilCard.doesCreatureAttackAI(ai, c)) {
|
&& ComputerUtilCard.doesCreatureAttackAI(ai, c)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1270,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);
|
Card pumped = getPumpedCreature(ai, sa, c, toughness, power, keywords);
|
||||||
List<Card> oppCreatures = opp.getCreaturesInPlay();
|
List<Card> oppCreatures = opp.getCreaturesInPlay();
|
||||||
float chance = 0;
|
float chance = 0;
|
||||||
@@ -1294,14 +1293,14 @@ public class ComputerUtilCard {
|
|||||||
// cast it during Declare Blockers, thus ruining its attacker
|
// cast it during Declare Blockers, thus ruining its attacker
|
||||||
if (holdCombatTricks && sa.getApi() == ApiType.Pump
|
if (holdCombatTricks && sa.getApi() == ApiType.Pump
|
||||||
&& sa.hasParam("NumAtt") && sa.getHostCard() != null
|
&& 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
|
&& c.getNetPower() > 0 // too obvious if attacking with a 0-power creature
|
||||||
&& sa.getHostCard().isInstant() // only do it for instant speed spells in hand
|
&& sa.getHostCard().isInstant() // only do it for instant speed spells in hand
|
||||||
&& ComputerUtilMana.hasEnoughManaSourcesToCast(sa, ai)) {
|
&& ComputerUtilMana.hasEnoughManaSourcesToCast(sa, ai)) {
|
||||||
combatTrick = true;
|
combatTrick = true;
|
||||||
|
|
||||||
final List<String> kws = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & "))
|
final List<String> kws = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & "))
|
||||||
: Lists.newArrayList();
|
: Lists.<String>newArrayList();
|
||||||
for (String kw : kws) {
|
for (String kw : kws) {
|
||||||
if (!kw.equals("Trample") && !kw.equals("First Strike") && !kw.equals("Double Strike")) {
|
if (!kw.equals("Trample") && !kw.equals("First Strike") && !kw.equals("Double Strike")) {
|
||||||
combatTrick = false;
|
combatTrick = false;
|
||||||
@@ -1457,10 +1456,6 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
if (totalPowerUnblocked >= opp.getLife()) {
|
if (totalPowerUnblocked >= opp.getLife()) {
|
||||||
return true;
|
return true;
|
||||||
} else if (totalPowerUnblocked > dmg && sa.getHostCard() != null && sa.getHostCard().isInPlay()) {
|
|
||||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost()) {
|
|
||||||
return true; // always activate abilities which cost no mana and which can increase unblocked damage
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float value = 1.0f * (pumpedDmg - dmg);
|
float value = 1.0f * (pumpedDmg - dmg);
|
||||||
@@ -1571,7 +1566,7 @@ public class ComputerUtilCard {
|
|||||||
Card pumped = CardFactory.copyCard(c, true);
|
Card pumped = CardFactory.copyCard(c, true);
|
||||||
pumped.setSickness(c.hasSickness());
|
pumped.setSickness(c.hasSickness());
|
||||||
final long timestamp = c.getGame().getNextTimestamp();
|
final long timestamp = c.getGame().getNextTimestamp();
|
||||||
final List<String> kws = new ArrayList<>();
|
final List<String> kws = new ArrayList<String>();
|
||||||
for (String kw : keywords) {
|
for (String kw : keywords) {
|
||||||
if (kw.startsWith("HIDDEN")) {
|
if (kw.startsWith("HIDDEN")) {
|
||||||
pumped.addHiddenExtrinsicKeyword(kw);
|
pumped.addHiddenExtrinsicKeyword(kw);
|
||||||
@@ -1601,12 +1596,12 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pumped.addNewPT(c.getCurrentPower(), c.getCurrentToughness(), timestamp);
|
pumped.addNewPT(c.getCurrentPower(), c.getCurrentToughness(), timestamp);
|
||||||
pumped.setPTBoost(c.getPTBoostTable());
|
pumped.addTempPowerBoost(c.getTempPowerBoost() + power + berserkPower);
|
||||||
pumped.addPTBoost(power + berserkPower, toughness, timestamp, null);
|
pumped.addTempToughnessBoost(c.getTempToughnessBoost() + toughness);
|
||||||
pumped.addChangedCardKeywords(kws, null, false, false, timestamp);
|
pumped.addChangedCardKeywords(kws, null, false, false, timestamp);
|
||||||
Set<CounterType> types = c.getCounters().keySet();
|
Set<CounterType> types = c.getCounters().keySet();
|
||||||
for(CounterType ct : types) {
|
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.)
|
//Copies tap-state and extra keywords (auras, equipment, etc.)
|
||||||
if (c.isTapped()) {
|
if (c.isTapped()) {
|
||||||
@@ -1648,9 +1643,7 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
list.add(vCard); // account for the static abilities that may be present on the card itself
|
list.add(vCard); // account for the static abilities that may be present on the card itself
|
||||||
for (final Card c : list) {
|
for (final Card c : list) {
|
||||||
// remove old boost that might be copied
|
|
||||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||||
vCard.removePTBoost(c.getTimestamp(), stAb.getId());
|
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
final Map<String, String> params = stAb.getMapParams();
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
if (!params.get("Mode").equals("Continuous")) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1665,25 +1658,26 @@ public class ComputerUtilCard {
|
|||||||
if (!vCard.isValid(valid, c.getController(), c, null)) {
|
if (!vCard.isValid(valid, c.getController(), c, null)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int att = 0;
|
|
||||||
if (params.containsKey("AddPower")) {
|
if (params.containsKey("AddPower")) {
|
||||||
String addP = params.get("AddPower");
|
String addP = params.get("AddPower");
|
||||||
|
int att = 0;
|
||||||
if (addP.equals("AffectedX")) {
|
if (addP.equals("AffectedX")) {
|
||||||
att = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addP));
|
att = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addP));
|
||||||
} else {
|
} else {
|
||||||
att = AbilityUtils.calculateAmount(c, addP, stAb);
|
att = AbilityUtils.calculateAmount(c, addP, stAb);
|
||||||
}
|
}
|
||||||
|
vCard.addTempPowerBoost(att);
|
||||||
}
|
}
|
||||||
int def = 0;
|
|
||||||
if (params.containsKey("AddToughness")) {
|
if (params.containsKey("AddToughness")) {
|
||||||
String addT = params.get("AddToughness");
|
String addT = params.get("AddToughness");
|
||||||
|
int def = 0;
|
||||||
if (addT.equals("AffectedY")) {
|
if (addT.equals("AffectedY")) {
|
||||||
def = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addT));
|
def = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addT));
|
||||||
} else {
|
} else {
|
||||||
def = AbilityUtils.calculateAmount(c, addT, stAb);
|
def = AbilityUtils.calculateAmount(c, addT, stAb);
|
||||||
}
|
}
|
||||||
|
vCard.addTempToughnessBoost(def);
|
||||||
}
|
}
|
||||||
vCard.addPTBoost(att, def, c.getTimestamp(), stAb.getId());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1742,7 +1736,7 @@ public class ComputerUtilCard {
|
|||||||
if (!c.isCreature()) {
|
if (!c.isCreature()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (c.hasKeyword("CARDNAME can't attack or block.") || (!c.canUntapPhaseController() && c.isTapped()) || (c.getOwner() == ai && ai.getOpponents().contains(c.getController()))) {
|
if (c.hasKeyword("CARDNAME can't attack or block.") || (c.hasKeyword("CARDNAME doesn't untap during your untap step.") && c.isTapped()) || (c.getOwner() == ai && ai.getOpponents().contains(c.getController()))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -1848,45 +1842,8 @@ public class ComputerUtilCard {
|
|||||||
String needsToPlayName = isRightSplit ? "SplitNeedsToPlay" : "NeedsToPlay";
|
String needsToPlayName = isRightSplit ? "SplitNeedsToPlay" : "NeedsToPlay";
|
||||||
String needsToPlayVarName = isRightSplit ? "SplitNeedsToPlayVar" : "NeedsToPlayVar";
|
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)) {
|
if (card.hasSVar(needsToPlayName)) {
|
||||||
final String needsToPlay = card.getSVar(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);
|
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
|
||||||
|
|
||||||
list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card, null);
|
list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card, null);
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
@@ -28,8 +28,8 @@ import com.google.common.collect.Maps;
|
|||||||
import forge.game.CardTraitBase;
|
import forge.game.CardTraitBase;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
|
import forge.game.GlobalRuleChange;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
import forge.game.ability.AbilityKey;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
@@ -42,7 +42,6 @@ import forge.game.phase.Untap;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementLayer;
|
import forge.game.replacement.ReplacementLayer;
|
||||||
import forge.game.replacement.ReplacementType;
|
|
||||||
import forge.game.spellability.AbilityActivated;
|
import forge.game.spellability.AbilityActivated;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
@@ -59,7 +58,7 @@ import forge.util.collect.FCollection;
|
|||||||
* <p>
|
* <p>
|
||||||
* ComputerCombatUtil class.
|
* ComputerCombatUtil class.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Forge
|
* @author Forge
|
||||||
* @version $Id: ComputerUtil.java 19179 2013-01-25 18:48:29Z Max mtg $
|
* @version $Id: ComputerUtil.java 19179 2013-01-25 18:48:29Z Max mtg $
|
||||||
*/
|
*/
|
||||||
@@ -75,7 +74,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* canAttackNextTurn.
|
* canAttackNextTurn.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -85,7 +84,7 @@ public class ComputerUtilCombat {
|
|||||||
return Iterables.any(defenders, new Predicate<GameEntity>() {
|
return Iterables.any(defenders, new Predicate<GameEntity>() {
|
||||||
@Override public boolean apply(final GameEntity input) {
|
@Override public boolean apply(final GameEntity input) {
|
||||||
return ComputerUtilCombat.canAttackNextTurn(attacker, input);
|
return ComputerUtilCombat.canAttackNextTurn(attacker, input);
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
} // canAttackNextTurn(Card)
|
} // canAttackNextTurn(Card)
|
||||||
|
|
||||||
@@ -93,7 +92,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* canAttackNextTurn.
|
* canAttackNextTurn.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param atacker
|
* @param atacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param defender
|
||||||
@@ -120,14 +119,18 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The creature won't untap next turn
|
// The creature won't untap next turn
|
||||||
return !atacker.isTapped() || Untap.canUntap(atacker);
|
if (atacker.isTapped() && !Untap.canUntap(atacker)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
} // canAttackNextTurn(Card, GameEntity)
|
} // canAttackNextTurn(Card, GameEntity)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* getTotalFirstStrikeBlockPower.
|
* getTotalFirstStrikeBlockPower.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param player
|
* @param player
|
||||||
@@ -147,14 +150,14 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
return ComputerUtilCombat.totalDamageOfBlockers(attacker, list);
|
return ComputerUtilCombat.totalDamageOfBlockers(attacker, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// This function takes Doran and Double Strike into account
|
// This function takes Doran and Double Strike into account
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* getAttack.
|
* getAttack.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param c
|
* @param c
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
@@ -168,14 +171,14 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Returns the damage an unblocked attacker would deal
|
// Returns the damage an unblocked attacker would deal
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* damageIfUnblocked.
|
* damageIfUnblocked.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param attacked
|
* @param attacked
|
||||||
@@ -191,9 +194,11 @@ public class ComputerUtilCombat {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ask ReplacementDamage directly
|
if (!attacked.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) {
|
||||||
if (isCombatDamagePrevented(attacker, attacked, damage)) {
|
// ask ReplacementDamage directly
|
||||||
return 0;
|
if (isCombatDamagePrevented(attacker, attacked, damage)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
damage += ComputerUtilCombat.predictPowerBonusOfAttacker(attacker, null, combat, withoutAbilities);
|
damage += ComputerUtilCombat.predictPowerBonusOfAttacker(attacker, null, combat, withoutAbilities);
|
||||||
@@ -211,7 +216,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* poisonIfUnblocked.
|
* poisonIfUnblocked.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param attacked
|
* @param attacked
|
||||||
@@ -240,7 +245,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* sumDamageIfUnblocked.
|
* sumDamageIfUnblocked.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attackers
|
* @param attackers
|
||||||
* @param attacked
|
* @param attacked
|
||||||
* a {@link forge.game.player.Player} object.
|
* a {@link forge.game.player.Player} object.
|
||||||
@@ -259,7 +264,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* sumPoisonIfUnblocked.
|
* sumPoisonIfUnblocked.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attackers
|
* @param attackers
|
||||||
* @param attacked
|
* @param attacked
|
||||||
* a {@link forge.game.player.Player} object.
|
* a {@link forge.game.player.Player} object.
|
||||||
@@ -278,7 +283,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* lifeThatWouldRemain.
|
* lifeThatWouldRemain.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param combat
|
* @param combat
|
||||||
* a {@link forge.game.combat.Combat} object.
|
* a {@link forge.game.combat.Combat} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
@@ -320,7 +325,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* resultingPoison.
|
* resultingPoison.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param combat
|
* @param combat
|
||||||
* a {@link forge.game.combat.Combat} object.
|
* a {@link forge.game.combat.Combat} object.
|
||||||
* @return a int.
|
* @return a int.
|
||||||
@@ -360,12 +365,12 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
return ai.getPoisonCounters() + poison;
|
return ai.getPoisonCounters() + poison;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Card> getLifeThreateningCommanders(final Player ai, final Combat combat) {
|
public static List<Card> getLifeThreateningCommanders(final Player ai, final Combat combat) {
|
||||||
List<Card> res = Lists.newArrayList();
|
List<Card> res = Lists.newArrayList();
|
||||||
for (Card c : combat.getAttackers()) {
|
for (Card c : combat.getAttackers()) {
|
||||||
if (c.isCommander()) {
|
if (c.isCommander()) {
|
||||||
int currentCommanderDamage = ai.getCommanderDamage(c);
|
int currentCommanderDamage = ai.getCommanderDamage(c);
|
||||||
if (damageIfUnblocked(c, ai, combat, false) + currentCommanderDamage >= 21) {
|
if (damageIfUnblocked(c, ai, combat, false) + currentCommanderDamage >= 21) {
|
||||||
res.add(c);
|
res.add(c);
|
||||||
}
|
}
|
||||||
@@ -379,7 +384,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* lifeInDanger.
|
* lifeInDanger.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param combat
|
* @param combat
|
||||||
* a {@link forge.game.combat.Combat} object.
|
* a {@link forge.game.combat.Combat} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -438,7 +443,7 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
int threshold = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD));
|
int threshold = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD));
|
||||||
int maxTreshold = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_MAX_THRESHOLD)) - threshold;
|
int maxTreshold = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_MAX_THRESHOLD)) - threshold;
|
||||||
|
|
||||||
int chance = MyRandom.getRandom().nextInt(80) + 5;
|
int chance = MyRandom.getRandom().nextInt(80) + 5;
|
||||||
while (maxTreshold > 0) {
|
while (maxTreshold > 0) {
|
||||||
if (MyRandom.getRandom().nextInt(100) < chance) {
|
if (MyRandom.getRandom().nextInt(100) < chance) {
|
||||||
@@ -460,7 +465,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* wouldLoseLife.
|
* wouldLoseLife.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param combat
|
* @param combat
|
||||||
* a {@link forge.game.combat.Combat} object.
|
* a {@link forge.game.combat.Combat} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -475,7 +480,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* lifeInSeriousDanger.
|
* lifeInSeriousDanger.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param combat
|
* @param combat
|
||||||
* a {@link forge.game.combat.Combat} object.
|
* a {@link forge.game.combat.Combat} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -516,7 +521,7 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
return (ComputerUtilCombat.resultingPoison(ai, combat) > 9);
|
return (ComputerUtilCombat.resultingPoison(ai, combat) > 9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// This calculates the amount of damage a blockgang can deal to the attacker
|
// This calculates the amount of damage a blockgang can deal to the attacker
|
||||||
// (first strike not supported)
|
// (first strike not supported)
|
||||||
@@ -524,7 +529,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* totalDamageOfBlockers.
|
* totalDamageOfBlockers.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defenders
|
* @param defenders
|
||||||
@@ -550,7 +555,7 @@ public class ComputerUtilCombat {
|
|||||||
*/
|
*/
|
||||||
public static int totalFirstStrikeDamageOfBlockers(final Card attacker, final List<Card> defenders) {
|
public static int totalFirstStrikeDamageOfBlockers(final Card attacker, final List<Card> defenders) {
|
||||||
int damage = 0;
|
int damage = 0;
|
||||||
|
|
||||||
if (attacker.isEquippedBy("Godsend") && !defenders.isEmpty()) {
|
if (attacker.isEquippedBy("Godsend") && !defenders.isEmpty()) {
|
||||||
defenders.remove(0);
|
defenders.remove(0);
|
||||||
}
|
}
|
||||||
@@ -567,7 +572,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* dealsDamageAsBlocker.
|
* dealsDamageAsBlocker.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param defender
|
||||||
@@ -633,7 +638,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* totalShieldDamage.
|
* totalShieldDamage.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defenders
|
* @param defenders
|
||||||
@@ -656,7 +661,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* shieldDamage.
|
* shieldDamage.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param blocker
|
* @param blocker
|
||||||
@@ -696,8 +701,8 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* combatantWouldBeDestroyed.
|
* combatantWouldBeDestroyed.
|
||||||
* </p>
|
* </p>
|
||||||
* @param ai
|
* @param ai
|
||||||
*
|
*
|
||||||
* @param combatant
|
* @param combatant
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -718,8 +723,8 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* attackerWouldBeDestroyed.
|
* attackerWouldBeDestroyed.
|
||||||
* </p>
|
* </p>
|
||||||
* @param ai
|
* @param ai
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -751,7 +756,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* combatTriggerWillTrigger.
|
* combatTriggerWillTrigger.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param defender
|
||||||
@@ -764,10 +769,6 @@ public class ComputerUtilCombat {
|
|||||||
*/
|
*/
|
||||||
public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger,
|
public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger,
|
||||||
Combat combat) {
|
Combat combat) {
|
||||||
return combatTriggerWillTrigger(attacker, defender, trigger, combat, null);
|
|
||||||
}
|
|
||||||
public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger,
|
|
||||||
Combat combat, final List<Card> plannedAttackers) {
|
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
final Map<String, String> trigParams = trigger.getMapParams();
|
final Map<String, String> trigParams = trigger.getMapParams();
|
||||||
boolean willTrigger = false;
|
boolean willTrigger = false;
|
||||||
@@ -814,9 +815,6 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("Alone") && plannedAttackers != null && plannedAttackers.size() != 1) {
|
|
||||||
return false; // won't trigger since the AI is planning to attack with more than one creature
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// defender == null means unblocked
|
// defender == null means unblocked
|
||||||
@@ -878,19 +876,20 @@ public class ComputerUtilCombat {
|
|||||||
} else if (mode == TriggerType.DamageDone) {
|
} else if (mode == TriggerType.DamageDone) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidSource")) {
|
if (trigParams.containsKey("ValidSource")) {
|
||||||
if (!(CardTraitBase.matchesValid(defender, trigParams.get("ValidSource").split(","), source)
|
if (CardTraitBase.matchesValid(defender, trigParams.get("ValidSource").split(","), source)
|
||||||
&& defender.getNetCombatDamage() > 0
|
&& defender.getNetCombatDamage() > 0
|
||||||
&& (!trigParams.containsKey("ValidTarget")
|
&& (!trigParams.containsKey("ValidTarget")
|
||||||
|| CardTraitBase.matchesValid(attacker, trigParams.get("ValidTarget").split(","), source)))) {
|
|| CardTraitBase.matchesValid(attacker, trigParams.get("ValidTarget").split(","), source))) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
if (!(CardTraitBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), source)
|
if (CardTraitBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), source)
|
||||||
&& attacker.getNetCombatDamage() > 0
|
&& attacker.getNetCombatDamage() > 0
|
||||||
&& (!trigParams.containsKey("ValidTarget")
|
&& (!trigParams.containsKey("ValidTarget")
|
||||||
|| CardTraitBase.matchesValid(defender, trigParams.get("ValidTarget").split(","), source)))) {
|
|| CardTraitBase.matchesValid(defender, trigParams.get("ValidTarget").split(","), source))) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return willTrigger;
|
return willTrigger;
|
||||||
@@ -902,7 +901,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* predictPowerBonusOfBlocker.
|
* predictPowerBonusOfBlocker.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param blocker
|
* @param blocker
|
||||||
@@ -918,13 +917,13 @@ public class ComputerUtilCombat {
|
|||||||
if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) {
|
if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) {
|
||||||
power -= attacker.getAmountOfKeyword("Flanking");
|
power -= attacker.getAmountOfKeyword("Flanking");
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
// Serene Master switches power with attacker
|
// Serene Master switches power with attacker
|
||||||
if (blocker.getName().equals("Serene Master")) {
|
if (blocker.getName().equals("Serene Master")) {
|
||||||
power += attacker.getNetPower() - blocker.getNetPower();
|
power += attacker.getNetPower() - blocker.getNetPower();
|
||||||
} else if (blocker.getName().equals("Shape Stealer")) {
|
} else if (blocker.getName().equals("Shape Stealer")) {
|
||||||
power += attacker.getNetPower() - blocker.getNetPower();
|
power += attacker.getNetPower() - blocker.getNetPower();
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the attacker has first strike and wither the blocker will deal
|
// if the attacker has first strike and wither the blocker will deal
|
||||||
// less damage than expected
|
// less damage than expected
|
||||||
@@ -964,7 +963,7 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final FCollection<Trigger> theTriggers = new FCollection<>();
|
final FCollection<Trigger> theTriggers = new FCollection<Trigger>();
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
}
|
}
|
||||||
@@ -1030,7 +1029,7 @@ public class ComputerUtilCombat {
|
|||||||
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
|
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1042,7 +1041,7 @@ public class ComputerUtilCombat {
|
|||||||
if (!ability.hasParam("NumAtt")) {
|
if (!ability.hasParam("NumAtt")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCost.canPayCost(ability, blocker.getController())) {
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController())) {
|
||||||
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||||
if (pBonus > 0) {
|
if (pBonus > 0) {
|
||||||
@@ -1070,7 +1069,7 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return power;
|
return power;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1080,7 +1079,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* predictToughnessBonusOfBlocker.
|
* predictToughnessBonusOfBlocker.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param blocker
|
* @param blocker
|
||||||
@@ -1093,13 +1092,13 @@ public class ComputerUtilCombat {
|
|||||||
if (attacker.hasKeyword(Keyword.FLANKING) && !blocker.hasKeyword(Keyword.FLANKING)) {
|
if (attacker.hasKeyword(Keyword.FLANKING) && !blocker.hasKeyword(Keyword.FLANKING)) {
|
||||||
toughness -= attacker.getAmountOfKeyword(Keyword.FLANKING);
|
toughness -= attacker.getAmountOfKeyword(Keyword.FLANKING);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blocker.getName().equals("Shape Stealer")) {
|
if (blocker.getName().equals("Shape Stealer")) {
|
||||||
toughness += attacker.getNetToughness() - blocker.getNetToughness();
|
toughness += attacker.getNetToughness() - blocker.getNetToughness();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
final FCollection<Trigger> theTriggers = new FCollection<>();
|
final FCollection<Trigger> theTriggers = new FCollection<Trigger>();
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
}
|
}
|
||||||
@@ -1218,7 +1217,7 @@ public class ComputerUtilCombat {
|
|||||||
if (!ability.hasParam("NumDef")) {
|
if (!ability.hasParam("NumDef")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCost.canPayCost(ability, blocker.getController())) {
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController())) {
|
||||||
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
||||||
if (tBonus > 0) {
|
if (tBonus > 0) {
|
||||||
@@ -1255,7 +1254,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* predictPowerBonusOfAttacker.
|
* predictPowerBonusOfAttacker.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param blocker
|
* @param blocker
|
||||||
@@ -1283,7 +1282,7 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
final FCollection<Trigger> theTriggers = new FCollection<>();
|
final FCollection<Trigger> theTriggers = new FCollection<Trigger>();
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
}
|
}
|
||||||
@@ -1408,7 +1407,7 @@ public class ComputerUtilCombat {
|
|||||||
if (att.matches("[0-9][0-9]?") || att.matches("-" + "[0-9][0-9]?")) {
|
if (att.matches("[0-9][0-9]?") || att.matches("-" + "[0-9][0-9]?")) {
|
||||||
power += Integer.parseInt(att);
|
power += Integer.parseInt(att);
|
||||||
} else {
|
} else {
|
||||||
String bonus = source.getSVar(att);
|
String bonus = new String(source.getSVar(att));
|
||||||
if (bonus.contains("TriggerCount$NumBlockers")) {
|
if (bonus.contains("TriggerCount$NumBlockers")) {
|
||||||
bonus = TextUtil.fastReplace(bonus, "TriggerCount$NumBlockers", "Number$1");
|
bonus = TextUtil.fastReplace(bonus, "TriggerCount$NumBlockers", "Number$1");
|
||||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||||
@@ -1440,7 +1439,7 @@ public class ComputerUtilCombat {
|
|||||||
if (!ability.hasParam("NumAtt")) {
|
if (!ability.hasParam("NumAtt")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
||||||
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||||
if (pBonus > 0) {
|
if (pBonus > 0) {
|
||||||
@@ -1456,7 +1455,7 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ability.hasParam("Adapt") && attacker.getCounters(CounterType.P1P1) > 0) {
|
if (ability.hasParam("Adapt") && blocker != null && blocker.getCounters(CounterType.P1P1) > 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1477,7 +1476,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* predictToughnessBonusOfAttacker.
|
* predictToughnessBonusOfAttacker.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param blocker
|
* @param blocker
|
||||||
@@ -1504,7 +1503,7 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
final FCollection<Trigger> theTriggers = new FCollection<>();
|
final FCollection<Trigger> theTriggers = new FCollection<Trigger>();
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
}
|
}
|
||||||
@@ -1649,7 +1648,7 @@ public class ComputerUtilCombat {
|
|||||||
if (def.matches("[0-9][0-9]?") || def.matches("-" + "[0-9][0-9]?")) {
|
if (def.matches("[0-9][0-9]?") || def.matches("-" + "[0-9][0-9]?")) {
|
||||||
toughness += Integer.parseInt(def);
|
toughness += Integer.parseInt(def);
|
||||||
} else {
|
} else {
|
||||||
String bonus = source.getSVar(def);
|
String bonus = new String(source.getSVar(def));
|
||||||
if (bonus.contains("TriggerCount$NumBlockers")) {
|
if (bonus.contains("TriggerCount$NumBlockers")) {
|
||||||
bonus = TextUtil.fastReplace(bonus, "TriggerCount$NumBlockers", "Number$1");
|
bonus = TextUtil.fastReplace(bonus, "TriggerCount$NumBlockers", "Number$1");
|
||||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||||
@@ -1693,7 +1692,7 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ability.hasParam("Adapt") && attacker.getCounters(CounterType.P1P1) > 0) {
|
if (ability.hasParam("Adapt") && blocker.getCounters(CounterType.P1P1) > 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1707,7 +1706,7 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
return toughness;
|
return toughness;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check whether the attacker will be destroyed by triggered abilities before First Strike damage
|
// check whether the attacker will be destroyed by triggered abilities before First Strike damage
|
||||||
public static boolean canDestroyAttackerBeforeFirstStrike(final Card attacker, final Card blocker, final Combat combat,
|
public static boolean canDestroyAttackerBeforeFirstStrike(final Card attacker, final Card blocker, final Combat combat,
|
||||||
final boolean withoutAbilities) {
|
final boolean withoutAbilities) {
|
||||||
@@ -1717,7 +1716,7 @@ public class ComputerUtilCombat {
|
|||||||
if (attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || ComputerUtil.canRegenerate(attacker.getController(), attacker)) {
|
if (attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || ComputerUtil.canRegenerate(attacker.getController(), attacker)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check triggers that deal damage or shrink the attacker
|
//Check triggers that deal damage or shrink the attacker
|
||||||
if (ComputerUtilCombat.getDamageToKill(attacker)
|
if (ComputerUtilCombat.getDamageToKill(attacker)
|
||||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities) <= 0) {
|
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities) <= 0) {
|
||||||
@@ -1725,7 +1724,7 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check Destroy triggers (Cockatrice and friends)
|
// check Destroy triggers (Cockatrice and friends)
|
||||||
final FCollection<Trigger> theTriggers = new FCollection<>();
|
final FCollection<Trigger> theTriggers = new FCollection<Trigger>();
|
||||||
for (Card card : attacker.getGame().getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : attacker.getGame().getCardsIn(ZoneType.Battlefield)) {
|
||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
}
|
}
|
||||||
@@ -1771,8 +1770,8 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* attackerCantBeDestroyedNow.
|
* attackerCantBeDestroyedNow.
|
||||||
* </p>
|
* </p>
|
||||||
* @param ai
|
* @param ai
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -1801,8 +1800,8 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* canDestroyAttacker.
|
* canDestroyAttacker.
|
||||||
* </p>
|
* </p>
|
||||||
* @param ai
|
* @param ai
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param blocker
|
* @param blocker
|
||||||
@@ -1886,8 +1885,10 @@ public class ComputerUtilCombat {
|
|||||||
// consider Damage Prevention/Replacement
|
// consider Damage Prevention/Replacement
|
||||||
defenderDamage = predictDamageTo(attacker, defenderDamage, possibleAttackerPrevention, blocker, true);
|
defenderDamage = predictDamageTo(attacker, defenderDamage, possibleAttackerPrevention, blocker, true);
|
||||||
attackerDamage = predictDamageTo(blocker, attackerDamage, possibleDefenderPrevention, attacker, true);
|
attackerDamage = predictDamageTo(blocker, attackerDamage, possibleDefenderPrevention, attacker, true);
|
||||||
if (defenderDamage > 0 && isCombatDamagePrevented(blocker, attacker, defenderDamage)) {
|
if (!attacker.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) {
|
||||||
return false;
|
if (defenderDamage > 0 && isCombatDamagePrevented(blocker, attacker, defenderDamage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final int defenderLife = ComputerUtilCombat.getDamageToKill(blocker)
|
final int defenderLife = ComputerUtilCombat.getDamageToKill(blocker)
|
||||||
@@ -1940,7 +1941,7 @@ public class ComputerUtilCombat {
|
|||||||
return defenderDamage >= attackerLife;
|
return defenderDamage >= attackerLife;
|
||||||
|
|
||||||
} // defender no double strike
|
} // defender no double strike
|
||||||
return false;// should never arrive here
|
return false; // should never arrive here
|
||||||
} // canDestroyAttacker
|
} // canDestroyAttacker
|
||||||
|
|
||||||
// For AI safety measures like Regeneration
|
// For AI safety measures like Regeneration
|
||||||
@@ -1948,8 +1949,8 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* blockerWouldBeDestroyed.
|
* blockerWouldBeDestroyed.
|
||||||
* </p>
|
* </p>
|
||||||
* @param ai
|
* @param ai
|
||||||
*
|
*
|
||||||
* @param blocker
|
* @param blocker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -1973,7 +1974,7 @@ public class ComputerUtilCombat {
|
|||||||
if (attacker.isEquippedBy("Godsend")) {
|
if (attacker.isEquippedBy("Godsend")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attacker.getName().equals("Elven Warhounds")) {
|
if (attacker.getName().equals("Elven Warhounds")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1991,7 +1992,7 @@ public class ComputerUtilCombat {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} // flanking
|
} // flanking
|
||||||
|
|
||||||
if (blocker.hasKeyword(Keyword.INDESTRUCTIBLE) || dontTestRegen
|
if (blocker.hasKeyword(Keyword.INDESTRUCTIBLE) || dontTestRegen
|
||||||
|| ComputerUtil.canRegenerate(blocker.getController(), blocker)) {
|
|| ComputerUtil.canRegenerate(blocker.getController(), blocker)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2003,7 +2004,7 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Game game = blocker.getGame();
|
final Game game = blocker.getGame();
|
||||||
final FCollection<Trigger> theTriggers = new FCollection<>();
|
final FCollection<Trigger> theTriggers = new FCollection<Trigger>();
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
theTriggers.addAll(card.getTriggers());
|
theTriggers.addAll(card.getTriggers());
|
||||||
}
|
}
|
||||||
@@ -2042,17 +2043,17 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// can the attacker destroy this blocker?
|
// can the attacker destroy this blocker?
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* canDestroyBlocker.
|
* canDestroyBlocker.
|
||||||
* </p>
|
* </p>
|
||||||
* @param ai
|
* @param ai
|
||||||
*
|
*
|
||||||
* @param blocker
|
* @param blocker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param attacker
|
* @param attacker
|
||||||
@@ -2090,7 +2091,7 @@ public class ComputerUtilCombat {
|
|||||||
if (canDestroyAttackerBeforeFirstStrike(attacker, blocker, combat, withoutAbilities)) {
|
if (canDestroyAttackerBeforeFirstStrike(attacker, blocker, combat, withoutAbilities)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int defenderDamage;
|
int defenderDamage;
|
||||||
int attackerDamage;
|
int attackerDamage;
|
||||||
if (blocker.toughnessAssignsDamage()) {
|
if (blocker.toughnessAssignsDamage()) {
|
||||||
@@ -2120,11 +2121,13 @@ public class ComputerUtilCombat {
|
|||||||
attackerDamage = predictDamageTo(blocker, attackerDamage, possibleDefenderPrevention, attacker, true);
|
attackerDamage = predictDamageTo(blocker, attackerDamage, possibleDefenderPrevention, attacker, true);
|
||||||
|
|
||||||
// Damage prevention might come from a static effect
|
// Damage prevention might come from a static effect
|
||||||
if (isCombatDamagePrevented(attacker, blocker, attackerDamage)) {
|
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) {
|
||||||
attackerDamage = 0;
|
if (isCombatDamagePrevented(attacker, blocker, attackerDamage)) {
|
||||||
}
|
attackerDamage = 0;
|
||||||
if (isCombatDamagePrevented(blocker, attacker, defenderDamage)) {
|
}
|
||||||
defenderDamage = 0;
|
if (isCombatDamagePrevented(blocker, attacker, defenderDamage)) {
|
||||||
|
defenderDamage = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (combat != null) {
|
if (combat != null) {
|
||||||
@@ -2185,7 +2188,7 @@ public class ComputerUtilCombat {
|
|||||||
return attackerDamage >= defenderLife;
|
return attackerDamage >= defenderLife;
|
||||||
|
|
||||||
} // attacker no double strike
|
} // attacker no double strike
|
||||||
return false;// should never arrive here
|
return false; // should never arrive here
|
||||||
} // canDestroyBlocker
|
} // canDestroyBlocker
|
||||||
|
|
||||||
|
|
||||||
@@ -2193,54 +2196,54 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* distributeAIDamage.
|
* distributeAIDamage.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param attacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param block
|
* @param block
|
||||||
* @param dmgCanDeal
|
* @param dmgCanDeal
|
||||||
* a int.
|
* a int.
|
||||||
* @param defender
|
* @param defender
|
||||||
* @param overrideOrder overriding combatant order
|
* @param overrideOrder overriding combatant order
|
||||||
*/
|
*/
|
||||||
public static Map<Card, Integer> distributeAIDamage(final Card attacker, final CardCollectionView block, int dmgCanDeal, GameEntity defender, boolean overrideOrder) {
|
public static Map<Card, Integer> distributeAIDamage(final Card attacker, final CardCollectionView block, int dmgCanDeal, GameEntity defender, boolean overrideOrder) {
|
||||||
// TODO: Distribute defensive Damage (AI controls how damage is dealt to own cards) for Banding and Defensive Formation
|
// TODO: Distribute defensive Damage (AI controls how damage is dealt to own cards) for Banding and Defensive Formation
|
||||||
Map<Card, Integer> damageMap = Maps.newHashMap();
|
Map<Card, Integer> damageMap = Maps.newHashMap();
|
||||||
|
|
||||||
boolean isAttacking = defender != null;
|
boolean isAttacking = defender != null;
|
||||||
|
|
||||||
if (isAttacking && (attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
if (isAttacking && (attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
||||||
|| attacker.hasKeyword("CARDNAME assigns its combat damage as though it weren't blocked."))) {
|
|| attacker.hasKeyword("CARDNAME assigns its combat damage as though it weren't blocked."))) {
|
||||||
damageMap.put(null, dmgCanDeal);
|
damageMap.put(null, dmgCanDeal);
|
||||||
return damageMap;
|
return damageMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean hasTrample = attacker.hasKeyword(Keyword.TRAMPLE);
|
final boolean hasTrample = attacker.hasKeyword(Keyword.TRAMPLE);
|
||||||
|
|
||||||
if (block.size() == 1) {
|
if (block.size() == 1) {
|
||||||
final Card blocker = block.getFirst();
|
final Card blocker = block.getFirst();
|
||||||
|
|
||||||
// trample
|
// trample
|
||||||
if (hasTrample) {
|
if (hasTrample) {
|
||||||
|
|
||||||
int dmgToKill = ComputerUtilCombat.getEnoughDamageToKill(blocker, dmgCanDeal, attacker, true);
|
int dmgToKill = ComputerUtilCombat.getEnoughDamageToKill(blocker, dmgCanDeal, attacker, true);
|
||||||
|
|
||||||
if (dmgCanDeal < dmgToKill) {
|
if (dmgCanDeal < dmgToKill) {
|
||||||
dmgToKill = Math.min(blocker.getLethalDamage(), dmgCanDeal);
|
dmgToKill = Math.min(blocker.getLethalDamage(), dmgCanDeal);
|
||||||
} else {
|
} else {
|
||||||
dmgToKill = Math.max(blocker.getLethalDamage(), dmgToKill);
|
dmgToKill = Math.max(blocker.getLethalDamage(), dmgToKill);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isAttacking) { // no entity to deliver damage via trample
|
if (!isAttacking) { // no entity to deliver damage via trample
|
||||||
dmgToKill = dmgCanDeal;
|
dmgToKill = dmgCanDeal;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int remainingDmg = dmgCanDeal - dmgToKill;
|
final int remainingDmg = dmgCanDeal - dmgToKill;
|
||||||
|
|
||||||
// If Extra trample damage, assign to defending player/planeswalker (when there is one)
|
// If Extra trample damage, assign to defending player/planeswalker (when there is one)
|
||||||
if (remainingDmg > 0) {
|
if (remainingDmg > 0) {
|
||||||
damageMap.put(null, remainingDmg);
|
damageMap.put(null, remainingDmg);
|
||||||
}
|
}
|
||||||
|
|
||||||
damageMap.put(blocker, dmgToKill);
|
damageMap.put(blocker, dmgToKill);
|
||||||
} else {
|
} else {
|
||||||
damageMap.put(blocker, dmgCanDeal);
|
damageMap.put(blocker, dmgCanDeal);
|
||||||
@@ -2266,8 +2269,8 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // for
|
} // for
|
||||||
|
|
||||||
if (dmgCanDeal > 0 ) { // if any damage left undistributed,
|
if (dmgCanDeal > 0 ) { // if any damage left undistributed,
|
||||||
if (hasTrample && isAttacking) // if you have trample, deal damage to defending entity
|
if (hasTrample && isAttacking) // if you have trample, deal damage to defending entity
|
||||||
damageMap.put(null, dmgCanDeal);
|
damageMap.put(null, dmgCanDeal);
|
||||||
else if ( lastBlocker != null ) { // otherwise flush it into last blocker
|
else if ( lastBlocker != null ) { // otherwise flush it into last blocker
|
||||||
@@ -2277,14 +2280,14 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
return damageMap;
|
return damageMap;
|
||||||
} // setAssignedDamage()
|
} // setAssignedDamage()
|
||||||
|
|
||||||
|
|
||||||
// how much damage is enough to kill the creature (for AI)
|
// how much damage is enough to kill the creature (for AI)
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* getEnoughDamageToKill.
|
* getEnoughDamageToKill.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param maxDamage
|
* @param maxDamage
|
||||||
* a int.
|
* a int.
|
||||||
* @param source
|
* @param source
|
||||||
@@ -2301,7 +2304,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* getEnoughDamageToKill.
|
* getEnoughDamageToKill.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param maxDamage
|
* @param maxDamage
|
||||||
* a int.
|
* a int.
|
||||||
* @param source
|
* @param source
|
||||||
@@ -2352,7 +2355,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* getKillDamage.
|
* getKillDamage.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public final static int getDamageToKill(final Card c) {
|
public final static int getDamageToKill(final Card c) {
|
||||||
@@ -2365,13 +2368,13 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
return killDamage;
|
return killDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* predictDamage.
|
* predictDamage.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param damage
|
* @param damage
|
||||||
* a int.
|
* a int.
|
||||||
* @param source
|
* @param source
|
||||||
@@ -2392,7 +2395,7 @@ public class ComputerUtilCombat {
|
|||||||
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
|
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
|
||||||
for (final ReplacementEffect re : ca.getReplacementEffects()) {
|
for (final ReplacementEffect re : ca.getReplacementEffects()) {
|
||||||
Map<String, String> params = re.getMapParams();
|
Map<String, String> params = re.getMapParams();
|
||||||
if (!re.getMode().equals(ReplacementType.DamageDone) || !params.containsKey("PreventionEffect")) {
|
if (!"DamageDone".equals(params.get("Event")) || !params.containsKey("PreventionEffect")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Immortal Coil prevents the damage but has a similar negative effect
|
// Immortal Coil prevents the damage but has a similar negative effect
|
||||||
@@ -2427,12 +2430,12 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
return restDamage;
|
return restDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* predictDamage.
|
* predictDamage.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param damage
|
* @param damage
|
||||||
* a int.
|
* a int.
|
||||||
* @param source
|
* @param source
|
||||||
@@ -2460,7 +2463,7 @@ public class ComputerUtilCombat {
|
|||||||
* <p>
|
* <p>
|
||||||
* predictDamage.
|
* predictDamage.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param damage
|
* @param damage
|
||||||
* a int.
|
* a int.
|
||||||
* @param possiblePrevention
|
* @param possiblePrevention
|
||||||
@@ -2472,25 +2475,25 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public final static int predictDamageTo(final Card target, final int damage, final int possiblePrevention, final Card source, final boolean isCombat) {
|
public final static int predictDamageTo(final Card target, final int damage, final int possiblePrevention, final Card source, final boolean isCombat) {
|
||||||
|
|
||||||
int restDamage = damage;
|
int restDamage = damage;
|
||||||
|
|
||||||
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
|
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
|
||||||
restDamage = target.staticDamagePrevention(restDamage, possiblePrevention, source, isCombat);
|
restDamage = target.staticDamagePrevention(restDamage, possiblePrevention, source, isCombat);
|
||||||
|
|
||||||
return restDamage;
|
return restDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final static boolean dealsFirstStrikeDamage(final Card combatant, final boolean withoutAbilities, final Combat combat) {
|
public final static boolean dealsFirstStrikeDamage(final Card combatant, final boolean withoutAbilities, final Combat combat) {
|
||||||
|
|
||||||
if (combatant.hasKeyword(Keyword.DOUBLE_STRIKE) || combatant.hasKeyword(Keyword.FIRST_STRIKE)) {
|
if (combatant.hasKeyword(Keyword.DOUBLE_STRIKE) || combatant.hasKeyword(Keyword.FIRST_STRIKE)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!withoutAbilities) {
|
if (!withoutAbilities) {
|
||||||
return canGainKeyword(combatant, Lists.newArrayList("Double Strike", "First Strike"), combat);
|
return canGainKeyword(combatant, Lists.newArrayList("Double Strike", "First Strike"), combat);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2550,10 +2553,10 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms into alternate state if possible
|
* Transforms into alternate state if possible
|
||||||
* @param original original creature
|
* @param original original creature
|
||||||
@@ -2574,25 +2577,24 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isCombatDamagePrevented(final Card attacker, final GameEntity target, final int damage) {
|
public static boolean isCombatDamagePrevented(final Card attacker, final GameEntity target, final int damage) {
|
||||||
if (!attacker.canDamagePrevented(true)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
|
|
||||||
// first try to replace the damage
|
// first try to replace the damage
|
||||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(target);
|
final Map<String, Object> repParams = Maps.newHashMap();
|
||||||
repParams.put(AbilityKey.DamageSource, attacker);
|
repParams.put("Event", "DamageDone");
|
||||||
repParams.put(AbilityKey.DamageAmount, damage);
|
repParams.put("Affected", target);
|
||||||
repParams.put(AbilityKey.IsCombat, true);
|
repParams.put("DamageSource", attacker);
|
||||||
repParams.put(AbilityKey.Prevention, true);
|
repParams.put("DamageAmount", damage);
|
||||||
|
repParams.put("IsCombat", true);
|
||||||
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(
|
repParams.put("Prevention", true);
|
||||||
ReplacementType.DamageDone, repParams, ReplacementLayer.Other);
|
// repParams.put("PreventMap", preventMap);
|
||||||
|
|
||||||
|
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(repParams,
|
||||||
|
ReplacementLayer.None);
|
||||||
|
|
||||||
return !list.isEmpty();
|
return !list.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean attackerHasThreateningAfflict(Card attacker, Player aiDefender) {
|
public static boolean attackerHasThreateningAfflict(Card attacker, Player aiDefender) {
|
||||||
// TODO: expand this to account for more complex situations like the Wildfire Eternal unblocked trigger
|
// TODO: expand this to account for more complex situations like the Wildfire Eternal unblocked trigger
|
||||||
int afflictDmg = attacker.getKeywordMagnitude(Keyword.AFFLICT);
|
int afflictDmg = attacker.getKeywordMagnitude(Keyword.AFFLICT);
|
||||||
|
|||||||
@@ -110,15 +110,6 @@ public class ComputerUtilCost {
|
|||||||
&& !source.hasKeyword(Keyword.UNDYING)) {
|
&& !source.hasKeyword(Keyword.UNDYING)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (part instanceof CostRemoveAnyCounter) {
|
|
||||||
if (sa != null) {
|
|
||||||
final CostRemoveAnyCounter remCounter = (CostRemoveAnyCounter) part;
|
|
||||||
|
|
||||||
PaymentDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa).visit(remCounter);
|
|
||||||
return decision != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -424,7 +415,7 @@ public class ComputerUtilCost {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final int remainingLife = ai.getLife();
|
final int remainingLife = ai.getLife();
|
||||||
final int lifeCost = part.convertAmount();
|
final int lifeCost = ((CostPayLife) part).convertAmount();
|
||||||
if ((remainingLife - lifeCost) < 10) {
|
if ((remainingLife - lifeCost) < 10) {
|
||||||
return false; //Don't pay life if it would put AI under 10 life
|
return false; //Don't pay life if it would put AI under 10 life
|
||||||
} else if ((remainingLife / lifeCost) < 4) {
|
} else if ((remainingLife / lifeCost) < 4) {
|
||||||
@@ -468,7 +459,7 @@ public class ComputerUtilCost {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
extraManaNeeded += Integer.parseInt(parts[0]);
|
extraManaNeeded += Integer.parseInt(snem);
|
||||||
} catch (final NumberFormatException e) {
|
} catch (final NumberFormatException e) {
|
||||||
System.out.println("wrong SpellsNeedExtraMana SVar format on " + c);
|
System.out.println("wrong SpellsNeedExtraMana SVar format on " + c);
|
||||||
}
|
}
|
||||||
@@ -490,7 +481,7 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try not to lose Planeswalker if not threatened
|
// Try not to lose Planeswalker if not threatened
|
||||||
if (sa.isPwAbility()) {
|
if (sa.getRestrictions().isPwAbility()) {
|
||||||
for (final CostPart part : sa.getPayCosts().getCostParts()) {
|
for (final CostPart part : sa.getPayCosts().getCostParts()) {
|
||||||
if (part instanceof CostRemoveCounter) {
|
if (part instanceof CostRemoveCounter) {
|
||||||
if (part.convertAmount() != null && part.convertAmount() == sa.getHostCard().getCurrentLoyalty()) {
|
if (part.convertAmount() != null && part.convertAmount() == sa.getHostCard().getCurrentLoyalty()) {
|
||||||
@@ -552,7 +543,7 @@ public class ComputerUtilCost {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String aiLogic = sa.getParam("UnlessAI");
|
final String aiLogic = sa.getParam("UnlessAI");
|
||||||
boolean payForOwnOnly = "OnlyOwn".equals(aiLogic);
|
boolean payForOwnOnly = "OnlyOwn".equals(aiLogic);
|
||||||
boolean payOwner = sa.hasParam("UnlessAI") && aiLogic.startsWith("Defined");
|
boolean payOwner = sa.hasParam("UnlessAI") ? aiLogic.startsWith("Defined") : false;
|
||||||
boolean payNever = "Never".equals(aiLogic);
|
boolean payNever = "Never".equals(aiLogic);
|
||||||
boolean shockland = "Shockland".equals(aiLogic);
|
boolean shockland = "Shockland".equals(aiLogic);
|
||||||
boolean isMine = sa.getActivatingPlayer().equals(payer);
|
boolean isMine = sa.getActivatingPlayer().equals(payer);
|
||||||
@@ -594,11 +585,6 @@ public class ComputerUtilCost {
|
|||||||
if (c == null || c.isUntapped()) {
|
if (c == null || c.isUntapped()) {
|
||||||
return false;
|
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)) {
|
} else if ("MorePowerful".equals(aiLogic)) {
|
||||||
final int sourceCreatures = sa.getActivatingPlayer().getCreaturesInPlay().size();
|
final int sourceCreatures = sa.getActivatingPlayer().getCreaturesInPlay().size();
|
||||||
final int payerCreatures = payer.getCreaturesInPlay().size();
|
final int payerCreatures = payer.getCreaturesInPlay().size();
|
||||||
@@ -640,7 +626,7 @@ public class ComputerUtilCost {
|
|||||||
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
|
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
|
||||||
&& (!source.getName().equals("Perplex") || 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("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) {
|
public static Set<String> getAvailableManaColors(Player ai, Card additionalLand) {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.*;
|
import com.google.common.collect.*;
|
||||||
import forge.ai.ability.AnimateAi;
|
import forge.ai.ability.AnimateAi;
|
||||||
import forge.card.CardStateName;
|
|
||||||
import forge.card.ColorSet;
|
import forge.card.ColorSet;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.card.mana.ManaAtom;
|
import forge.card.mana.ManaAtom;
|
||||||
@@ -13,7 +12,6 @@ import forge.card.mana.ManaCostParser;
|
|||||||
import forge.card.mana.ManaCostShard;
|
import forge.card.mana.ManaCostShard;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameActionUtil;
|
import forge.game.GameActionUtil;
|
||||||
import forge.game.ability.AbilityKey;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
@@ -26,7 +24,6 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementType;
|
|
||||||
import forge.game.spellability.AbilityManaPart;
|
import forge.game.spellability.AbilityManaPart;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -68,8 +65,6 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
// Does not check if mana sources can be used right now, just checks for potential chance.
|
// 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) {
|
public static boolean hasEnoughManaSourcesToCast(final SpellAbility sa, final Player ai) {
|
||||||
if(ai == null || sa == null)
|
|
||||||
return false;
|
|
||||||
sa.setActivatingPlayer(ai);
|
sa.setActivatingPlayer(ai);
|
||||||
return payManaCost(sa, ai, true, 0, false);
|
return payManaCost(sa, ai, true, 0, false);
|
||||||
}
|
}
|
||||||
@@ -174,7 +169,8 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ability1.compareTo(ability2);
|
return ability1.compareTo(ability2);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return preOrder;
|
return preOrder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,17 +320,11 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
SpellAbility saPayment = chooseManaAbility(cost, sa, ai, toPay, saList, true);
|
SpellAbility saPayment = chooseManaAbility(cost, sa, ai, toPay, saList, true);
|
||||||
if (saPayment == null) {
|
if (saPayment == null) {
|
||||||
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
|
if (!toPay.isPhyrexian() || !ai.canPayLife(2)) {
|
||||||
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2)) {
|
|
||||||
break; // cannot pay
|
break; // cannot pay
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toPay.isPhyrexian()) {
|
cost.payPhyrexian();
|
||||||
cost.payPhyrexian();
|
|
||||||
} else if (lifeInsteadOfBlack) {
|
|
||||||
cost.decreaseShard(ManaCostShard.BLACK, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,10 +337,6 @@ public class ComputerUtilMana {
|
|||||||
payMultipleMana(cost, manaProduced, ai);
|
payMultipleMana(cost, manaProduced, ai);
|
||||||
|
|
||||||
// remove from available lists
|
// remove from available lists
|
||||||
/*
|
|
||||||
* Refactoring this code to sourcesForShards.values().removeIf((SpellAbility srcSa) -> srcSa.getHostCard().equals(saPayment.getHostCard()));
|
|
||||||
* causes Android build not to compile
|
|
||||||
* */
|
|
||||||
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
|
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
|
||||||
while (itSa.hasNext()) {
|
while (itSa.hasNext()) {
|
||||||
SpellAbility srcSa = itSa.next();
|
SpellAbility srcSa = itSa.next();
|
||||||
@@ -369,7 +355,7 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, boolean checkPlayable) {
|
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, boolean checkPlayable) {
|
||||||
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
|
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();
|
boolean purePhyrexian = cost.containsOnlyPhyrexianMana();
|
||||||
int testEnergyPool = ai.getCounters(CounterType.ENERGY);
|
int testEnergyPool = ai.getCounters(CounterType.ENERGY);
|
||||||
|
|
||||||
@@ -394,8 +380,6 @@ public class ComputerUtilMana {
|
|||||||
while (!cost.isPaid()) {
|
while (!cost.isPaid()) {
|
||||||
toPay = getNextShardToPay(cost);
|
toPay = getNextShardToPay(cost);
|
||||||
|
|
||||||
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
|
|
||||||
|
|
||||||
Collection<SpellAbility> saList = null;
|
Collection<SpellAbility> saList = null;
|
||||||
if (hasConverge &&
|
if (hasConverge &&
|
||||||
(toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) {
|
(toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) {
|
||||||
@@ -446,8 +430,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (saPayment == null) {
|
if (saPayment == null) {
|
||||||
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2)
|
if (!toPay.isPhyrexian() || !ai.canPayLife(2) || (ai.getLife() <= 2 && !ai.cantLoseForZeroOrLessLife())) {
|
||||||
|| (ai.getLife() <= 2 && !ai.cantLoseForZeroOrLessLife())) {
|
|
||||||
break; // cannot pay
|
break; // cannot pay
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,12 +445,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toPay.isPhyrexian()) {
|
cost.payPhyrexian();
|
||||||
cost.payPhyrexian();
|
|
||||||
} else if (lifeInsteadOfBlack) {
|
|
||||||
cost.decreaseShard(ManaCostShard.BLACK, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!test) {
|
if (!test) {
|
||||||
ai.payLife(2, sa.getHostCard());
|
ai.payLife(2, sa.getHostCard());
|
||||||
}
|
}
|
||||||
@@ -494,10 +472,6 @@ public class ComputerUtilMana {
|
|||||||
payMultipleMana(cost, manaProduced, ai);
|
payMultipleMana(cost, manaProduced, ai);
|
||||||
|
|
||||||
// remove from available lists
|
// remove from available lists
|
||||||
/*
|
|
||||||
* Refactoring this code to sourcesForShards.values().removeIf((SpellAbility srcSa) -> srcSa.getHostCard().equals(saPayment.getHostCard()));
|
|
||||||
* causes Android build not to compile
|
|
||||||
* */
|
|
||||||
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
|
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
|
||||||
while (itSa.hasNext()) {
|
while (itSa.hasNext()) {
|
||||||
SpellAbility srcSa = itSa.next();
|
SpellAbility srcSa = itSa.next();
|
||||||
@@ -528,10 +502,6 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
if (hasConverge) { // hack to prevent converge re-using sources
|
if (hasConverge) { // hack to prevent converge re-using sources
|
||||||
// remove from available lists
|
// remove from available lists
|
||||||
/*
|
|
||||||
* Refactoring this code to sourcesForShards.values().removeIf((SpellAbility srcSa) -> srcSa.getHostCard().equals(saPayment.getHostCard()));
|
|
||||||
* causes Android build not to compile
|
|
||||||
* */
|
|
||||||
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
|
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
|
||||||
while (itSa.hasNext()) {
|
while (itSa.hasNext()) {
|
||||||
SpellAbility srcSa = itSa.next();
|
SpellAbility srcSa = itSa.next();
|
||||||
@@ -895,12 +865,10 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
// For combat tricks, always obey mana reservation
|
// For combat tricks, always obey mana reservation
|
||||||
if (curPhase == PhaseType.COMBAT_DECLARE_BLOCKERS || curPhase == PhaseType.CLEANUP) {
|
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);
|
||||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
|
} else if (!(ai.getGame().getPhaseHandler().isPlayerTurn(ai)) && (curPhase == PhaseType.COMBAT_DECLARE_BLOCKERS || curPhase == PhaseType.CLEANUP)) {
|
||||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
|
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 {
|
} else {
|
||||||
if ((AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK)) ||
|
if ((AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK)) ||
|
||||||
(AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK))) {
|
(AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK))) {
|
||||||
@@ -1153,11 +1121,11 @@ public class ComputerUtilMana {
|
|||||||
// For Count$xPaid set PayX in the AFs then use that here
|
// For Count$xPaid set PayX in the AFs then use that here
|
||||||
// Else calculate it as appropriate.
|
// Else calculate it as appropriate.
|
||||||
final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
|
final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
|
||||||
if (!sa.getSVar(xSvar).isEmpty() || card.hasSVar(xSvar) || card.getState(CardStateName.Original).hasSVar(xSvar)) {
|
if (!sa.getSVar(xSvar).isEmpty() || card.hasSVar(xSvar)) {
|
||||||
if (xSvar.equals("PayX") && (card.hasSVar(xSvar) || card.getState(CardStateName.Original).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)
|
// X SVar may end up being an empty string when copying a spell with no cost (e.g. Jhoira Avatar)
|
||||||
String xValue = card.hasSVar(xSvar) ? card.getSVar(xSvar) : card.getState(CardStateName.Original).getSVar(xSvar);
|
String xValue = card.getSVar(xSvar);
|
||||||
manaToAdd = xValue.isEmpty() ? 0 : Integer.parseInt(xValue) * cost.getXcounter(); // X
|
manaToAdd = xValue.isEmpty() ? 0 : Integer.parseInt(card.getSVar(xSvar)) * cost.getXcounter(); // X
|
||||||
} else {
|
} else {
|
||||||
manaToAdd = AbilityUtils.calculateAmount(card, xSvar, sa) * cost.getXcounter();
|
manaToAdd = AbilityUtils.calculateAmount(card, xSvar, sa) * cost.getXcounter();
|
||||||
}
|
}
|
||||||
@@ -1363,13 +1331,12 @@ public class ComputerUtilMana {
|
|||||||
final ListMultimap<Integer, SpellAbility> manaMap = ArrayListMultimap.create();
|
final ListMultimap<Integer, SpellAbility> manaMap = ArrayListMultimap.create();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
List<ReplacementEffect> replacementEffects = new ArrayList<>();
|
List<ReplacementEffect> replacementEffects = new ArrayList<ReplacementEffect>();
|
||||||
for (final Player p : game.getPlayers()) {
|
for (final Player p : game.getPlayers()) {
|
||||||
for (final Card crd : p.getAllCards()) {
|
for (final Card crd : p.getAllCards()) {
|
||||||
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
|
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
|
||||||
if (replacementEffect.requirementsCheck(game)
|
if (replacementEffect.requirementsCheck(game)
|
||||||
&& replacementEffect.getMode() == ReplacementType.ProduceMana
|
&& replacementEffect.getMapParams().containsKey("ManaReplacement")
|
||||||
&& replacementEffect.hasParam("ManaReplacement")
|
|
||||||
&& replacementEffect.zonesCheck(game.getZoneOf(crd))) {
|
&& replacementEffect.zonesCheck(game.getZoneOf(crd))) {
|
||||||
replacementEffects.add(replacementEffect);
|
replacementEffects.add(replacementEffect);
|
||||||
}
|
}
|
||||||
@@ -1409,16 +1376,17 @@ public class ComputerUtilMana {
|
|||||||
AbilityManaPart mp = m.getManaPart();
|
AbilityManaPart mp = m.getManaPart();
|
||||||
|
|
||||||
// setup produce mana replacement effects
|
// setup produce mana replacement effects
|
||||||
final Map<AbilityKey, Object> repParams = AbilityKey.newMap();
|
final Map<String, Object> repParams = new HashMap<>();
|
||||||
repParams.put(AbilityKey.Mana, mp.getOrigProduced());
|
repParams.put("Event", "ProduceMana");
|
||||||
repParams.put(AbilityKey.Affected, sourceCard);
|
repParams.put("Mana", mp.getOrigProduced());
|
||||||
repParams.put(AbilityKey.Player, ai);
|
repParams.put("Affected", sourceCard);
|
||||||
repParams.put(AbilityKey.AbilityMana, m);
|
repParams.put("Player", ai);
|
||||||
|
repParams.put("AbilityMana", m);
|
||||||
|
|
||||||
for (final ReplacementEffect replacementEffect : replacementEffects) {
|
for (final ReplacementEffect replacementEffect : replacementEffects) {
|
||||||
if (replacementEffect.canReplace(repParams)) {
|
if (replacementEffect.canReplace(repParams)) {
|
||||||
Card crd = replacementEffect.getHostCard();
|
Card crd = replacementEffect.getHostCard();
|
||||||
String repType = crd.getSVar(replacementEffect.getParam("ManaReplacement"));
|
String repType = crd.getSVar(replacementEffect.getMapParams().get("ManaReplacement"));
|
||||||
if (repType.contains("Chosen")) {
|
if (repType.contains("Chosen")) {
|
||||||
repType = TextUtil.fastReplace(repType, "Chosen", MagicColor.toShortString(crd.getChosenColor()));
|
repType = TextUtil.fastReplace(repType, "Chosen", MagicColor.toShortString(crd.getChosenColor()));
|
||||||
}
|
}
|
||||||
@@ -1569,7 +1537,7 @@ public class ComputerUtilMana {
|
|||||||
* @return map between creatures and shards to convoke
|
* @return map between creatures and shards to convoke
|
||||||
*/
|
*/
|
||||||
public static Map<Card, ManaCostShard> getConvokeOrImproviseFromList(final ManaCost cost, List<Card> list, boolean improvise) {
|
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;
|
Card convoked = null;
|
||||||
if (!improvise) {
|
if (!improvise) {
|
||||||
for (ManaCostShard toPay : cost) {
|
for (ManaCostShard toPay : cost) {
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
|||||||
if (c.hasKeyword("CARDNAME can't attack or block.")) {
|
if (c.hasKeyword("CARDNAME can't attack or block.")) {
|
||||||
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
|
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
|
||||||
}
|
}
|
||||||
if (!c.canUntapPhaseController()) {
|
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
|
||||||
if (c.isTapped()) {
|
if (c.isTapped()) {
|
||||||
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
|
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilityManaPart;
|
import forge.game.spellability.AbilityManaPart;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.ability.AbilityKey;
|
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.PlayerZone;
|
import forge.game.zone.PlayerZone;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -42,7 +41,7 @@ import java.util.*;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
public abstract class GameState {
|
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 {
|
static {
|
||||||
ZONES.put(ZoneType.Battlefield, "battlefield");
|
ZONES.put(ZoneType.Battlefield, "battlefield");
|
||||||
ZONES.put(ZoneType.Hand, "hand");
|
ZONES.put(ZoneType.Hand, "hand");
|
||||||
@@ -67,15 +66,14 @@ public abstract class GameState {
|
|||||||
|
|
||||||
private boolean puzzleCreatorState = false;
|
private boolean puzzleCreatorState = false;
|
||||||
|
|
||||||
private final Map<ZoneType, String> humanCardTexts = 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.class);
|
private final Map<ZoneType, String> aiCardTexts = new EnumMap<ZoneType, String>(ZoneType.class);
|
||||||
|
|
||||||
private final Map<Integer, Card> idToCard = new HashMap<>();
|
private final Map<Integer, Card> idToCard = new HashMap<>();
|
||||||
private final Map<Card, Integer> cardToAttachId = new HashMap<>();
|
private final Map<Card, Integer> cardToAttachId = new HashMap<>();
|
||||||
private final Map<Card, Integer> cardToEnchantPlayerId = new HashMap<>();
|
private final Map<Card, Integer> cardToEnchantPlayerId = new HashMap<>();
|
||||||
private final Map<Card, Integer> markedDamage = new HashMap<>();
|
private final Map<Card, Integer> markedDamage = new HashMap<>();
|
||||||
private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>();
|
private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>();
|
||||||
private final Map<Card, CardCollection> cardToChosenCards = new HashMap<>();
|
|
||||||
private final Map<Card, String> cardToChosenType = 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>> cardToRememberedId = new HashMap<>();
|
||||||
private final Map<Card, List<String>> cardToImprintedId = new HashMap<>();
|
private final Map<Card, List<String>> cardToImprintedId = new HashMap<>();
|
||||||
@@ -98,13 +96,8 @@ public abstract class GameState {
|
|||||||
private String precastHuman = null;
|
private String precastHuman = null;
|
||||||
private String precastAI = null;
|
private String precastAI = null;
|
||||||
|
|
||||||
private String putOnStackHuman = null;
|
|
||||||
private String putOnStackAI = null;
|
|
||||||
|
|
||||||
private int turn = 1;
|
private int turn = 1;
|
||||||
|
|
||||||
private boolean removeSummoningSickness = false;
|
|
||||||
|
|
||||||
// Targeting for precast spells in a game state (mostly used by Puzzle Mode game states)
|
// 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_NONE = -1; // untargeted spell (e.g. Joraga Invocation)
|
||||||
private final int TARGET_HUMAN = -2;
|
private final int TARGET_HUMAN = -2;
|
||||||
@@ -218,10 +211,6 @@ public abstract class GameState {
|
|||||||
// Remember the IDs of imprinted cards
|
// Remember the IDs of imprinted cards
|
||||||
cardsReferencedByID.add(i);
|
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)) {
|
if (game.getCombat() != null && game.getCombat().isAttacking(card)) {
|
||||||
// Remember the IDs of attacked planeswalkers
|
// Remember the IDs of attacked planeswalkers
|
||||||
GameEntity def = game.getCombat().getDefenderByAttacker(card);
|
GameEntity def = game.getCombat().getDefenderByAttacker(card);
|
||||||
@@ -255,7 +244,7 @@ public abstract class GameState {
|
|||||||
newText.append(";");
|
newText.append(";");
|
||||||
}
|
}
|
||||||
if (c.isToken()) {
|
if (c.isToken()) {
|
||||||
newText.append("t:").append(new TokenInfo(c).toString());
|
newText.append("t:" + new TokenInfo(c).toString());
|
||||||
} else {
|
} else {
|
||||||
if (c.getPaperCard() == null) {
|
if (c.getPaperCard() == null) {
|
||||||
return;
|
return;
|
||||||
@@ -323,17 +312,6 @@ public abstract class GameState {
|
|||||||
newText.append("|NamedCard:").append(c.getNamedCard());
|
newText.append("|NamedCard:").append(c.getNamedCard());
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
List<String> rememberedCardIds = Lists.newArrayList();
|
||||||
for (Object obj : c.getRemembered()) {
|
for (Object obj : c.getRemembered()) {
|
||||||
if (obj instanceof Card) {
|
if (obj instanceof Card) {
|
||||||
@@ -378,7 +356,7 @@ public abstract class GameState {
|
|||||||
newText.append("|Attacking");
|
newText.append("|Attacking");
|
||||||
GameEntity def = c.getGame().getCombat().getDefenderByAttacker(c);
|
GameEntity def = c.getGame().getCombat().getDefenderByAttacker(c);
|
||||||
if (def instanceof Card) {
|
if (def instanceof Card) {
|
||||||
newText.append(":").append(def.getId());
|
newText.append(":" + def.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -454,10 +432,6 @@ public abstract class GameState {
|
|||||||
turn = Integer.parseInt(categoryValue);
|
turn = Integer.parseInt(categoryValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (categoryName.equals("removesummoningsickness")) {
|
|
||||||
removeSummoningSickness = categoryValue.equalsIgnoreCase("true");
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (categoryName.endsWith("life")) {
|
else if (categoryName.endsWith("life")) {
|
||||||
if (isHuman)
|
if (isHuman)
|
||||||
humanLife = Integer.parseInt(categoryValue);
|
humanLife = Integer.parseInt(categoryValue);
|
||||||
@@ -539,13 +513,6 @@ public abstract class GameState {
|
|||||||
precastAI = categoryValue;
|
precastAI = categoryValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (categoryName.endsWith("putonstack")) {
|
|
||||||
if (isHuman)
|
|
||||||
putOnStackHuman = categoryValue;
|
|
||||||
else
|
|
||||||
putOnStackAI = categoryValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (categoryName.endsWith("manapool")) {
|
else if (categoryName.endsWith("manapool")) {
|
||||||
if (isHuman)
|
if (isHuman)
|
||||||
humanManaPool = categoryValue;
|
humanManaPool = categoryValue;
|
||||||
@@ -585,7 +552,6 @@ public abstract class GameState {
|
|||||||
cardToExiledWithId.clear();
|
cardToExiledWithId.clear();
|
||||||
markedDamage.clear();
|
markedDamage.clear();
|
||||||
cardToChosenClrs.clear();
|
cardToChosenClrs.clear();
|
||||||
cardToChosenCards.clear();
|
|
||||||
cardToChosenType.clear();
|
cardToChosenType.clear();
|
||||||
cardToScript.clear();
|
cardToScript.clear();
|
||||||
cardAttackMap.clear();
|
cardAttackMap.clear();
|
||||||
@@ -625,16 +591,11 @@ public abstract class GameState {
|
|||||||
|
|
||||||
game.getTriggerHandler().setSuppressAllTriggers(false);
|
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)
|
// 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.)
|
// Note: triggers may fire during combat declarations ("whenever X attacks, ...", etc.)
|
||||||
if (newPhase == PhaseType.COMBAT_DECLARE_ATTACKERS || newPhase == PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
if (newPhase == PhaseType.COMBAT_DECLARE_ATTACKERS || newPhase == PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
||||||
boolean toDeclareBlockers = newPhase == PhaseType.COMBAT_DECLARE_BLOCKERS;
|
boolean toDeclareBlockers = newPhase == PhaseType.COMBAT_DECLARE_BLOCKERS;
|
||||||
if (newPlayerTurn != null) {
|
handleCombat(game, newPlayerTurn, newPlayerTurn.getSingleOpponent(), toDeclareBlockers);
|
||||||
handleCombat(game, newPlayerTurn, newPlayerTurn.getSingleOpponent(), toDeclareBlockers);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
game.getStack().setResolving(false);
|
game.getStack().setResolving(false);
|
||||||
@@ -644,25 +605,19 @@ public abstract class GameState {
|
|||||||
game.getPhaseHandler().devAdvanceToPhase(advPhase);
|
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
|
game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
|
||||||
}
|
}
|
||||||
|
|
||||||
private String processManaPool(ManaPool manaPool) {
|
private String processManaPool(ManaPool manaPool) {
|
||||||
StringBuilder mana = new StringBuilder();
|
String mana = "";
|
||||||
for (final byte c : MagicColor.WUBRGC) {
|
for (final byte c : MagicColor.WUBRGC) {
|
||||||
int amount = manaPool.getAmountOfColor(c);
|
int amount = manaPool.getAmountOfColor(c);
|
||||||
for (int i = 0; i < amount; i++) {
|
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) {
|
private void updateManaPool(Player p, String manaDef, boolean clearPool, boolean persistent) {
|
||||||
@@ -720,10 +675,10 @@ public abstract class GameState {
|
|||||||
for (final Card c : combat.getAttackers()) {
|
for (final Card c : combat.getAttackers()) {
|
||||||
attackedTarget.add(combat.getDefenderByAttacker(c));
|
attackedTarget.add(combat.getDefenderByAttacker(c));
|
||||||
}
|
}
|
||||||
final Map<AbilityKey, Object> runParams = Maps.newEnumMap(AbilityKey.class);
|
final Map<String, Object> runParams = Maps.newHashMap();
|
||||||
runParams.put(AbilityKey.Attackers, combat.getAttackers());
|
runParams.put("Attackers", combat.getAttackers());
|
||||||
runParams.put(AbilityKey.AttackingPlayer, combat.getAttackingPlayer());
|
runParams.put("AttackingPlayer", combat.getAttackingPlayer());
|
||||||
runParams.put(AbilityKey.AttackedTarget, attackedTarget);
|
runParams.put("AttackedTarget", attackedTarget);
|
||||||
game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false);
|
game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,9 +774,6 @@ public abstract class GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void executeScript(Game game, Card c, String sPtr) {
|
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;
|
int tgtID = TARGET_NONE;
|
||||||
if (sPtr.contains("->")) {
|
if (sPtr.contains("->")) {
|
||||||
String tgtDef = sPtr.substring(sPtr.lastIndexOf("->") + 2);
|
String tgtDef = sPtr.substring(sPtr.lastIndexOf("->") + 2);
|
||||||
@@ -894,22 +846,16 @@ public abstract class GameState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa != null) {
|
sa.setActivatingPlayer(c.getController());
|
||||||
sa.setActivatingPlayer(c.getController());
|
|
||||||
}
|
|
||||||
handleScriptedTargetingForSA(game, sa, tgtID);
|
handleScriptedTargetingForSA(game, sa, tgtID);
|
||||||
|
|
||||||
if (putOnStack) {
|
sa.resolve();
|
||||||
game.getStack().addAndUnfreeze(sa);
|
|
||||||
} else {
|
|
||||||
sa.resolve();
|
|
||||||
|
|
||||||
// resolve subabilities
|
// resolve subabilities
|
||||||
SpellAbility subSa = sa.getSubAbility();
|
SpellAbility subSa = sa.getSubAbility();
|
||||||
while (subSa != null) {
|
while (subSa != null) {
|
||||||
subSa.resolve();
|
subSa.resolve();
|
||||||
subSa = subSa.getSubAbility();
|
subSa = subSa.getSubAbility();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -931,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) {
|
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;
|
int tgtID = TARGET_NONE;
|
||||||
String scriptID = "";
|
String scriptID = "";
|
||||||
|
|
||||||
@@ -977,7 +902,7 @@ public abstract class GameState {
|
|||||||
SpellAbility sa = null;
|
SpellAbility sa = null;
|
||||||
|
|
||||||
if (!scriptID.isEmpty()) {
|
if (!scriptID.isEmpty()) {
|
||||||
executeScript(game, c, scriptID, putOnStack);
|
executeScript(game, c, scriptID);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -986,11 +911,7 @@ public abstract class GameState {
|
|||||||
|
|
||||||
handleScriptedTargetingForSA(game, sa, tgtID);
|
handleScriptedTargetingForSA(game, sa, tgtID);
|
||||||
|
|
||||||
if (putOnStack) {
|
sa.resolve();
|
||||||
game.getStack().addAndUnfreeze(sa);
|
|
||||||
} else {
|
|
||||||
sa.resolve();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMarkedDamage() {
|
private void handleMarkedDamage() {
|
||||||
@@ -1026,12 +947,6 @@ public abstract class GameState {
|
|||||||
Card c = entry.getKey();
|
Card c = entry.getKey();
|
||||||
c.setNamedCard(entry.getValue());
|
c.setNamedCard(entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chosen cards
|
|
||||||
for (Entry<Card, CardCollection> entry : cardToChosenCards.entrySet()) {
|
|
||||||
Card c = entry.getKey();
|
|
||||||
c.setChosenCards(entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCardAttachments() {
|
private void handleCardAttachments() {
|
||||||
@@ -1062,11 +977,11 @@ public abstract class GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void applyCountersToGameEntity(GameEntity entity, String counterString) {
|
private void applyCountersToGameEntity(GameEntity entity, String counterString) {
|
||||||
entity.setCounters(Maps.newEnumMap(CounterType.class));
|
entity.setCounters(Maps.<CounterType, Integer>newEnumMap(CounterType.class));
|
||||||
String[] allCounterStrings = counterString.split(",");
|
String[] allCounterStrings = counterString.split(",");
|
||||||
for (final String counterPair : allCounterStrings) {
|
for (final String counterPair : allCounterStrings) {
|
||||||
String[] pair = counterPair.split("=", 2);
|
String[] pair = counterPair.split("=", 2);
|
||||||
entity.addCounter(CounterType.valueOf(pair[0]), Integer.parseInt(pair[1]), null, false, false, null);
|
entity.addCounter(CounterType.valueOf(pair[0]), Integer.parseInt(pair[1]), null, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1079,7 +994,7 @@ public abstract class GameState {
|
|||||||
p.getZone(zt).removeAllCards(true);
|
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()) {
|
for (Entry<ZoneType, String> kv : cardTexts.entrySet()) {
|
||||||
String value = kv.getValue();
|
String value = kv.getValue();
|
||||||
playerCards.put(kv.getKey(), processCardsForZone(value.isEmpty() ? new String[0] : value.split(";"), p));
|
playerCards.put(kv.getKey(), processCardsForZone(value.isEmpty() ? new String[0] : value.split(";"), p));
|
||||||
@@ -1092,7 +1007,7 @@ public abstract class GameState {
|
|||||||
for (Entry<ZoneType, CardCollectionView> kv : playerCards.entrySet()) {
|
for (Entry<ZoneType, CardCollectionView> kv : playerCards.entrySet()) {
|
||||||
PlayerZone zone = p.getZone(kv.getKey());
|
PlayerZone zone = p.getZone(kv.getKey());
|
||||||
if (kv.getKey() == ZoneType.Battlefield) {
|
if (kv.getKey() == ZoneType.Battlefield) {
|
||||||
List<Card> cards = new ArrayList<>();
|
List<Card> cards = new ArrayList<Card>();
|
||||||
for (final Card c : kv.getValue()) {
|
for (final Card c : kv.getValue()) {
|
||||||
if (c.isToken()) {
|
if (c.isToken()) {
|
||||||
cards.add(c);
|
cards.add(c);
|
||||||
@@ -1108,7 +1023,7 @@ public abstract class GameState {
|
|||||||
Map<CounterType, Integer> counters = c.getCounters();
|
Map<CounterType, Integer> counters = c.getCounters();
|
||||||
// Note: Not clearCounters() since we want to keep the counters
|
// Note: Not clearCounters() since we want to keep the counters
|
||||||
// var as-is.
|
// var as-is.
|
||||||
c.setCounters(Maps.newEnumMap(CounterType.class));
|
c.setCounters(Maps.<CounterType, Integer>newEnumMap(CounterType.class));
|
||||||
if (c.isAura()) {
|
if (c.isAura()) {
|
||||||
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
|
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
|
||||||
// (will be overridden later, so the actual value shouldn't matter)
|
// (will be overridden later, so the actual value shouldn't matter)
|
||||||
@@ -1132,6 +1047,7 @@ public abstract class GameState {
|
|||||||
zone.setCards(kv.getValue());
|
zone.setCards(kv.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1192,7 +1108,7 @@ public abstract class GameState {
|
|||||||
} else if (info.startsWith("SummonSick")) {
|
} else if (info.startsWith("SummonSick")) {
|
||||||
c.setSickness(true);
|
c.setSickness(true);
|
||||||
} else if (info.startsWith("FaceDown")) {
|
} else if (info.startsWith("FaceDown")) {
|
||||||
c.turnFaceDown(true);
|
c.setState(CardStateName.FaceDown, true);
|
||||||
if (info.endsWith("Manifested")) {
|
if (info.endsWith("Manifested")) {
|
||||||
c.setManifested(true);
|
c.setManifested(true);
|
||||||
}
|
}
|
||||||
@@ -1217,14 +1133,6 @@ public abstract class GameState {
|
|||||||
// TODO: improve this for game states with more than two players
|
// TODO: improve this for game states with more than two players
|
||||||
String tgt = info.substring(info.indexOf(':') + 1);
|
String tgt = info.substring(info.indexOf(':') + 1);
|
||||||
cardToEnchantPlayerId.put(c, tgt.equalsIgnoreCase("AI") ? TARGET_AI : TARGET_HUMAN);
|
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:")) {
|
} else if (info.startsWith("Ability:")) {
|
||||||
String abString = info.substring(info.indexOf(':') + 1).toLowerCase();
|
String abString = info.substring(info.indexOf(':') + 1).toLowerCase();
|
||||||
c.addSpellAbility(AbilityFactory.getAbility(abilityString.get(abString), c));
|
c.addSpellAbility(AbilityFactory.getAbility(abilityString.get(abString), c));
|
||||||
@@ -1235,13 +1143,6 @@ public abstract class GameState {
|
|||||||
cardToChosenClrs.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
|
cardToChosenClrs.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
|
||||||
} else if (info.startsWith("ChosenType:")) {
|
} else if (info.startsWith("ChosenType:")) {
|
||||||
cardToChosenType.put(c, info.substring(info.indexOf(':') + 1));
|
cardToChosenType.put(c, info.substring(info.indexOf(':') + 1));
|
||||||
} 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("NamedCard:")) {
|
} else if (info.startsWith("NamedCard:")) {
|
||||||
cardToNamedCard.put(c, info.substring(info.indexOf(':') + 1));
|
cardToNamedCard.put(c, info.substring(info.indexOf(':') + 1));
|
||||||
} else if (info.startsWith("ExecuteScript:")) {
|
} else if (info.startsWith("ExecuteScript:")) {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import forge.game.card.*;
|
|||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.cost.*;
|
import forge.game.cost.*;
|
||||||
import forge.game.keyword.KeywordInterface;
|
|
||||||
import forge.game.mana.Mana;
|
import forge.game.mana.Mana;
|
||||||
import forge.game.mana.ManaConversionMatrix;
|
import forge.game.mana.ManaConversionMatrix;
|
||||||
import forge.game.mana.ManaCostBeingPaid;
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
@@ -92,7 +91,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
// AI does not know how to sideboard
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -163,22 +162,10 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends GameEntity> List<T> chooseEntitiesForEffect(
|
public <T extends GameEntity> List<T> chooseEntitiesForEffect(
|
||||||
FCollectionView<T> optionList, int min, int max, DelayedReveal delayedReveal, SpellAbility sa, String title,
|
FCollectionView<T> optionList, DelayedReveal delayedReveal, SpellAbility sa, String title,
|
||||||
Player targetedPlayer) {
|
Player targetedPlayer) {
|
||||||
if (delayedReveal != null) {
|
// this isn't used
|
||||||
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix());
|
return null;
|
||||||
}
|
|
||||||
FCollection<T> remaining = new FCollection<>(optionList);
|
|
||||||
List<T> selecteds = new ArrayList<>();
|
|
||||||
T selected;
|
|
||||||
do {
|
|
||||||
selected = chooseSingleEntityForEffect(remaining, null, sa, title, selecteds.size()>=min, targetedPlayer);
|
|
||||||
if ( selected != null ) {
|
|
||||||
remaining.remove(selected);
|
|
||||||
selecteds.add(selected);
|
|
||||||
}
|
|
||||||
} while ( (selected != null ) && (selecteds.size() < max) );
|
|
||||||
return selecteds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -354,7 +341,8 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
@Override
|
@Override
|
||||||
public boolean apply(Card card) {
|
public boolean apply(Card card) {
|
||||||
// need a custom predicate here since Volrath's Shapeshifter may have a different name OTB
|
// need a custom predicate here since Volrath's Shapeshifter may have a different name OTB
|
||||||
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()) {
|
}).isEmpty()) {
|
||||||
int bestValue = 0;
|
int bestValue = 0;
|
||||||
@@ -492,7 +480,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
|
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
|
||||||
return new CardCollection(toDiscard);
|
return new CardCollection(toDiscard);
|
||||||
}
|
}
|
||||||
return getAi().getCardsToDiscard(num, null, sa);
|
return getAi().getCardsToDiscard(num, (String[])null, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -523,66 +511,15 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return brains.aiShouldRun(replacementEffect, effectSA);
|
return brains.aiShouldRun(replacementEffect, effectSA);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mulliganKeepHand(Player firstPlayer, int cardsToReturn) {
|
|
||||||
return !ComputerUtil.wantMulligan(player, cardsToReturn);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CardCollectionView getCardsToMulligan(Player firstPlayer) {
|
public CardCollectionView getCardsToMulligan(Player firstPlayer) {
|
||||||
if (!ComputerUtil.wantMulligan(player, 0)) {
|
if (!ComputerUtil.wantMulligan(player)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return player.getCardsIn(ZoneType.Hand);
|
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
|
@Override
|
||||||
public void declareAttackers(Player attacker, Combat combat) {
|
public void declareAttackers(Player attacker, Combat combat) {
|
||||||
brains.declareAttackers(attacker, combat);
|
brains.declareAttackers(attacker, combat);
|
||||||
@@ -612,7 +549,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard) {
|
public CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard) {
|
||||||
return brains.getCardsToDiscard(numDiscard, null, null);
|
return brains.getCardsToDiscard(numDiscard, (String[])null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -669,7 +606,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
||||||
}
|
}
|
||||||
return SpellApiToAi.Converter.get(api).chooseNumber(player, sa, min, max, params);
|
return SpellApiToAi.Converter.get(api).chooseNumber(player, sa, min, max, params);
|
||||||
}
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int chooseNumber(SpellAbility sa, String title, List<Integer> options, Player relatedPlayer) {
|
public int chooseNumber(SpellAbility sa, String title, List<Integer> options, Player relatedPlayer) {
|
||||||
@@ -729,15 +666,14 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
Card rem = (Card) source.getFirstRemembered();
|
Card rem = (Card) source.getFirstRemembered();
|
||||||
if (!rem.isInZone(ZoneType.Battlefield)) {
|
if (!rem.getZone().is(ZoneType.Battlefield)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case "BetterTgtThanRemembered":
|
case "BetterTgtThanRemembered":
|
||||||
if (source.getRememberedCount() > 0) {
|
if (source.getRememberedCount() > 0) {
|
||||||
Card rem = (Card) source.getFirstRemembered();
|
Card rem = (Card) source.getFirstRemembered();
|
||||||
if (!rem.isInZone(ZoneType.Battlefield)) {
|
if (!rem.getZone().is(ZoneType.Battlefield)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (Card c : source.getController().getCreaturesInPlay()) {
|
for (Card c : source.getController().getCreaturesInPlay()) {
|
||||||
@@ -747,7 +683,6 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -866,8 +801,8 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReplacementEffect chooseSingleReplacementEffect(String prompt, List<ReplacementEffect> possibleReplacers) {
|
public ReplacementEffect chooseSingleReplacementEffect(String prompt, List<ReplacementEffect> possibleReplacers, Map<String, Object> runParams) {
|
||||||
return brains.chooseSingleReplacementEffect(possibleReplacers);
|
return brains.chooseSingleReplacementEffect(possibleReplacers, runParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -941,10 +876,6 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
|
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
|
||||||
emptyAbility.setActivatingPlayer(player);
|
emptyAbility.setActivatingPlayer(player);
|
||||||
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
|
||||||
for (String sVar : sa.getSVars()) {
|
|
||||||
emptyAbility.setSVar(sVar, sa.getSVar(sVar));
|
|
||||||
}
|
|
||||||
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
||||||
ComputerUtil.playNoStack(player, emptyAbility, game); // AI needs something to resolve to pay that cost
|
ComputerUtil.playNoStack(player, emptyAbility, game); // AI needs something to resolve to pay that cost
|
||||||
return true;
|
return true;
|
||||||
@@ -996,6 +927,11 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<GameEntity, CounterType> chooseProliferation(SpellAbility sa) {
|
||||||
|
return brains.chooseProliferation(sa);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chooseTargetsFor(SpellAbility currentAbility) {
|
public boolean chooseTargetsFor(SpellAbility currentAbility) {
|
||||||
return brains.doTrigger(currentAbility, true);
|
return brains.doTrigger(currentAbility, true);
|
||||||
@@ -1072,7 +1008,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return new HashMap<>();
|
return new HashMap<Card, ManaCostShard>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1098,7 +1034,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
public String chooseCardName(SpellAbility sa, Predicate<ICardFace> cpp, String valid, String message) {
|
public String chooseCardName(SpellAbility sa, Predicate<ICardFace> cpp, String valid, String message) {
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
CardCollectionView aiLibrary = player.getCardsIn(ZoneType.Library);
|
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 Card source = sa.getHostCard();
|
||||||
final String logic = sa.getParam("AILogic");
|
final String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
@@ -1154,7 +1090,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Card> chooseCardsForZoneChange(
|
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) {
|
DelayedReveal delayedReveal, String selectPrompt, Player decider) {
|
||||||
// this isn't used
|
// this isn't used
|
||||||
return null;
|
return null;
|
||||||
@@ -1220,18 +1156,6 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
// Choose the optional cost if it can be paid (to be improved later, check for playability and other conditions perhaps)
|
// 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);
|
Cost fullCost = opt.getCost().copy().add(costSoFar);
|
||||||
SpellAbility fullCostSa = chosen.copyWithDefinedCost(fullCost);
|
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)) {
|
if (ComputerUtilCost.canPayCost(fullCostSa, player)) {
|
||||||
chosenOptCosts.add(opt);
|
chosenOptCosts.add(opt);
|
||||||
costSoFar.add(opt.getCost());
|
costSoFar.add(opt.getCost());
|
||||||
@@ -1240,44 +1164,4 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
return chosenOptCosts;
|
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() != null ? sa.getPayCosts().copy() : Cost.Zero;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
CardCollection choices = new CardCollection();
|
|
||||||
|
|
||||||
for (String mapKey: validMap.keySet()) {
|
|
||||||
CardCollection cc = validMap.get(mapKey);
|
|
||||||
cc.removeAll(choices);
|
|
||||||
choices.add(ComputerUtilCard.getBestAI(cc)); // TODO: should the AI limit itself here with the max number of cards in hand?
|
|
||||||
}
|
|
||||||
|
|
||||||
return choices;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,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 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();
|
int paidCMC = cost.getConvertedManaCost();
|
||||||
if (paidCMC < minCMC) {
|
if (paidCMC < minCMC) {
|
||||||
// if it's a CMC 3 spell and we're more than one mana source short for it, might be worth it anyway
|
if (paidCMC == 3 && numManaSrcs < 3) {
|
||||||
return 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 true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -168,7 +172,7 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return best != null ? best.getName() : "";
|
return best.getName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,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() {
|
public static int getSacThreshold() {
|
||||||
@@ -327,7 +335,7 @@ public class SpecialCardAi {
|
|||||||
boolean canTrample = source.hasKeyword(Keyword.TRAMPLE);
|
boolean canTrample = source.hasKeyword(Keyword.TRAMPLE);
|
||||||
|
|
||||||
if (!isBlocking && combat.getDefenderByAttacker(source) instanceof Card) {
|
if (!isBlocking && combat.getDefenderByAttacker(source) instanceof Card) {
|
||||||
int loyalty = combat.getDefenderByAttacker(source).getCounters(CounterType.LOYALTY);
|
int loyalty = ((Card)combat.getDefenderByAttacker(source)).getCounters(CounterType.LOYALTY);
|
||||||
int totalDamageToPW = 0;
|
int totalDamageToPW = 0;
|
||||||
for (Card atk : (combat.getAttackersOf(combat.getDefenderByAttacker(source)))) {
|
for (Card atk : (combat.getAttackersOf(combat.getDefenderByAttacker(source)))) {
|
||||||
if (combat.isUnblocked(atk)) {
|
if (combat.isUnblocked(atk)) {
|
||||||
@@ -403,7 +411,11 @@ public class SpecialCardAi {
|
|||||||
Pair<Integer, Integer> predictedPT = getPumpedPT(ai, source.getNetCombatDamage(), source.getNetToughness());
|
Pair<Integer, Integer> predictedPT = getPumpedPT(ai, source.getNetCombatDamage(), source.getNetToughness());
|
||||||
int oppT = Aggregates.sum(potentialBlockers, CardPredicates.Accessors.fnGetNetToughness);
|
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) {
|
public static Pair<Integer, Integer> getPumpedPT(Player ai, int power, int toughness) {
|
||||||
@@ -489,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
|
// Guilty Conscience
|
||||||
public static class GuiltyConscience {
|
public static class GuiltyConscience {
|
||||||
public static Card getBestAttachTarget(final Player ai, final SpellAbility sa, final List<Card> list) {
|
public static Card getBestAttachTarget(final Player ai, final SpellAbility sa, final List<Card> list) {
|
||||||
@@ -556,7 +525,10 @@ public class SpecialCardAi {
|
|||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
// Don't enchant creatures that can survive
|
// 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);
|
chosen = ComputerUtilCard.getBestCreatureAI(creatures);
|
||||||
@@ -896,14 +868,15 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
} else if (blackViseOTB && computerHandSize + exiledWithNecro - 1 >= 4) {
|
} else if (blackViseOTB && computerHandSize + exiledWithNecro - 1 >= 4) {
|
||||||
// try not to overdraw in presence of Black Vise
|
// try not to overdraw in presence of Black Vise
|
||||||
return false;
|
return false;
|
||||||
} else if (computerHandSize + exiledWithNecro - 1 >= maxHandSize) {
|
} else if (computerHandSize + exiledWithNecro - 1 >= maxHandSize) {
|
||||||
// Only draw until we reach max hand size
|
// Only draw until we reach max hand size
|
||||||
return false;
|
return false;
|
||||||
} else if (!ph.isPlayerTurn(ai) || !ph.is(PhaseType.MAIN2)) {
|
} else if (!ph.isPlayerTurn(ai) || !ph.is(PhaseType.MAIN2)) {
|
||||||
// Only activate in AI's own turn (sans the exception above)
|
// Only activate in AI's own turn (sans the exception above)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -925,7 +898,11 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
}
|
}
|
||||||
// Maybe use it for some important high-impact spells even if there are more cards in hand?
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1106,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(CounterType.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
|
// Survival of the Fittest
|
||||||
public static class SurvivalOfTheFittest {
|
public static class SurvivalOfTheFittest {
|
||||||
public static Card considerDiscardTarget(final Player ai) {
|
public static Card considerDiscardTarget(final Player ai) {
|
||||||
@@ -1211,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
|
// 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
|
// 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)
|
// (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;
|
return maxCMC;
|
||||||
}
|
}
|
||||||
// We appear to be playing Reanimator (or we have a reanimator card in hand already), so it's
|
// We appear to be playing Reanimator (or we have a reanimator card in hand already), so it's
|
||||||
@@ -1290,8 +1229,12 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// use in case we're getting low on cards or if we're significantly behind our opponent in cards in hand
|
if (aiHandSize < HAND_SIZE_THRESHOLD || maxOppHandSize - aiHandSize > HAND_SIZE_THRESHOLD) {
|
||||||
return 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 true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1318,7 +1261,9 @@ public class SpecialCardAi {
|
|||||||
if (topGY == null
|
if (topGY == null
|
||||||
|| !topGY.isCreature()
|
|| !topGY.isCreature()
|
||||||
|| ComputerUtilCard.evaluateCreature(creatHand) > ComputerUtilCard.evaluateCreature(topGY) + 80) {
|
|| 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1439,7 +1384,8 @@ public class SpecialCardAi {
|
|||||||
} else if (!ph.isPlayerTurn(ai)) {
|
} else if (!ph.isPlayerTurn(ai)) {
|
||||||
// Only activate in AI's own turn (sans the exception above)
|
// Only activate in AI's own turn (sans the exception above)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ public abstract class SpellAbilityAi {
|
|||||||
protected static boolean isSorcerySpeed(final SpellAbility sa) {
|
protected static boolean isSorcerySpeed(final SpellAbility sa) {
|
||||||
return (sa.getRootAbility().isSpell() && sa.getHostCard().isSorcery())
|
return (sa.getRootAbility().isSpell() && sa.getHostCard().isSorcery())
|
||||||
|| (sa.getRootAbility().isAbility() && sa.getRestrictions().isSorcerySpeed())
|
|| (sa.getRootAbility().isAbility() && sa.getRestrictions().isSorcerySpeed())
|
||||||
|| (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."));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -276,7 +276,7 @@ public abstract class SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isPwAbility() && phase.is(PhaseType.MAIN2)) {
|
if (sa.getRestrictions().isPwAbility() && phase.is(PhaseType.MAIN2)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (sa.isSpell() && !sa.isBuyBackAbility()) {
|
if (sa.isSpell() && !sa.isBuyBackAbility()) {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
|
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
|
||||||
.put(ApiType.AddPhase, AddPhaseAi.class)
|
.put(ApiType.AddPhase, AddPhaseAi.class)
|
||||||
.put(ApiType.AddTurn, AddTurnAi.class)
|
.put(ApiType.AddTurn, AddTurnAi.class)
|
||||||
.put(ApiType.Amass, AmassAi.class)
|
|
||||||
.put(ApiType.Animate, AnimateAi.class)
|
.put(ApiType.Animate, AnimateAi.class)
|
||||||
.put(ApiType.AnimateAll, AnimateAllAi.class)
|
.put(ApiType.AnimateAll, AnimateAllAi.class)
|
||||||
.put(ApiType.Attach, AttachAi.class)
|
.put(ApiType.Attach, AttachAi.class)
|
||||||
@@ -33,10 +32,8 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.BidLife, BidLifeAi.class)
|
.put(ApiType.BidLife, BidLifeAi.class)
|
||||||
.put(ApiType.Bond, BondAi.class)
|
.put(ApiType.Bond, BondAi.class)
|
||||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||||
.put(ApiType.CantUntapTurn, CantUntapTurnAi.class)
|
|
||||||
.put(ApiType.ChangeCombatants, CannotPlayAi.class)
|
.put(ApiType.ChangeCombatants, CannotPlayAi.class)
|
||||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||||
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
|
||||||
.put(ApiType.ChangeZone, ChangeZoneAi.class)
|
.put(ApiType.ChangeZone, ChangeZoneAi.class)
|
||||||
.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class)
|
.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class)
|
||||||
.put(ApiType.Charm, CharmAi.class)
|
.put(ApiType.Charm, CharmAi.class)
|
||||||
@@ -63,7 +60,6 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.Destroy, DestroyAi.class)
|
.put(ApiType.Destroy, DestroyAi.class)
|
||||||
.put(ApiType.DestroyAll, DestroyAllAi.class)
|
.put(ApiType.DestroyAll, DestroyAllAi.class)
|
||||||
.put(ApiType.Dig, DigAi.class)
|
.put(ApiType.Dig, DigAi.class)
|
||||||
.put(ApiType.DigMultiple, DigMultipleAi.class)
|
|
||||||
.put(ApiType.DigUntil, DigUntilAi.class)
|
.put(ApiType.DigUntil, DigUntilAi.class)
|
||||||
.put(ApiType.Discard, DiscardAi.class)
|
.put(ApiType.Discard, DiscardAi.class)
|
||||||
.put(ApiType.DrainMana, DrainManaAi.class)
|
.put(ApiType.DrainMana, DrainManaAi.class)
|
||||||
@@ -71,7 +67,6 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.EachDamage, DamageEachAi.class)
|
.put(ApiType.EachDamage, DamageEachAi.class)
|
||||||
.put(ApiType.Effect, EffectAi.class)
|
.put(ApiType.Effect, EffectAi.class)
|
||||||
.put(ApiType.Encode, EncodeAi.class)
|
.put(ApiType.Encode, EncodeAi.class)
|
||||||
.put(ApiType.EndCombatPhase, EndTurnAi.class)
|
|
||||||
.put(ApiType.EndTurn, EndTurnAi.class)
|
.put(ApiType.EndTurn, EndTurnAi.class)
|
||||||
.put(ApiType.ExchangeLife, LifeExchangeAi.class)
|
.put(ApiType.ExchangeLife, LifeExchangeAi.class)
|
||||||
.put(ApiType.ExchangeLifeVariant, LifeExchangeVariantAi.class)
|
.put(ApiType.ExchangeLifeVariant, LifeExchangeVariantAi.class)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -21,7 +22,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
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());
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
|
|
||||||
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
||||||
@@ -45,7 +46,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
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 TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
@@ -56,9 +57,12 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
|||||||
} else {
|
} else {
|
||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
return defined.contains(opp);
|
if (!defined.contains(opp)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
@@ -83,7 +87,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ai.getWeakestOpponent());
|
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||||
}
|
}
|
||||||
|
|
||||||
return randomReturn;
|
return randomReturn;
|
||||||
|
|||||||
@@ -67,8 +67,10 @@ public class AddTurnAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: improve ai for Sage of Hours
|
if (!StringUtils.isNumeric(sa.getParam("NumTurns"))) {
|
||||||
return StringUtils.isNumeric(sa.getParam("NumTurns"));
|
// TODO: improve ai for Sage of Hours
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// not sure if the AI should be playing with cards that give the
|
// not sure if the AI should be playing with cards that give the
|
||||||
// Human more turns.
|
// Human more turns.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
package forge.ai.ability;
|
|
||||||
|
|
||||||
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.*;
|
|
||||||
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(CounterType.P1P1)) > 0;
|
|
||||||
} else {
|
|
||||||
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);
|
|
||||||
|
|
||||||
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(CounterType.P1P1)) {
|
|
||||||
token.setCounters(CounterType.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) {
|
|
||||||
Iterable<Card> better = CardLists.filter(options, CardPredicates.canReceiveCounters(CounterType.P1P1));
|
|
||||||
if (Iterables.isEmpty(better)) {
|
|
||||||
better = options;
|
|
||||||
}
|
|
||||||
return ComputerUtilCard.getBestAI(better);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -10,7 +10,6 @@ import forge.game.ability.AbilityFactory;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.cost.CostPutCounter;
|
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -78,13 +77,13 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
num = (num == null) ? "1" : num;
|
num = (num == null) ? "1" : num;
|
||||||
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
|
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
|
||||||
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
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));
|
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
|
||||||
ComputerUtilCard.sortByEvaluateCreature(list);
|
ComputerUtilCard.sortByEvaluateCreature(list);
|
||||||
if (!list.isEmpty() && list.size() == nToSac && ComputerUtilCost.canPayCost(sa, ai)) {
|
if (!list.isEmpty() && list.size() == nToSac && ComputerUtilCost.canPayCost(sa, ai)) {
|
||||||
Card animatedCopy = becomeAnimated(source, sa);
|
Card animatedCopy = becomeAnimated(source, sa);
|
||||||
list.add(animatedCopy);
|
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);
|
topStack);
|
||||||
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
|
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
|
||||||
if (ComputerUtilCard.evaluateCreature(animatedCopy) < ComputerUtilCard.evaluateCreature(list.get(0))
|
if (ComputerUtilCard.evaluateCreature(animatedCopy) < ComputerUtilCard.evaluateCreature(list.get(0))
|
||||||
@@ -110,16 +109,11 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
if (ph.is(PhaseType.MAIN2) && !sa.hasParam("Permanent") && !sa.hasParam("UntilYourNextTurn")) {
|
if (ph.is(PhaseType.MAIN2) && !sa.hasParam("Permanent") && !sa.hasParam("UntilYourNextTurn")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Don't animate if the AI won't attack anyway or use as a potential blocker
|
// Don't animate if the AI won't attack anyway
|
||||||
Player opponent = ai.getWeakestOpponent();
|
Player opponent = ai.getWeakestOpponent();
|
||||||
// Activating as a potential blocker is only viable if it's an ability activated from a permanent, otherwise
|
|
||||||
// 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
|
if (ph.isPlayerTurn(ai) && ai.getLife() < 6 && opponent.getLife() > 6
|
||||||
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)
|
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)
|
||||||
&& !sa.hasParam("AILogic") && !sa.hasParam("Permanent") && !activateAsPotentialBlocker) {
|
&& !sa.hasParam("AILogic") && !sa.hasParam("Permanent")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -207,16 +201,21 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
return bFlag; // All of the defined stuff is animated, not very useful
|
return bFlag; // All of the defined stuff is animated, not very useful
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return animateTgtAI(sa);
|
if (!animateTgtAI(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return animateTgtAI(sa);
|
if (!animateTgtAI(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -246,11 +245,6 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
private boolean animateTgtAI(final SpellAbility sa) {
|
private boolean animateTgtAI(final SpellAbility sa) {
|
||||||
final Player ai = sa.getActivatingPlayer();
|
final Player ai = sa.getActivatingPlayer();
|
||||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
final boolean alwaysActivatePWAbility = sa.hasParam("Planeswalker")
|
|
||||||
&& sa.getPayCosts() != null
|
|
||||||
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class)
|
|
||||||
&& sa.getTargetRestrictions() != null
|
|
||||||
&& sa.getTargetRestrictions().getMinTargets(sa.getHostCard(), sa) == 0;
|
|
||||||
|
|
||||||
final CardType types = new CardType();
|
final CardType types = new CardType();
|
||||||
if (sa.hasParam("Types")) {
|
if (sa.hasParam("Types")) {
|
||||||
@@ -270,7 +264,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
|
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
|
||||||
|
|
||||||
// list is empty, no possible targets
|
// list is empty, no possible targets
|
||||||
if (list.isEmpty() && !alwaysActivatePWAbility) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +317,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// data is empty, no good targets
|
// data is empty, no good targets
|
||||||
if (data.isEmpty() && !alwaysActivatePWAbility) {
|
if (data.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,16 +366,10 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
Integer power = null;
|
Integer power = null;
|
||||||
if (sa.hasParam("Power")) {
|
if (sa.hasParam("Power")) {
|
||||||
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
||||||
if (power == 0 && "PTByCMC".equals(sa.getParam("AILogic"))) {
|
|
||||||
power = card.getManaCost().getCMC();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Integer toughness = null;
|
Integer toughness = null;
|
||||||
if (sa.hasParam("Toughness")) {
|
if (sa.hasParam("Toughness")) {
|
||||||
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
|
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();
|
final CardType types = new CardType();
|
||||||
@@ -467,19 +455,26 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
AnimateEffectBase.doAnimate(card, sa, power, toughness, types, removeTypes, finalDesc, keywords, removeKeywords, hiddenKeywords, timestamp);
|
AnimateEffectBase.doAnimate(card, sa, power, toughness, types, removeTypes, finalDesc, keywords, removeKeywords, hiddenKeywords, timestamp);
|
||||||
|
|
||||||
|
// back to duplicating AnimateEffect.resolve
|
||||||
|
// TODO will all these abilities/triggers/replacements/etc. lead to
|
||||||
|
// memory leaks or unintended effects?
|
||||||
// remove abilities
|
// remove abilities
|
||||||
final List<SpellAbility> removedAbilities = Lists.newArrayList();
|
final List<SpellAbility> removedAbilities = Lists.newArrayList();
|
||||||
|
boolean clearAbilities = sa.hasParam("OverwriteAbilities");
|
||||||
boolean clearSpells = sa.hasParam("OverwriteSpells");
|
boolean clearSpells = sa.hasParam("OverwriteSpells");
|
||||||
boolean removeAll = sa.hasParam("RemoveAllAbilities");
|
boolean removeAll = sa.hasParam("RemoveAllAbilities");
|
||||||
boolean removeIntrinsic = sa.hasParam("RemoveIntrinsicAbilities");
|
boolean removeIntrinsic = sa.hasParam("RemoveIntrinsicAbilities");
|
||||||
|
|
||||||
if (clearSpells) {
|
if (clearAbilities || clearSpells || removeAll) {
|
||||||
removedAbilities.addAll(Lists.newArrayList(card.getSpells()));
|
for (final SpellAbility ab : card.getSpellAbilities()) {
|
||||||
}
|
if (removeAll
|
||||||
|
|| (ab.isIntrinsic() && removeIntrinsic && !ab.isBasicLandAbility())
|
||||||
if (sa.hasParam("RemoveThisAbility") && !removedAbilities.contains(sa)) {
|
|| (ab.isAbility() && clearAbilities)
|
||||||
removedAbilities.add(sa);
|
|| (ab.isSpell() && clearSpells)) {
|
||||||
|
card.removeSpellAbility(ab);
|
||||||
|
removedAbilities.add(ab);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// give abilities
|
// give abilities
|
||||||
@@ -487,7 +482,9 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
if (abilities.size() > 0) {
|
if (abilities.size() > 0) {
|
||||||
for (final String s : abilities) {
|
for (final String s : abilities) {
|
||||||
final String actualAbility = source.getSVar(s);
|
final String actualAbility = source.getSVar(s);
|
||||||
addedAbilities.add(AbilityFactory.getAbility(actualAbility, card));
|
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, source);
|
||||||
|
addedAbilities.add(grantedAbility);
|
||||||
|
card.addSpellAbility(grantedAbility);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,8 +493,8 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
if (triggers.size() > 0) {
|
if (triggers.size() > 0) {
|
||||||
for (final String s : triggers) {
|
for (final String s : triggers) {
|
||||||
final String actualTrigger = source.getSVar(s);
|
final String actualTrigger = source.getSVar(s);
|
||||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, false);
|
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, source, false);
|
||||||
addedTriggers.add(parsedTrigger);
|
addedTriggers.add(card.addTrigger(parsedTrigger));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,35 +503,31 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
if (replacements.size() > 0) {
|
if (replacements.size() > 0) {
|
||||||
for (final String s : replacements) {
|
for (final String s : replacements) {
|
||||||
final String actualReplacement = source.getSVar(s);
|
final String actualReplacement = source.getSVar(s);
|
||||||
final ReplacementEffect parsedReplacement = ReplacementHandler.parseReplacement(actualReplacement, card, false);
|
final ReplacementEffect parsedReplacement = ReplacementHandler.parseReplacement(actualReplacement,
|
||||||
addedReplacements.add(parsedReplacement);
|
source, false);
|
||||||
|
addedReplacements.add(card.addReplacementEffect(parsedReplacement));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// give static abilities (should only be used by cards to give
|
// suppress triggers from the animated card
|
||||||
// itself a static ability)
|
final List<Trigger> removedTriggers = Lists.newArrayList();
|
||||||
final List<StaticAbility> addedStaticAbilities = Lists.newArrayList();
|
if (sa.hasParam("OverwriteTriggers") || removeAll || removeIntrinsic) {
|
||||||
if (stAbs.size() > 0) {
|
for (final Trigger trigger : card.getTriggers()) {
|
||||||
for (final String s : stAbs) {
|
if (removeIntrinsic && !trigger.isIntrinsic()) {
|
||||||
final String actualAbility = source.getSVar(s);
|
continue;
|
||||||
addedStaticAbilities.add(new StaticAbility(actualAbility, card));
|
}
|
||||||
|
trigger.setSuppressed(true);
|
||||||
|
removedTriggers.add(trigger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removeAll || removeIntrinsic
|
|
||||||
|| !addedAbilities.isEmpty() || !removedAbilities.isEmpty() || !addedTriggers.isEmpty()
|
|
||||||
|| !addedReplacements.isEmpty() || !addedStaticAbilities.isEmpty()) {
|
|
||||||
card.addChangedCardTraits(addedAbilities, removedAbilities, addedTriggers, addedReplacements,
|
|
||||||
addedStaticAbilities, removeAll, false, removeIntrinsic, timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// give static abilities (should only be used by cards to give
|
// give static abilities (should only be used by cards to give
|
||||||
// itself a static ability)
|
// itself a static ability)
|
||||||
if (stAbs.size() > 0) {
|
if (stAbs.size() > 0) {
|
||||||
for (final String s : stAbs) {
|
for (final String s : stAbs) {
|
||||||
final String actualAbility = source.getSVar(s);
|
final String actualAbility = source.getSVar(s);
|
||||||
final StaticAbility stAb = card.addStaticAbility(actualAbility);
|
final StaticAbility stAb = card.addStaticAbility(actualAbility);
|
||||||
if ("Continuous".equals(stAb.getParam("Mode"))) {
|
if ("Continuous".equals(stAb.getMapParams().get("Mode"))) {
|
||||||
for (final StaticAbilityLayer layer : stAb.getLayers()) {
|
for (final StaticAbilityLayer layer : stAb.getLayers()) {
|
||||||
StaticAbilityContinuous.applyContinuousAbility(stAb, new CardCollection(card), layer);
|
StaticAbilityContinuous.applyContinuousAbility(stAb, new CardCollection(card), layer);
|
||||||
}
|
}
|
||||||
@@ -555,6 +548,30 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
card.setSVar(name, actualsVar);
|
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);
|
ComputerUtilCard.applyStaticContPT(game, card, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public class AnimateAllAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
return "Always".equals(sa.getParam("AILogic"));
|
return false;
|
||||||
} // end animateAllCanPlayAI()
|
} // end animateAllCanPlayAI()
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -31,7 +31,10 @@ import forge.game.trigger.TriggerType;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class AttachAi extends SpellAbilityAi {
|
public class AttachAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -120,7 +123,9 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
return !(c.hasProtectionFrom(source) || c.hasKeyword(Keyword.SHROUD) || c.hasKeyword(Keyword.HEXPROOF));
|
return !(c.hasProtectionFrom(source) || c.hasKeyword(Keyword.SHROUD) || c.hasKeyword(Keyword.HEXPROOF));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return !targets.isEmpty();
|
if (targets.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -228,13 +233,8 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
boolean alternativeConsiderations = hasFloatMana || willDiscardNow || willDieNow || willRespondToStack || willCastAtEOT || willCastEarly;
|
boolean alternativeConsiderations = hasFloatMana || willDiscardNow || willDieNow || willRespondToStack || willCastAtEOT || willCastEarly;
|
||||||
|
|
||||||
if (!alternativeConsiderations) {
|
if (!alternativeConsiderations && (combat == null || game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) || (!combat.isAttacking(attachTarget) && !combat.isBlocking(attachTarget))) {
|
||||||
if (combat == null ||
|
return false;
|
||||||
game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return combat.isAttacking(attachTarget) || combat.isBlocking(attachTarget);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -448,7 +448,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
private static Player attachToPlayerAIPreferences(final Player aiPlayer, final SpellAbility sa,
|
private static Player attachToPlayerAIPreferences(final Player aiPlayer, final SpellAbility sa,
|
||||||
final boolean mandatory) {
|
final boolean mandatory) {
|
||||||
List<Player> targetable = new ArrayList<>();
|
List<Player> targetable = new ArrayList<Player>();
|
||||||
for (final Player player : aiPlayer.getGame().getPlayers()) {
|
for (final Player player : aiPlayer.getGame().getPlayers()) {
|
||||||
if (sa.canTarget(player)) {
|
if (sa.canTarget(player)) {
|
||||||
targetable.add(player);
|
targetable.add(player);
|
||||||
@@ -852,7 +852,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int totToughness = 0;
|
int totToughness = 0;
|
||||||
int totPower = 0;
|
int totPower = 0;
|
||||||
final List<String> keywords = new ArrayList<>();
|
final List<String> keywords = new ArrayList<String>();
|
||||||
|
|
||||||
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
||||||
final Map<String, String> stabMap = stAbility.getMapParams();
|
final Map<String, String> stabMap = stAbility.getMapParams();
|
||||||
@@ -872,11 +872,15 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
String kws = stabMap.get("AddKeyword");
|
String kws = stabMap.get("AddKeyword");
|
||||||
if (kws != null) {
|
if (kws != null) {
|
||||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
for (final String kw : kws.split(" & ")) {
|
||||||
|
keywords.add(kw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
kws = stabMap.get("AddHiddenKeyword");
|
kws = stabMap.get("AddHiddenKeyword");
|
||||||
if (kws != null) {
|
if (kws != null) {
|
||||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
for (final String kw : kws.split(" & ")) {
|
||||||
|
keywords.add(kw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -899,7 +903,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
Card c = null;
|
Card c = null;
|
||||||
if (prefList == null || prefList.isEmpty()) {
|
if (prefList == null || prefList.isEmpty()) {
|
||||||
prefList = new ArrayList<>(list);
|
prefList = new ArrayList<Card>(list);
|
||||||
} else {
|
} else {
|
||||||
c = ComputerUtilCard.getBestAI(prefList);
|
c = ComputerUtilCard.getBestAI(prefList);
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
@@ -953,7 +957,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
// Check if there are any valid targets
|
// Check if there are any valid targets
|
||||||
List<GameObject> targets = new ArrayList<>();
|
List<GameObject> targets = new ArrayList<GameObject>();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
@@ -976,7 +980,9 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// don't equip creatures that don't gain anything
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1143,10 +1149,8 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int totToughness = 0;
|
int totToughness = 0;
|
||||||
int totPower = 0;
|
int totPower = 0;
|
||||||
final List<String> keywords = new ArrayList<>();
|
final List<String> keywords = new ArrayList<String>();
|
||||||
boolean grantingAbilities = false;
|
boolean grantingAbilities = false;
|
||||||
boolean grantingExtraBlock = false;
|
|
||||||
boolean grantingCantUntap = false;
|
|
||||||
|
|
||||||
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
||||||
final Map<String, String> stabMap = stAbility.getMapParams();
|
final Map<String, String> stabMap = stAbility.getMapParams();
|
||||||
@@ -1165,16 +1169,18 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), stAbility);
|
totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), stAbility);
|
||||||
|
|
||||||
grantingAbilities |= stabMap.containsKey("AddAbility");
|
grantingAbilities |= stabMap.containsKey("AddAbility");
|
||||||
grantingExtraBlock |= stabMap.containsKey("CanBlockAmount") || stabMap.containsKey("CanBlockAny");
|
|
||||||
grantingCantUntap |= stabMap.containsKey("CantUntap");
|
|
||||||
|
|
||||||
String kws = stabMap.get("AddKeyword");
|
String kws = stabMap.get("AddKeyword");
|
||||||
if (kws != null) {
|
if (kws != null) {
|
||||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
for (final String kw : kws.split(" & ")) {
|
||||||
|
keywords.add(kw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
kws = stabMap.get("AddHiddenKeyword");
|
kws = stabMap.get("AddHiddenKeyword");
|
||||||
if (kws != null) {
|
if (kws != null) {
|
||||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
for (final String kw : kws.split(" & ")) {
|
||||||
|
keywords.add(kw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1196,30 +1202,19 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//only add useful keywords unless P/T bonus is significant
|
//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 int pow = totPower;
|
||||||
final boolean extraBlock = grantingExtraBlock;
|
|
||||||
final boolean cantUntap = grantingCantUntap;
|
|
||||||
prefList = CardLists.filter(prefList, new Predicate<Card>() {
|
prefList = CardLists.filter(prefList, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
if (!keywords.isEmpty()) {
|
for (final String keyword : keywords) {
|
||||||
for (final String keyword : keywords) {
|
if (isUsefulAttachKeyword(keyword, c, sa, pow)) {
|
||||||
if (isUsefulAttachKeyword(keyword, c, sa, pow)) {
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
if (c.hasKeyword(Keyword.INFECT) && pow >= 2) {
|
||||||
|
// consider +2 power a significant bonus on Infect creatures
|
||||||
|
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;
|
|
||||||
}
|
|
||||||
if (cantUntap && c.isTapped()) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1351,7 +1346,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
CardCollection prefList = list;
|
CardCollection prefList = list;
|
||||||
|
|
||||||
// Filter AI-specific targets if provided
|
// 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"));
|
Card c = attachGeneralAI(aiPlayer, sa, prefList, mandatory, attachSource, sa.getParam("AILogic"));
|
||||||
|
|
||||||
@@ -1555,52 +1550,86 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (evasive) {
|
if (evasive) {
|
||||||
return card.getNetCombatDamage() + powerBonus > 0
|
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||||
&& ComputerUtilCombat.canAttackNextTurn(card)
|
|| !ComputerUtilCombat.canAttackNextTurn(card)
|
||||||
&& canBeBlocked;
|
|| !canBeBlocked) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (keyword.equals("Haste")) {
|
} else if (keyword.equals("Haste")) {
|
||||||
return card.hasSickness() && ph.isPlayerTurn(sa.getActivatingPlayer()) && !card.isTapped()
|
if (!card.hasSickness() || !ph.isPlayerTurn(sa.getActivatingPlayer()) || card.isTapped()
|
||||||
&& card.getNetCombatDamage() + powerBonus > 0
|
|| card.getNetCombatDamage() + powerBonus <= 0
|
||||||
&& !card.hasKeyword("CARDNAME can attack as though it had haste.")
|
|| card.hasKeyword("CARDNAME can attack as though it had haste.")
|
||||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& ComputerUtilCombat.canAttackNextTurn(card);
|
|| !ComputerUtilCombat.canAttackNextTurn(card)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (keyword.endsWith("Indestructible")) {
|
} else if (keyword.endsWith("Indestructible")) {
|
||||||
return true;
|
return true;
|
||||||
} else if (keyword.endsWith("Deathtouch") || keyword.endsWith("Wither")) {
|
} else if (keyword.endsWith("Deathtouch") || keyword.endsWith("Wither")) {
|
||||||
return card.getNetCombatDamage() + powerBonus > 0
|
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||||
&& ((canBeBlocked && ComputerUtilCombat.canAttackNextTurn(card))
|
|| ((!canBeBlocked || !ComputerUtilCombat.canAttackNextTurn(card))
|
||||||
|| CombatUtil.canBlock(card, true));
|
&& !CombatUtil.canBlock(card, true))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (keyword.equals("Double Strike") || keyword.equals("Lifelink")) {
|
} else if (keyword.equals("Double Strike") || keyword.equals("Lifelink")) {
|
||||||
return card.getNetCombatDamage() + powerBonus > 0
|
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||||
&& (ComputerUtilCombat.canAttackNextTurn(card) || CombatUtil.canBlock(card, true));
|
|| (!ComputerUtilCombat.canAttackNextTurn(card) && !CombatUtil.canBlock(card, true))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (keyword.equals("First Strike")) {
|
} else if (keyword.equals("First Strike")) {
|
||||||
return card.getNetCombatDamage() + powerBonus > 0 && !card.hasKeyword(Keyword.DOUBLE_STRIKE)
|
if (card.getNetCombatDamage() + powerBonus <= 0 || card.hasKeyword(Keyword.DOUBLE_STRIKE)
|
||||||
&& (ComputerUtilCombat.canAttackNextTurn(card) || CombatUtil.canBlock(card, true));
|
|| (!ComputerUtilCombat.canAttackNextTurn(card) && !CombatUtil.canBlock(card, true))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (keyword.startsWith("Flanking")) {
|
} else if (keyword.startsWith("Flanking")) {
|
||||||
return card.getNetCombatDamage() + powerBonus > 0
|
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||||
&& ComputerUtilCombat.canAttackNextTurn(card)
|
|| !ComputerUtilCombat.canAttackNextTurn(card)
|
||||||
&& canBeBlocked;
|
|| !canBeBlocked) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (keyword.startsWith("Bushido")) {
|
} else if (keyword.startsWith("Bushido")) {
|
||||||
return (canBeBlocked && ComputerUtilCombat.canAttackNextTurn(card))
|
if ((!canBeBlocked || !ComputerUtilCombat.canAttackNextTurn(card))
|
||||||
|| CombatUtil.canBlock(card, true);
|
&& !CombatUtil.canBlock(card, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (keyword.equals("Trample")) {
|
} else if (keyword.equals("Trample")) {
|
||||||
return card.getNetCombatDamage() + powerBonus > 1
|
if (card.getNetCombatDamage() + powerBonus <= 1
|
||||||
&& canBeBlocked
|
|| !canBeBlocked
|
||||||
&& ComputerUtilCombat.canAttackNextTurn(card);
|
|| !ComputerUtilCombat.canAttackNextTurn(card)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (keyword.equals("Infect")) {
|
} else if (keyword.equals("Infect")) {
|
||||||
return card.getNetCombatDamage() + powerBonus > 0
|
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||||
&& ComputerUtilCombat.canAttackNextTurn(card);
|
|| !ComputerUtilCombat.canAttackNextTurn(card)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (keyword.equals("Vigilance")) {
|
} else if (keyword.equals("Vigilance")) {
|
||||||
return card.getNetCombatDamage() + powerBonus > 0
|
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||||
&& ComputerUtilCombat.canAttackNextTurn(card)
|
|| !ComputerUtilCombat.canAttackNextTurn(card)
|
||||||
&& CombatUtil.canBlock(card, true);
|
|| !CombatUtil.canBlock(card, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (keyword.equals("Reach")) {
|
} 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.")) {
|
} 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")) {
|
} else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) {
|
||||||
return !card.hasKeyword(Keyword.SHROUD) && !card.hasKeyword(Keyword.HEXPROOF);
|
if (card.hasKeyword(Keyword.SHROUD) || card.hasKeyword(Keyword.HEXPROOF)) {
|
||||||
} else return !keyword.equals("Defender");
|
return false;
|
||||||
|
}
|
||||||
|
} else if (keyword.equals("Defender")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1621,11 +1650,17 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (keyword.endsWith("CARDNAME can't attack.") || keyword.equals("Defender")
|
if (keyword.endsWith("CARDNAME can't attack.") || keyword.equals("Defender")
|
||||||
|| keyword.endsWith("CARDNAME can't attack or block.")) {
|
|| keyword.endsWith("CARDNAME can't attack or block.")) {
|
||||||
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
|
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.")) {
|
} 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")) {
|
} 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.")) {
|
} else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) {
|
||||||
for (SpellAbility ability : card.getSpellAbilities()) {
|
for (SpellAbility ability : card.getSpellAbilities()) {
|
||||||
if (ability.isAbility()) {
|
if (ability.isAbility()) {
|
||||||
@@ -1634,10 +1669,18 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")) {
|
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")) {
|
||||||
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
|
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.")
|
} else if (keyword.endsWith("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
||||||
|| keyword.endsWith("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
|| keyword.endsWith("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
||||||
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 2;
|
if (!ComputerUtilCombat.canAttackNextTurn(card) || card.getNetCombatDamage() < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (keyword.endsWith("CARDNAME doesn't untap during your untap step.")) {
|
||||||
|
if (card.isUntapped()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1661,8 +1704,12 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// useless to equip a creature that can't attack or block.
|
if (sa.getHostCard().isEquipment() && ComputerUtilCard.isUselessCreature(ai, c)) {
|
||||||
return !sa.getHostCard().isEquipment() || !ComputerUtilCard.isUselessCreature(ai, c);
|
// useless to equip a creature that can't attack or block.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Card doPumpOrCurseAILogic(final Player ai, final SpellAbility sa, final List<Card> list, final String type) {
|
public static Card doPumpOrCurseAILogic(final Player ai, final SpellAbility sa, final List<Card> list, final String type) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
@@ -16,7 +17,7 @@ public class BalanceAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int diff = 0;
|
int diff = 0;
|
||||||
// TODO Add support for multiplayer logic
|
// 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 humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
||||||
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
|
|||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (tgt.canTgtCreature()) {
|
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);
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
package forge.ai.ability;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
import com.google.common.base.Predicates;
|
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.card.Card;
|
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.card.CardPredicates;
|
|
||||||
import forge.game.cost.CostPutCounter;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
|
|
||||||
public class CantUntapTurnAi extends SpellAbilityAi {
|
|
||||||
@Override
|
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
CardCollection oppCards = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
|
||||||
|
|
||||||
CardCollection relevantToHold = CardLists.filter(oppCards,
|
|
||||||
Predicates.and(CardPredicates.Presets.TAPPED, new Predicate<Card>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(Card card) {
|
|
||||||
if (card.isCreature()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (final SpellAbility ab : card.getSpellAbilities()) {
|
|
||||||
if (ab.isAbility() && (ab.getPayCosts() != null) && ab.getPayCosts().hasTapCost()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
Card bestToTap = ComputerUtilCard.getBestAI(relevantToHold);
|
|
||||||
Card validTarget = ComputerUtilCard.getBestAI(CardLists.filter(oppCards, CardPredicates.Presets.TAPPED));
|
|
||||||
if (validTarget == null) {
|
|
||||||
validTarget = ComputerUtilCard.getBestAI(oppCards);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bestToTap != null) {
|
|
||||||
sa.getTargets().add(bestToTap);
|
|
||||||
return true;
|
|
||||||
} else if (sa.hasParam("Planeswalker")
|
|
||||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostPutCounter.class)) {
|
|
||||||
sa.getTargets().add(validTarget);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
|
||||||
return mandatory || canPlayAI(aiPlayer, sa);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ import forge.card.MagicColor;
|
|||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
import forge.game.GlobalRuleChange;
|
import forge.game.GlobalRuleChange;
|
||||||
import forge.game.ability.AbilityKey;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
@@ -19,7 +18,6 @@ import forge.game.combat.Combat;
|
|||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostDiscard;
|
import forge.game.cost.CostDiscard;
|
||||||
import forge.game.cost.CostPart;
|
import forge.game.cost.CostPart;
|
||||||
import forge.game.cost.CostPutCounter;
|
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -28,7 +26,6 @@ import forge.game.spellability.AbilitySub;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -73,29 +70,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return false;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.checkAiLogic(ai, sa, aiLogic);
|
return super.checkAiLogic(ai, sa, aiLogic);
|
||||||
@@ -122,8 +96,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return SpecialCardAi.LivingDeath.consider(aiPlayer, sa);
|
return SpecialCardAi.LivingDeath.consider(aiPlayer, sa);
|
||||||
} else if (aiLogic.equals("TheScarabGod")) {
|
} else if (aiLogic.equals("TheScarabGod")) {
|
||||||
return SpecialCardAi.TheScarabGod.consider(aiPlayer, sa);
|
return SpecialCardAi.TheScarabGod.consider(aiPlayer, sa);
|
||||||
} else if (aiLogic.equals("SorinVengefulBloodlord")) {
|
|
||||||
return SpecialCardAi.SorinVengefulBloodlord.consider(aiPlayer, sa);
|
|
||||||
} else if (aiLogic.equals("Intuition")) {
|
} else if (aiLogic.equals("Intuition")) {
|
||||||
// This logic only fills the multiple cards array, the decision to play is made
|
// This logic only fills the multiple cards array, the decision to play is made
|
||||||
// separately in hiddenOriginCanPlayAI later.
|
// separately in hiddenOriginCanPlayAI later.
|
||||||
@@ -230,7 +202,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
ZoneType origin = null;
|
ZoneType origin = null;
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||||
|
|
||||||
if (sa.hasParam("Origin")) {
|
if (sa.hasParam("Origin")) {
|
||||||
@@ -344,11 +316,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
for (final Player p : pDefined) {
|
for (final Player p : pDefined) {
|
||||||
CardCollectionView list = p.getCardsIn(origin);
|
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) {
|
if (type != null && p == ai) {
|
||||||
// AI only "knows" about his information
|
// AI only "knows" about his information
|
||||||
list = CardLists.getValidCards(list, type, source.getController(), source);
|
list = CardLists.getValidCards(list, type, source.getController(), source);
|
||||||
@@ -356,7 +323,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
if (c.getType().isLegendary()) {
|
if (c.getType().isLegendary()) {
|
||||||
return !ai.isCardInPlay(c.getName());
|
if (ai.isCardInPlay(c.getName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -438,7 +407,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
// if putting cards from hand to library and parent is drawing cards
|
// if putting cards from hand to library and parent is drawing cards
|
||||||
// make sure this will actually do something:
|
// make sure this will actually do something:
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Player opp = aiPlayer.getWeakestOpponent();
|
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||||
if (tgt != null && tgt.canTgtPlayer()) {
|
if (tgt != null && tgt.canTgtPlayer()) {
|
||||||
boolean isCurse = sa.isCurse();
|
boolean isCurse = sa.isCurse();
|
||||||
if (isCurse && sa.canTarget(opp)) {
|
if (isCurse && sa.canTarget(opp)) {
|
||||||
@@ -483,7 +452,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ZoneType> origin = new ArrayList<>();
|
List<ZoneType> origin = new ArrayList<ZoneType>();
|
||||||
if (sa.hasParam("Origin")) {
|
if (sa.hasParam("Origin")) {
|
||||||
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
||||||
}
|
}
|
||||||
@@ -499,7 +468,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
Iterable<Player> pDefined;
|
Iterable<Player> pDefined;
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if ((tgt != null) && tgt.canTgtPlayer()) {
|
if ((tgt != null) && tgt.canTgtPlayer()) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
@@ -558,7 +527,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
private static Card basicManaFixing(final Player ai, final List<Card> list) { // Search for a Basic Land
|
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 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?
|
// what types can I go get?
|
||||||
for (final String name : MagicColor.Constant.BASIC_LANDS) {
|
for (final String name : MagicColor.Constant.BASIC_LANDS) {
|
||||||
@@ -618,7 +587,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
private static Card chooseCreature(final Player ai, CardCollection list) {
|
private static Card chooseCreature(final Player ai, CardCollection list) {
|
||||||
// Creating a new combat for testing purposes.
|
// Creating a new combat for testing purposes.
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||||
Combat combat = new Combat(opponent);
|
Combat combat = new Combat(opponent);
|
||||||
for (Card att : opponent.getCreaturesInPlay()) {
|
for (Card att : opponent.getCreaturesInPlay()) {
|
||||||
combat.addAttacker(att, ai);
|
combat.addAttacker(att, ai);
|
||||||
@@ -702,8 +671,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// only use blink or bounce effects
|
// only use blink or bounce effects
|
||||||
if (!(destination.equals(ZoneType.Exile)
|
if (!(destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone))
|
||||||
&& (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone || "DelayedBlink".equals(sa.getParam("AILogic"))))
|
|
||||||
&& !destination.equals(ZoneType.Hand)) {
|
&& !destination.equals(ZoneType.Hand)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -737,7 +705,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final AbilitySub subAb = sa.getSubAbility();
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -859,7 +831,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
list = CardLists.getTargetableCards(list, sa);
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
|
|
||||||
// Filter AI-specific targets if provided
|
// Filter AI-specific targets if provided
|
||||||
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, true);
|
||||||
if (sa.hasParam("AITgtsOnlyBetterThanSelf")) {
|
if (sa.hasParam("AITgtsOnlyBetterThanSelf")) {
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -930,7 +902,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
&& !currCombat.getBlockers(attacker).isEmpty()) {
|
&& !currCombat.getBlockers(attacker).isEmpty()) {
|
||||||
ComputerUtilCard.sortByEvaluateCreature(blockers);
|
ComputerUtilCard.sortByEvaluateCreature(blockers);
|
||||||
Combat combat = new Combat(ai);
|
Combat combat = new Combat(ai);
|
||||||
combat.addAttacker(attacker, ai.getWeakestOpponent());
|
combat.addAttacker(attacker, ComputerUtil.getOpponentFor(ai));
|
||||||
for (Card blocker : blockers) {
|
for (Card blocker : blockers) {
|
||||||
combat.addBlocker(attacker, blocker);
|
combat.addBlocker(attacker, blocker);
|
||||||
}
|
}
|
||||||
@@ -959,7 +931,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// if it's blink or bounce, try to save my about to die stuff
|
// if it's blink or bounce, try to save my about to die stuff
|
||||||
final boolean blink = (destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger
|
final boolean blink = (destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger
|
||||||
|| "DelayedBlink".equals(sa.getParam("AILogic")) || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered"))));
|
|| (subApi == ApiType.ChangeZone && subAffected.equals("Remembered"))));
|
||||||
if ((destination.equals(ZoneType.Hand) || blink) && (tgt.getMinTargets(sa.getHostCard(), sa) <= 1)) {
|
if ((destination.equals(ZoneType.Hand) || blink) && (tgt.getMinTargets(sa.getHostCard(), sa) <= 1)) {
|
||||||
// save my about to die stuff
|
// save my about to die stuff
|
||||||
Card tobounce = canBouncePermanent(ai, sa, list);
|
Card tobounce = canBouncePermanent(ai, sa, list);
|
||||||
@@ -990,7 +962,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
for (Card aura : c.getEnchantedBy()) {
|
for (Card aura : c.getEnchantedBy()) {
|
||||||
return aura.getController().isOpponentOf(ai);
|
if (aura.getController().isOpponentOf(ai)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (blink) {
|
if (blink) {
|
||||||
return c.isToken();
|
return c.isToken();
|
||||||
@@ -1103,11 +1079,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.getTargetRestrictions() != null
|
if (list.isEmpty()) {
|
||||||
&& sa.getTargetRestrictions().getMinTargets(source, sa) == 0 && sa.getPayCosts() != null
|
|
||||||
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class);
|
|
||||||
|
|
||||||
if (list.isEmpty() && !doWithoutTarget) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1189,11 +1161,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
}
|
}
|
||||||
if (!doWithoutTarget) {
|
return false;
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (!sa.isTrigger() && !ComputerUtil.shouldCastLessThanMax(ai, source)) {
|
if (!sa.isTrigger() && !ComputerUtil.shouldCastLessThanMax(ai, source)) {
|
||||||
boolean aiTgtsOK = false;
|
boolean aiTgtsOK = false;
|
||||||
@@ -1219,15 +1187,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// honor the Same Creature Type restriction
|
|
||||||
if (tgt.isWithSameCreatureType()) {
|
|
||||||
Card firstTarget = sa.getTargetCard();
|
|
||||||
if (firstTarget != null && !choice.sharesCreatureTypeWith(firstTarget)) {
|
|
||||||
list.remove(choice);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
}
|
}
|
||||||
@@ -1266,7 +1225,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
// filter out untargetables
|
// filter out untargetables
|
||||||
CardCollectionView aiPermanents = CardLists
|
CardCollectionView aiPermanents = CardLists
|
||||||
.filterControlledBy(list, ai);
|
.filterControlledBy(list, ai);
|
||||||
CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS);
|
|
||||||
|
|
||||||
// Felidar Guardian + Saheeli Rai combo support
|
// Felidar Guardian + Saheeli Rai combo support
|
||||||
if (sa.getHostCard().getName().equals("Felidar Guardian")) {
|
if (sa.getHostCard().getName().equals("Felidar Guardian")) {
|
||||||
@@ -1282,7 +1240,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
final List<GameObject> objects = ComputerUtil
|
final List<GameObject> objects = ComputerUtil
|
||||||
.predictThreatenedObjects(ai, sa);
|
.predictThreatenedObjects(ai, sa);
|
||||||
|
|
||||||
final List<Card> threatenedTargets = new ArrayList<>();
|
final List<Card> threatenedTargets = new ArrayList<Card>();
|
||||||
|
|
||||||
for (final Card c : aiPermanents) {
|
for (final Card c : aiPermanents) {
|
||||||
if (objects.contains(c)) {
|
if (objects.contains(c)) {
|
||||||
@@ -1310,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(CounterType.LOYALTY) - b.getCounters(CounterType.LOYALTY);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for (Card pw : aiPlaneswalkers) {
|
|
||||||
int curLoyalty = pw.getCounters(CounterType.LOYALTY);
|
|
||||||
int freshLoyalty = Integer.valueOf(pw.getCurrentState().getBaseLoyalty());
|
|
||||||
if (freshLoyalty - curLoyalty >= loyaltyDiff && curLoyalty <= maxLoyaltyToConsider) {
|
|
||||||
return pw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1462,12 +1393,16 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
final Card attachedTo = list.get(0);
|
final Card attachedTo = list.get(0);
|
||||||
// This code is for the Dragon auras
|
// 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)) {
|
} else if (isPreferredTarget(ai, sa, mandatory, true)) {
|
||||||
// do nothing
|
// do nothing
|
||||||
} else return isUnpreferredTarget(ai, sa, mandatory);
|
} else if (!isUnpreferredTarget(ai, sa, mandatory)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1486,8 +1421,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else if ("WorstCard".equals(logic)) {
|
} else if ("WorstCard".equals(logic)) {
|
||||||
return ComputerUtilCard.getWorstAI(fetchList);
|
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)) {
|
} else if ("Mairsil".equals(logic)) {
|
||||||
return SpecialCardAi.MairsilThePretender.considerCardFromList(fetchList);
|
return SpecialCardAi.MairsilThePretender.considerCardFromList(fetchList);
|
||||||
} else if ("SurvivalOfTheFittest".equals(logic)) {
|
} else if ("SurvivalOfTheFittest".equals(logic)) {
|
||||||
@@ -1519,7 +1452,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
if (c.getType().isLegendary()) {
|
if (c.getType().isLegendary()) {
|
||||||
return !decider.isCardInPlay(c.getName());
|
if (decider.isCardInPlay(c.getName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1528,7 +1463,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
fetchList = CardLists.filter(fetchList, new Predicate<Card>() {
|
fetchList = CardLists.filter(fetchList, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return !ComputerUtilCard.isCardRemAIDeck(c) && !ComputerUtilCard.isCardRemRandomDeck(c);
|
if (ComputerUtilCard.isCardRemAIDeck(c) || ComputerUtilCard.isCardRemRandomDeck(c)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1700,7 +1638,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
if (c.getType().isLegendary()) {
|
if (c.getType().isLegendary()) {
|
||||||
return !ai.isCardInPlay(c.getName());
|
if (ai.isCardInPlay(c.getName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1790,8 +1730,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean doReturnCommanderLogic(SpellAbility sa, Player aiPlayer) {
|
public boolean doReturnCommanderLogic(SpellAbility sa, Player aiPlayer) {
|
||||||
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>)sa.getReplacingObject(AbilityKey.OriginalParams);
|
Map<String, Object> originalParams = (Map<String, Object>)sa.getReplacingObject("OriginalParams");
|
||||||
SpellAbility causeSa = (SpellAbility)originalParams.get(AbilityKey.Cause);
|
SpellAbility causeSa = (SpellAbility)originalParams.get("Cause");
|
||||||
SpellAbility causeSub = null;
|
SpellAbility causeSub = null;
|
||||||
|
|
||||||
// Squee, the Immortal: easier to recast it (the call below has to be "contains" since SA is an intrinsic effect)
|
// Squee, the Immortal: easier to recast it (the call below has to be "contains" since SA is an intrinsic effect)
|
||||||
@@ -1806,16 +1746,20 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
&& "Battlefield".equals(causeSub.getParam("Destination"))) {
|
&& "Battlefield".equals(causeSub.getParam("Destination"))) {
|
||||||
// A blink effect implemented using ChangeZone API
|
// A blink effect implemented using ChangeZone API
|
||||||
return false;
|
return false;
|
||||||
} else // This is an intrinsic effect that blinks the card (e.g. Obzedat, Ghost Council), no need to
|
} else if (subApi == ApiType.DelayedTrigger) {
|
||||||
// return the commander to the Command zone.
|
|
||||||
if (subApi == ApiType.DelayedTrigger) {
|
|
||||||
SpellAbility exec = causeSub.getAdditionalAbility("Execute");
|
SpellAbility exec = causeSub.getAdditionalAbility("Execute");
|
||||||
if (exec != null && exec.getApi() == ApiType.ChangeZone) {
|
if (exec != null && exec.getApi() == ApiType.ChangeZone) {
|
||||||
// A blink effect implemented using a delayed trigger
|
if ("Exile".equals(exec.getParam("Origin")) && "Battlefield".equals(exec.getParam("Destination"))) {
|
||||||
return !"Exile".equals(exec.getParam("Origin")) || !"Battlefield".equals(exec.getParam("Destination"));
|
// A blink effect implemented using a delayed trigger
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else return causeSa.getHostCard() == null || !causeSa.getHostCard().equals(sa.getReplacingObject(AbilityKey.Card))
|
} else if (causeSa.getHostCard() != null && causeSa.getHostCard().equals((Card)sa.getReplacingObject("Card"))
|
||||||
|| !causeSa.getActivatingPlayer().equals(aiPlayer);
|
&& 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normally we want the commander back in Command zone to recast him later
|
// Normally we want the commander back in Command zone to recast him later
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicates;
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
@@ -58,11 +57,6 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
CardCollectionView oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
|
CardCollectionView oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
|
||||||
CardCollectionView computerType = ai.getCardsIn(origin);
|
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 check need to be done before filterListByType because of ChosenX
|
||||||
// Ugin AI: always try to sweep before considering +1
|
// Ugin AI: always try to sweep before considering +1
|
||||||
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
||||||
@@ -335,8 +329,11 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
// if AI creature is better than Human Creature
|
// if AI creature is better than Human Creature
|
||||||
return ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard
|
if (ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard
|
||||||
.evaluateCreatureList(humanCards);
|
.evaluateCreatureList(humanCards)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -438,21 +435,29 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
if (sa.getParam("GainControl") != null) {
|
if (sa.getParam("GainControl") != null) {
|
||||||
// Check if the cards are valuable enough
|
// Check if the cards are valuable enough
|
||||||
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
||||||
return (ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
|
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
|
||||||
.evaluateCreatureList(humanType)) >= 1;
|
.evaluateCreatureList(humanType)) < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
} // otherwise evaluate both lists by CMC and pass only if human
|
||||||
// permanents are less valuable
|
// permanents are less valuable
|
||||||
else return (ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
||||||
.evaluatePermanentList(humanType)) >= 1;
|
.evaluatePermanentList(humanType)) < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// don't activate if human gets more back than AI does
|
// don't activate if human gets more back than AI does
|
||||||
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
|
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
|
||||||
return ComputerUtilCard.evaluateCreatureList(computerType) > ComputerUtilCard
|
if (ComputerUtilCard.evaluateCreatureList(computerType) <= ComputerUtilCard
|
||||||
.evaluateCreatureList(humanType);
|
.evaluateCreatureList(humanType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
} // otherwise evaluate both lists by CMC and pass only if human
|
||||||
// permanents are less valuable
|
// permanents are less valuable
|
||||||
else return ComputerUtilCard.evaluatePermanentList(computerType) > ComputerUtilCard
|
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= ComputerUtilCard
|
||||||
.evaluatePermanentList(humanType);
|
.evaluatePermanentList(humanType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
@@ -71,15 +72,21 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
choices = CardLists.filterControlledBy(choices, ai.getOpponents());
|
choices = CardLists.filterControlledBy(choices, ai.getOpponents());
|
||||||
}
|
}
|
||||||
if (aiLogic.equals("AtLeast1") || aiLogic.equals("OppPreferred")) {
|
if (aiLogic.equals("AtLeast1") || aiLogic.equals("OppPreferred")) {
|
||||||
return !choices.isEmpty();
|
if (choices.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
|
} 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")) {
|
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) {
|
||||||
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||||
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
||||||
|
|
||||||
choices = CardLists.getValidCards(choices, filter, host.getController(), host);
|
choices = CardLists.getValidCards(choices, filter, host.getController(), host);
|
||||||
return !choices.isEmpty();
|
if (choices.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (aiLogic.equals("Never")) {
|
} else if (aiLogic.equals("Never")) {
|
||||||
return false;
|
return false;
|
||||||
} else if (aiLogic.equals("NeedsPrevention")) {
|
} else if (aiLogic.equals("NeedsPrevention")) {
|
||||||
@@ -97,7 +104,9 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return !choices.isEmpty();
|
if (choices.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (aiLogic.equals("Ashiok")) {
|
} else if (aiLogic.equals("Ashiok")) {
|
||||||
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
|
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
|
||||||
for (int i = loyalty; i >= 0; i--) {
|
for (int i = loyalty; i >= 0; i--) {
|
||||||
@@ -109,12 +118,16 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !choices.isEmpty();
|
if (choices.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (aiLogic.equals("RandomNonLand")) {
|
} else if (aiLogic.equals("RandomNonLand")) {
|
||||||
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host).isEmpty();
|
if (CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host).isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (aiLogic.equals("Duneblast")) {
|
} else if (aiLogic.equals("Duneblast")) {
|
||||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||||
CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay();
|
CardCollection oppCreatures = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
||||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
||||||
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
|
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
|
||||||
|
|
||||||
@@ -127,8 +140,10 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
aiCreatures.remove(chosen);
|
aiCreatures.remove(chosen);
|
||||||
int minGain = 200;
|
int minGain = 200;
|
||||||
|
|
||||||
return (ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) < ComputerUtilCard
|
if ((ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) >= ComputerUtilCard
|
||||||
.evaluateCreatureList(oppCreatures);
|
.evaluateCreatureList(oppCreatures)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (tgt.canOnlyTgtOpponent()) {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
sa.getTargets().add(ai.getWeakestOpponent());
|
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||||
} else {
|
} else {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,13 +52,16 @@ public class ChooseColorAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("Addle".equals(sourceName)) {
|
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")) {
|
if (logic.equals("MostExcessOpponentControls")) {
|
||||||
for (byte color : MagicColor.WUBRG) {
|
for (byte color : MagicColor.WUBRG) {
|
||||||
CardCollectionView ailist = ai.getCardsIn(ZoneType.Battlefield);
|
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));
|
ailist = CardLists.filter(ailist, CardPredicates.isColor(color));
|
||||||
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));
|
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ public class ChooseDirectionAi extends SpellAbilityAi {
|
|||||||
CardCollection right = CardLists.filterControlledBy(all, game.getNextPlayerAfter(ai, Direction.Right));
|
CardCollection right = CardLists.filterControlledBy(all, game.getNextPlayerAfter(ai, Direction.Right));
|
||||||
int leftValue = Aggregates.sum(left, CardPredicates.Accessors.fnGetCmc);
|
int leftValue = Aggregates.sum(left, CardPredicates.Accessors.fnGetCmc);
|
||||||
int rightValue = Aggregates.sum(right, 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;
|
return true;
|
||||||
|
|||||||
@@ -1,14 +1,28 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import forge.ai.*;
|
|
||||||
|
import forge.ai.ComputerUtilAbility;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.ComputerUtilCost;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.ai.SpellApiToAi;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.card.*;
|
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.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
|
import forge.game.card.CardUtil;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
@@ -21,9 +35,6 @@ import forge.game.zone.ZoneType;
|
|||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
|
||||||
public class ChooseGenericEffectAi extends SpellAbilityAi {
|
public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -39,8 +50,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ("GideonBlackblade".equals(aiLogic)) {
|
|
||||||
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -86,9 +95,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
return spells.get(0);
|
return spells.get(0);
|
||||||
} else if ("Random".equals(logic)) {
|
} else if ("Random".equals(logic)) {
|
||||||
return Aggregates.random(spells);
|
return Aggregates.random(spells);
|
||||||
} else if ("GideonBlackblade".equals(logic)) {
|
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
|
||||||
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>() {
|
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final SpellAbility sp) {
|
public boolean apply(final SpellAbility sp) {
|
||||||
@@ -103,7 +110,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
Cost unless = new Cost(unlessCost, false);
|
Cost unless = new Cost(unlessCost, false);
|
||||||
SpellAbility paycost = new SpellAbility.EmptySa(sa.getHostCard(), player);
|
SpellAbility paycost = new SpellAbility.EmptySa(sa.getHostCard(), player);
|
||||||
paycost.setPayCosts(unless);
|
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)) {
|
&& ComputerUtilCost.canPayCost(paycost, player)) {
|
||||||
return sp;
|
return sp;
|
||||||
}
|
}
|
||||||
@@ -385,7 +392,9 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
final Player opp = player.getWeakestOpponent();
|
final Player opp = player.getWeakestOpponent();
|
||||||
if (opp != null) {
|
if (opp != null) {
|
||||||
// TODO add predict Combat Damage?
|
// TODO add predict Combat Damage?
|
||||||
return opp.getLife() < copy.getNetPower();
|
if (opp.getLife() < copy.getNetPower()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// haste might not be good enough?
|
// haste might not be good enough?
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -16,7 +17,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
|
|||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = aiPlayer.getWeakestOpponent();
|
Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
@@ -67,7 +68,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
@@ -97,7 +98,10 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
|
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) {
|
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
||||||
return false;
|
return false;
|
||||||
@@ -116,7 +120,9 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
|
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return !choices.isEmpty();
|
if (choices.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -20,6 +21,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
|
|
||||||
@@ -37,13 +39,27 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
// TODO - add some kind of check for during human turn to answer
|
// TODO - add some kind of check for during human turn to answer
|
||||||
// "Can I use this to block something?"
|
// "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;
|
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);
|
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
boolean bFlag = false;
|
boolean bFlag = false;
|
||||||
@@ -115,7 +131,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
* <p>
|
* <p>
|
||||||
* cloneTgtAI.
|
* cloneTgtAI.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
@@ -139,7 +155,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
// a good target
|
// a good target
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||||
*/
|
*/
|
||||||
@@ -162,7 +178,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
|
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
|
||||||
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
|
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
|
||||||
* forge.game.player.Player)
|
* forge.game.player.Player)
|
||||||
@@ -170,38 +186,19 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||||
Player targetedPlayer) {
|
Player targetedPlayer) {
|
||||||
|
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Player ctrl = host.getController();
|
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 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";
|
: "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);
|
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||||
if (!newOptions.isEmpty()) {
|
if (!newOptions.isEmpty()) {
|
||||||
options = newOptions;
|
options = newOptions;
|
||||||
}
|
}
|
||||||
|
Card choice = ComputerUtilCard.getBestAI(options);
|
||||||
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);
|
|
||||||
|
|
||||||
if (isVesuva && "Vesuva".equals(choice.getName())) {
|
if (isVesuva && "Vesuva".equals(choice.getName())) {
|
||||||
choice = null;
|
choice = null;
|
||||||
}
|
}
|
||||||
@@ -209,41 +206,4 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
return choice;
|
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.base.Predicate;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -29,7 +30,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
CardCollection list =
|
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
|
// AI won't try to grab cards that are filtered out of AI decks on
|
||||||
// purpose
|
// purpose
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
|
|||||||
@@ -84,7 +84,9 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
if (sa.hasParam("AllValid")) {
|
if (sa.hasParam("AllValid")) {
|
||||||
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), opponents);
|
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), opponents);
|
||||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||||
return !tgtCards.isEmpty();
|
if (tgtCards.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -245,7 +247,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
sa.getTargets().add(t);
|
sa.getTargets().add(t);
|
||||||
@@ -294,12 +296,15 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
|
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
|
||||||
}
|
}
|
||||||
|
|
||||||
return !lose.contains("EOT")
|
if (lose.contains("EOT")
|
||||||
|| !game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
|
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return this.canPlayAI(ai, sa);
|
return this.canPlayAI(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
} // pumpDrawbackAI()
|
} // pumpDrawbackAI()
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ import java.util.List;
|
|||||||
public class CopyPermanentAi extends SpellAbilityAi {
|
public class CopyPermanentAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
// Card source = sa.getHostCard();
|
||||||
// TODO - I'm sure someone can do this AI better
|
// TODO - I'm sure someone can do this AI better
|
||||||
Card source = sa.getHostCard();
|
|
||||||
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
@@ -37,11 +38,6 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
return ph.is(PhaseType.END_OF_TURN);
|
return ph.is(PhaseType.END_OF_TURN);
|
||||||
} else if ("AtOppEOT".equals(aiLogic)) {
|
} else if ("AtOppEOT".equals(aiLogic)) {
|
||||||
return ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn() != aiPlayer;
|
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)) {
|
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
|
||||||
@@ -50,7 +46,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (sa.hasParam("Defined")) {
|
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 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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,32 +60,9 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (sa.usesTargeting() && sa.hasParam("TargetingPlayer")) {
|
if (sa.usesTargeting() && sa.hasParam("TargetingPlayer")) {
|
||||||
sa.resetTargets();
|
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);
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
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 {
|
} else {
|
||||||
return this.doTriggerAINoCost(aiPlayer, sa, false);
|
return this.doTriggerAINoCost(aiPlayer, sa, false);
|
||||||
}
|
}
|
||||||
@@ -101,7 +74,6 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
final Player activator = sa.getActivatingPlayer();
|
final Player activator = sa.getActivatingPlayer();
|
||||||
final Game game = host.getGame();
|
final Game game = host.getGame();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
final boolean canCopyLegendary = sa.hasParam("NonLegendary");
|
|
||||||
|
|
||||||
|
|
||||||
// ////
|
// ////
|
||||||
@@ -142,7 +114,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return (!c.getType().isLegendary() || canCopyLegendary) || !c.getController().equals(aiPlayer);
|
return !c.getType().isLegendary() || !c.getController().equals(aiPlayer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Card choice;
|
Card choice;
|
||||||
@@ -208,8 +180,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
private CardCollection getBetterOptions(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional) {
|
private CardCollection getBetterOptions(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional) {
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Player ctrl = host.getController();
|
final Player ctrl = host.getController();
|
||||||
final boolean canCopyLegendary = sa.hasParam("NonLegendary");
|
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||||
final String filter = canCopyLegendary ? "Permanent" : "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
|
||||||
// TODO add filter to not select Legendary from Other Player when ai already have a Legendary with that name
|
// 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);
|
return CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,9 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// for Simic Fluxmage and other
|
// for Simic Fluxmage and other
|
||||||
return ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN);
|
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
|
} else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
|
||||||
// something like Cyptoplast Root-kin
|
// something like Cyptoplast Root-kin
|
||||||
@@ -105,7 +107,9 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
// Make sure that removing the last counter doesn't kill the creature
|
// Make sure that removing the last counter doesn't kill the creature
|
||||||
if ("Self".equals(sa.getParam("Source"))) {
|
if ("Self".equals(sa.getParam("Source"))) {
|
||||||
return host == null || host.getNetToughness() - 1 > 0;
|
if (host != null && host.getNetToughness() - 1 <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -189,7 +193,9 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// check for some specific AI preferences
|
// check for some specific AI preferences
|
||||||
if ("DontMoveCounterIfLethal".equals(sa.getParam("AILogic"))) {
|
if ("DontMoveCounterIfLethal".equals(sa.getParam("AILogic"))) {
|
||||||
return cType != CounterType.P1P1 || src.getNetToughness() - src.getTempToughnessBoost() - 1 > 0;
|
if (cType == CounterType.P1P1 && src.getNetToughness() - src.getTempToughnessBoost() - 1 <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// no target
|
// no target
|
||||||
@@ -201,7 +207,9 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return moveTgtAI(ai, sa);
|
if (!moveTgtAI(ai, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -279,7 +287,10 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
// do not steal a P1P1 from Undying if it would die
|
// do not steal a P1P1 from Undying if it would die
|
||||||
// this way
|
// this way
|
||||||
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||||
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken();
|
if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
if (!c.canReceiveCounters(counterType)) {
|
if (!c.canReceiveCounters(counterType)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
for (Map.Entry<CounterType, Integer> e : c.getCounters().entrySet()) {
|
for (Map.Entry<CounterType, Integer> e : c.getCounters().entrySet()) {
|
||||||
// has negative counter it would double
|
// has negative counter it would double
|
||||||
@@ -97,7 +96,10 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
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) {
|
private CounterType getCounterType(SpellAbility sa) {
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.GameEntity;
|
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardUtil;
|
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -38,14 +33,10 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card crd) {
|
public boolean apply(final Card crd) {
|
||||||
if (!crd.hasCounters()) {
|
if (crd.hasCounters()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (crd.isPlaneswalker()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// iterate only over existing counters
|
// iterate only over existing counters
|
||||||
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||||
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||||
@@ -65,11 +56,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card crd) {
|
public boolean apply(final Card crd) {
|
||||||
if (!crd.hasCounters()) {
|
if (crd.hasCounters()) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (crd.isPlaneswalker()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,9 +70,12 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (cperms.isEmpty() && hperms.isEmpty() && !opponentPoison && !allyExpOrEnergy) {
|
||||||
return !cperms.isEmpty() || !hperms.isEmpty() || opponentPoison || allyExpOrEnergy;
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -102,68 +92,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
if ("Always".equals(sa.getParam("AILogic"))) {
|
return canPlayAI(ai, sa);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// Proliferate is always optional for all, no need to select best
|
|
||||||
|
|
||||||
// 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(CounterType.POISON) > 0 && p.canReceiveCounters(CounterType.POISON)) {
|
|
||||||
return (T)p;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (p.getCounters(CounterType.POISON) <= 5 || p.canReceiveCounters(CounterType.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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,8 +220,6 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if ("Never".equals(logic)) {
|
if ("Never".equals(logic)) {
|
||||||
return false;
|
return false;
|
||||||
} else if ("AristocratCounters".equals(logic)) {
|
|
||||||
return PumpAi.doAristocratWithCountersLogic(sa, ai);
|
|
||||||
} else if ("PayEnergy".equals(logic)) {
|
} else if ("PayEnergy".equals(logic)) {
|
||||||
return true;
|
return true;
|
||||||
} else if ("PayEnergyConservatively".equals(logic)) {
|
} else if ("PayEnergyConservatively".equals(logic)) {
|
||||||
@@ -313,20 +311,13 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("Adapt") && source.getCounters(CounterType.P1P1) > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO handle proper calculation of X values based on Cost
|
// TODO handle proper calculation of X values based on Cost
|
||||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
|
|
||||||
if (sa.hasParam("Adapt")) {
|
|
||||||
Game game = ai.getGame();
|
|
||||||
Combat combat = game.getCombat();
|
|
||||||
|
|
||||||
if (!source.canReceiveCounters(CounterType.P1P1) || source.getCounters(CounterType.P1P1) > 0) {
|
|
||||||
return false;
|
|
||||||
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
|
||||||
return doCombatAdaptLogic(source, amount, combat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("Fight".equals(logic)) {
|
if ("Fight".equals(logic)) {
|
||||||
int nPump = 0;
|
int nPump = 0;
|
||||||
if (type.equals("P1P1")) {
|
if (type.equals("P1P1")) {
|
||||||
@@ -469,7 +460,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
int left = amount;
|
int left = amount;
|
||||||
for (Card c : list) {
|
for (Card c : list) {
|
||||||
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, i, i,
|
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, i, i,
|
||||||
Lists.newArrayList())) {
|
Lists.<String>newArrayList())) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
abTgt.addDividedAllocation(c, i);
|
abTgt.addDividedAllocation(c, i);
|
||||||
left -= i;
|
left -= i;
|
||||||
@@ -506,7 +497,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa)) {
|
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
for (Card c : list) {
|
for (Card c : list) {
|
||||||
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, amount, amount,
|
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, amount, amount,
|
||||||
Lists.newArrayList())) {
|
Lists.<String>newArrayList())) {
|
||||||
choice = c;
|
choice = c;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -595,7 +586,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1058,39 +1049,4 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
return false;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -38,13 +39,8 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
final boolean curse = sa.isCurse();
|
final boolean curse = sa.isCurse();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
|
||||||
if ("OwnCreatsAndOtherPWs".equals(sa.getParam("AILogic"))) {
|
hList = CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
||||||
hList = CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), "Creature.YouCtrl,Planeswalker.YouCtrl+Other", source.getController(), source);
|
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
||||||
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), "Creature.YouCtrl,Planeswalker.YouCtrl+Other", source.getController(), source);
|
|
||||||
} else {
|
|
||||||
hList = CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
|
||||||
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for these costs
|
// AI currently disabled for these costs
|
||||||
@@ -72,7 +68,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
Player pl = curse ? ai.getWeakestOpponent() : ai;
|
Player pl = curse ? ComputerUtil.getOpponentFor(ai) : ai;
|
||||||
sa.getTargets().add(pl);
|
sa.getTargets().add(pl);
|
||||||
|
|
||||||
hList = CardLists.filterControlledBy(hList, pl);
|
hList = CardLists.filterControlledBy(hList, pl);
|
||||||
@@ -153,7 +149,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
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
|
@Override
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import forge.ai.ComputerUtilCard;
|
|||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameEntity;
|
|
||||||
import forge.game.GlobalRuleChange;
|
import forge.game.GlobalRuleChange;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
@@ -101,7 +100,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filter AI-specific targets if provided
|
// 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);
|
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||||
|
|
||||||
@@ -201,20 +200,14 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do as P1P1 part
|
// do as P1P1 part
|
||||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasLessCounter(CounterType.P1P1, amount));
|
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
||||||
CardCollection aiUndyingList = CardLists.getKeyword(aiP1P1List, Keyword.UNDYING);
|
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
|
||||||
|
|
||||||
if (!aiUndyingList.isEmpty()) {
|
if (!aiUndyingList.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiUndyingList));
|
aiP1P1List = aiUndyingList;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
if (!aiP1P1List.isEmpty()) {
|
||||||
// remove P1P1 counters from opposing creatures
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
|
||||||
CardCollection oppP1P1List = CardLists.filter(list,
|
|
||||||
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
|
|
||||||
CardPredicates.hasCounter(CounterType.P1P1));
|
|
||||||
if (!oppP1P1List.isEmpty()) {
|
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(oppP1P1List));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,30 +354,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||||
GameEntity target = (GameEntity) params.get("Target");
|
// TODO Auto-generated method stub
|
||||||
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 == CounterType.P1P1
|
|
||||||
&& targetCard.getCounters(CounterType.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.equals(CounterType.POISON) ? max : min;
|
|
||||||
} else {
|
|
||||||
return type.equals(CounterType.POISON) ? max : min;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.chooseNumber(player, sa, min, max, params);
|
return super.chooseNumber(player, sa, min, max, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,49 +370,30 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
return super.chooseCounterType(options, sa, params);
|
return super.chooseCounterType(options, sa, params);
|
||||||
}
|
}
|
||||||
Player ai = sa.getActivatingPlayer();
|
Player ai = sa.getActivatingPlayer();
|
||||||
GameEntity target = (GameEntity) params.get("Target");
|
Card target = (Card) params.get("Target");
|
||||||
|
|
||||||
if (target instanceof Card) {
|
if (target.getController().isOpponentOf(ai)) {
|
||||||
Card targetCard = (Card) target;
|
// if its a Planeswalker try to remove Loyality first
|
||||||
if (targetCard.getController().isOpponentOf(ai)) {
|
if (target.isPlaneswalker()) {
|
||||||
// if its a Planeswalker try to remove Loyality first
|
return CounterType.LOYALTY;
|
||||||
if (targetCard.isPlaneswalker()) {
|
}
|
||||||
return CounterType.LOYALTY;
|
for (CounterType type : options) {
|
||||||
}
|
if (!ComputerUtil.isNegativeCounter(type, target)) {
|
||||||
for (CounterType type : options) {
|
return type;
|
||||||
if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (options.contains(CounterType.M1M1) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
|
||||||
return CounterType.M1M1;
|
|
||||||
} else if (options.contains(CounterType.P1P1) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
|
||||||
return CounterType.P1P1;
|
|
||||||
}
|
|
||||||
for (CounterType type : options) {
|
|
||||||
if (ComputerUtil.isNegativeCounter(type, targetCard)) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (target instanceof Player) {
|
} else {
|
||||||
Player targetPlayer = (Player) target;
|
if (options.contains(CounterType.M1M1) && target.hasKeyword(Keyword.PERSIST)) {
|
||||||
if (targetPlayer.isOpponentOf(ai)) {
|
return CounterType.M1M1;
|
||||||
for (CounterType type : options) {
|
} else if (options.contains(CounterType.P1P1) && target.hasKeyword(Keyword.UNDYING)) {
|
||||||
if (!type.equals(CounterType.POISON)) {
|
return CounterType.M1M1;
|
||||||
return type;
|
}
|
||||||
}
|
for (CounterType type : options) {
|
||||||
}
|
if (ComputerUtil.isNegativeCounter(type, target)) {
|
||||||
} else {
|
return type;
|
||||||
for (CounterType type : options) {
|
|
||||||
if (type.equals(CounterType.POISON)) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.chooseCounterType(options, sa, params);
|
return super.chooseCounterType(options, sa, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -19,7 +20,7 @@ import forge.util.MyRandom;
|
|||||||
|
|
||||||
public abstract class DamageAiBase extends SpellAbilityAi {
|
public abstract class DamageAiBase extends SpellAbilityAi {
|
||||||
protected boolean avoidTargetP(final Player comp, final SpellAbility sa) {
|
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
|
// Logic for cards that damage owner, like Fireslinger
|
||||||
// Do not target a player if they aren't below 75% of our health.
|
// Do not target a player if they aren't below 75% of our health.
|
||||||
// Unless Lifelink will cancel the damage to us
|
// Unless Lifelink will cancel the damage to us
|
||||||
@@ -37,7 +38,9 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if ("SelfDamage".equals(sa.getParam("AILogic"))) {
|
if ("SelfDamage".equals(sa.getParam("AILogic"))) {
|
||||||
if (comp.getLife() * 0.75 < enemy.getLife()) {
|
if (comp.getLife() * 0.75 < enemy.getLife()) {
|
||||||
return !lifelink;
|
if (!lifelink) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -51,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) {
|
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention, final boolean noPlaneswalkerRedirection) {
|
||||||
int restDamage = d;
|
int restDamage = d;
|
||||||
final Game game = comp.getGame();
|
final Game game = comp.getGame();
|
||||||
Player enemy = comp.getWeakestOpponent();
|
Player enemy = ComputerUtil.getOpponentFor(comp);
|
||||||
boolean dmgByCardsInHand = false;
|
boolean dmgByCardsInHand = false;
|
||||||
|
|
||||||
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
|
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate creatures getting killed
|
// Evaluate creatures getting killed
|
||||||
Player enemy = ai.getWeakestOpponent();
|
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||||
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
||||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
@@ -294,7 +294,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate creatures getting killed
|
// Evaluate creatures getting killed
|
||||||
Player enemy = ai.getWeakestOpponent();
|
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||||
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
||||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import forge.game.ability.AbilityUtils;
|
|||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostPart;
|
|
||||||
import forge.game.cost.CostPartMana;
|
import forge.game.cost.CostPartMana;
|
||||||
import forge.game.cost.CostRemoveCounter;
|
import forge.game.cost.CostRemoveCounter;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
@@ -76,11 +75,14 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
source.setSVar("PayX", Integer.toString(dmg));
|
source.setSVar("PayX", Integer.toString(dmg));
|
||||||
} else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.isInZone(ZoneType.Hand)) {
|
} else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.getZone().is(ZoneType.Hand)) {
|
||||||
dmg--; // the card will be spent casting the spell, so actual damage is 1 less
|
dmg--; // the card will be spent casting the spell, so actual damage is 1 less
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.damageTargetAI(ai, sa, dmg, true);
|
if (!this.damageTargetAI(ai, sa, dmg, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -113,7 +115,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
// Set PayX here to maximum value. It will be adjusted later depending on the target.
|
// Set PayX here to maximum value. It will be adjusted later depending on the target.
|
||||||
source.setSVar("PayX", Integer.toString(dmg));
|
source.setSVar("PayX", Integer.toString(dmg));
|
||||||
} else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) {
|
} 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
|
dmg = CardFactoryUtil.xCount(source, sa.getSVar(damage)) - 1; // the card will be spent casting the spell, so actual damage is 1 less
|
||||||
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
|
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
|
||||||
// cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact
|
// cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact
|
||||||
@@ -156,7 +158,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ai.getCreaturesAttackedThisTurn().isEmpty()) {
|
if (ai.getAttackedWithCreatureThisTurn()) {
|
||||||
dmg = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
dmg = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
||||||
}
|
}
|
||||||
} else if ("WildHunt".equals(logic)) {
|
} else if ("WildHunt".equals(logic)) {
|
||||||
@@ -269,11 +271,11 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
sourceName.equals("Crater's Claws")){
|
sourceName.equals("Crater's Claws")){
|
||||||
// If I can kill my target by paying less mana, do it
|
// If I can kill my target by paying less mana, do it
|
||||||
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) {
|
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) {
|
||||||
int actualPay = dmg;
|
int actualPay = 0;
|
||||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||||
final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||||
if (adjDamage < actualPay) {
|
if ((adjDamage > actualPay) && (adjDamage <= dmg)) {
|
||||||
actualPay = adjDamage;
|
actualPay = adjDamage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,23 +286,6 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("XCountersDamage".equals(logic) && sa.getPayCosts() != null) {
|
|
||||||
// Check to ensure that we have enough counters to remove per the defined PayX
|
|
||||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
|
||||||
if (part instanceof CostRemoveCounter) {
|
|
||||||
if (source.getCounters(((CostRemoveCounter) part).counter) < Integer.valueOf(source.getSVar("PayX"))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
|
||||||
final int CMC = Integer.parseInt(source.getSVar("PayX"));
|
|
||||||
return !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(CMC)).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,14 +434,14 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
for (SpellAbility sa : pw.getSpellAbilities()) {
|
for (SpellAbility sa : pw.getSpellAbilities()) {
|
||||||
if (sa.hasParam("Ultimate") && sa.getPayCosts() != null) {
|
if (sa.hasParam("Ultimate") && sa.getPayCosts() != null) {
|
||||||
Integer loyaltyCost = 0;
|
int loyaltyCost = 0;
|
||||||
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
|
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
|
||||||
if (remLoyalty != null) {
|
if (remLoyalty != null) {
|
||||||
// if remLoyalty is null, generally there's an AddCounter<0/LOYALTY> cost, like for Gideon Jura.
|
// if remLoyalty is null, generally there's an AddCounter<0/LOYALTY> cost, like for Gideon Jura.
|
||||||
loyaltyCost = remLoyalty.convertAmount();
|
loyaltyCost = remLoyalty.convertAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loyaltyCost != null && loyaltyCost != 0 && loyaltyCost - curLoyalty <= 1) {
|
if (loyaltyCost != 0 && loyaltyCost - curLoyalty <= 1) {
|
||||||
// Will ultimate soon
|
// Will ultimate soon
|
||||||
pwScore += 10000;
|
pwScore += 10000;
|
||||||
}
|
}
|
||||||
@@ -487,7 +472,9 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
for (final Object o : objects) {
|
for (final Object o : objects) {
|
||||||
if (o instanceof Card) {
|
if (o instanceof Card) {
|
||||||
final Card c = (Card) o;
|
final Card c = (Card) o;
|
||||||
hPlay.remove(c);
|
if (hPlay.contains(c)) {
|
||||||
|
hPlay.remove(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hPlay = CardLists.getTargetableCards(hPlay, sa);
|
hPlay = CardLists.getTargetableCards(hPlay, sa);
|
||||||
@@ -547,7 +534,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer");
|
final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer");
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
Player enemy = ai.getWeakestOpponent();
|
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||||
|
|
||||||
if ("PowerDmg".equals(logic)) {
|
if ("PowerDmg".equals(logic)) {
|
||||||
// check if it is better to target the player instead, the original target is already set in PumpAi.pumpTgtAI()
|
// check if it is better to target the player instead, the original target is already set in PumpAi.pumpTgtAI()
|
||||||
@@ -648,9 +635,10 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (c != null && !this.shouldTgtP(ai, sa, dmg, noPrevention, true)) {
|
if (c != null && !this.shouldTgtP(ai, sa, dmg, noPrevention, true)) {
|
||||||
tcs.add(c);
|
tcs.add(c);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||||
assignedDamage = Math.min(dmg, assignedDamage);
|
if (assignedDamage <= dmg) {
|
||||||
tgt.addDividedAllocation(c, assignedDamage);
|
tgt.addDividedAllocation(c, assignedDamage);
|
||||||
|
}
|
||||||
dmg = dmg - assignedDamage;
|
dmg = dmg - assignedDamage;
|
||||||
if (dmg <= 0) {
|
if (dmg <= 0) {
|
||||||
break;
|
break;
|
||||||
@@ -717,7 +705,8 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (phase.is(PhaseType.MAIN2) && sa.isAbility()) {
|
if (phase.is(PhaseType.MAIN2) && sa.isAbility()) {
|
||||||
if (sa.isPwAbility() || source.hasSVar("EndOfTurnLeavePlay"))
|
if (sa.getRestrictions().isPwAbility()
|
||||||
|
|| source.hasSVar("EndOfTurnLeavePlay"))
|
||||||
freePing = true;
|
freePing = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -871,7 +860,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
// this is for Triggered targets that are mandatory
|
// this is for Triggered targets that are mandatory
|
||||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
if (tgt.canTgtPlaneswalker()) {
|
if (tgt.canTgtPlaneswalker()) {
|
||||||
@@ -947,7 +936,9 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
// If it's not mandatory check a few things
|
// 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 {
|
} else {
|
||||||
if (!this.damageChoosingTargets(ai, sa, tgt, dmg, mandatory, true) && !mandatory) {
|
if (!this.damageChoosingTargets(ai, sa, tgt, dmg, mandatory, true) && !mandatory) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1089,7 +1080,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
continue; // not a toughness debuff
|
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()) {
|
if (sa.usesTargeting() && ab.usesTargeting()) {
|
||||||
// Ensure that the chained spell can target at least the same things (or more) as the current one
|
// Ensure that the chained spell can target at least the same things (or more) as the current one
|
||||||
TargetRestrictions tgtSa = sa.getTargetRestrictions();
|
TargetRestrictions tgtSa = sa.getTargetRestrictions();
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
tcs.add(ai);
|
tcs.add(ai);
|
||||||
chance = true;
|
chance = true;
|
||||||
}
|
}
|
||||||
final List<Card> threatenedTargets = new ArrayList<>();
|
final List<Card> threatenedTargets = new ArrayList<Card>();
|
||||||
// filter AIs battlefield by what I can target
|
// filter AIs battlefield by what I can target
|
||||||
List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard, sa);
|
List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard, sa);
|
||||||
targetables = CardLists.getTargetableCards(targetables, sa);
|
targetables = CardLists.getTargetableCards(targetables, sa);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -124,7 +125,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
sa.resetTargets();
|
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);
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||||
|
|
||||||
// several uses here:
|
// several uses here:
|
||||||
@@ -176,7 +177,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
* @return a CardCollection.
|
* @return a CardCollection.
|
||||||
*/
|
*/
|
||||||
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
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);
|
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@@ -216,7 +217,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
list.remove(c);
|
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 CardCollection forced = CardLists.filterControlledBy(list, ai);
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
@@ -266,7 +267,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
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 (sa.getTargetRestrictions() == null) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class DelayedTriggerAi extends SpellAbilityAi {
|
|||||||
trigsa.setActivatingPlayer(ai);
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
if (trigsa instanceof AbilitySub) {
|
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 {
|
} else {
|
||||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasXCost = abCost.getCostMana() != null && abCost.getCostMana().getAmountOfX() > 0;
|
hasXCost = abCost.getCostMana() != null ? abCost.getCostMana().getAmountOfX() > 0 : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("AtOpponentsCombatOrAfter".equals(sa.getParam("AILogic"))) {
|
if ("AtOpponentsCombatOrAfter".equals(sa.getParam("AILogic"))) {
|
||||||
@@ -63,7 +63,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) {
|
} else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) {
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
if (!ph.is(PhaseType.END_OF_TURN) || !ai.getCreaturesAttackedThisTurn().isEmpty()) {
|
if (!ph.is(PhaseType.END_OF_TURN) || ai.getAttackedWithCreatureThisTurn()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +132,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filter AI-specific targets if provided
|
// 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);
|
list = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
|
||||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||||
@@ -288,7 +288,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
} else if (sa.hasParam("Defined")) {
|
} else if (sa.hasParam("Defined")) {
|
||||||
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
if ("WillSkipTurn".equals(logic) && (sa.getHostCard().getController().equals(ai)
|
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)
|
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|
||||||
|| ai.getLife() <= 5)) {
|
|| ai.getLife() <= 5)) {
|
||||||
// Basic ai logic for Lethal Vapors
|
// Basic ai logic for Lethal Vapors
|
||||||
@@ -296,8 +296,8 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (list.isEmpty()
|
if (list.isEmpty()
|
||||||
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|
||||||
|| CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE).isEmpty()) {
|
|| CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE).isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,7 +342,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filter AI-specific targets if provided
|
// 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) {
|
for (final Card c : preferred) {
|
||||||
list.remove(c);
|
list.remove(c);
|
||||||
@@ -400,11 +400,16 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getTargets().getNumTargeted() >= tgt.getMinTargets(sa.getHostCard(), sa);
|
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return mandatory;
|
if (!mandatory) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLand, String logic) {
|
public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLand, String logic) {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
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;
|
final int CREATURE_EVAL_THRESHOLD = 200;
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
block.assignBlockersForCombat(combat);
|
block.assignBlockersForCombat(combat);
|
||||||
|
|
||||||
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} // only lands involved
|
} // only lands involved
|
||||||
@@ -171,6 +171,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
|
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.keyword.Keyword;
|
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
@@ -24,7 +20,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
|
|
||||||
@@ -46,9 +42,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
if ("Never".equals(sa.getParam("AILogic"))) {
|
if ("Never".equals(sa.getParam("AILogic"))) {
|
||||||
return false;
|
return false;
|
||||||
} else if ("AtOppEndOfTurn".equals(sa.getParam("AILogic"))) {
|
} else if ("AtOppEndOfTurn".equals(sa.getParam("AILogic"))) {
|
||||||
if (!(game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN))) {
|
return game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't deck yourself
|
// don't deck yourself
|
||||||
@@ -66,8 +60,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String num = sa.getParam("DigNum");
|
final String num = sa.getParam("DigNum");
|
||||||
final boolean payXLogic = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayX");
|
if (num != null && num.equals("X") && host.getSVar(num).equals("Count$xPaid")) {
|
||||||
if (num != null && (num.equals("X") && host.getSVar(num).equals("Count$xPaid")) || payXLogic) {
|
|
||||||
// By default, set PayX here to maximum value.
|
// By default, set PayX here to maximum value.
|
||||||
if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) {
|
if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) {
|
||||||
int manaToSave = 0;
|
int manaToSave = 0;
|
||||||
@@ -106,7 +99,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
@@ -131,25 +124,6 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) {
|
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) {
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
|
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
|
||||||
return ComputerUtilCard.getWorstPermanentAI(valid, false, true, false, false);
|
return ComputerUtilCard.getWorstPermanentAI(valid, false, true, false, false);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -35,7 +36,7 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||||
|
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
|
|
||||||
if ("DontMillSelf".equals(logic)) {
|
if ("DontMillSelf".equals(logic)) {
|
||||||
// A card that digs for specific things and puts everything revealed before it into graveyard
|
// A card that digs for specific things and puts everything revealed before it into graveyard
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
return SpecialCardAi.VolrathsShapeshifter.consider(ai, sa);
|
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 (tgt != null) {
|
||||||
if (!discardTargetAI(ai, sa)) {
|
if (!discardTargetAI(ai, sa)) {
|
||||||
@@ -87,7 +87,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
if (sa.hasParam("NumCards")) {
|
if (sa.hasParam("NumCards")) {
|
||||||
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getWeakestOpponent()
|
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
|
||||||
.getCardsIn(ZoneType.Hand).size());
|
.getCardsIn(ZoneType.Hand).size());
|
||||||
if (cardsToDiscard < 1) {
|
if (cardsToDiscard < 1) {
|
||||||
return false;
|
return false;
|
||||||
@@ -150,17 +150,14 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
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)) {
|
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
|
||||||
continue;
|
return false;
|
||||||
} else if (!opp.canDiscardBy(sa)) { // e.g. Tamiyo, Collector of Tales
|
}
|
||||||
continue;
|
if (tgt != null) {
|
||||||
}
|
if (sa.canTarget(opp)) {
|
||||||
if (tgt != null) {
|
sa.getTargets().add(opp);
|
||||||
if (sa.canTarget(opp)) {
|
return true;
|
||||||
sa.getTargets().add(opp);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -172,7 +169,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
if (!discardTargetAI(ai, sa)) {
|
if (!discardTargetAI(ai, sa)) {
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
@@ -193,7 +190,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if ("X".equals(sa.getParam("RevealNumber")) && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
|
if ("X".equals(sa.getParam("RevealNumber")) && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getWeakestOpponent()
|
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
|
||||||
.getCardsIn(ZoneType.Hand).size());
|
.getCardsIn(ZoneType.Hand).size());
|
||||||
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
|
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -18,7 +19,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
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());
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
|
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
@@ -40,7 +41,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
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 TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
@@ -51,9 +52,12 @@ public class DrainManaAi extends SpellAbilityAi {
|
|||||||
} else {
|
} else {
|
||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
return defined.contains(opp);
|
if (!defined.contains(opp)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
@@ -78,7 +82,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ai.getWeakestOpponent());
|
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||||
}
|
}
|
||||||
|
|
||||||
return randomReturn;
|
return randomReturn;
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!canLoot(ai, sa)) {
|
if (!canLoot(ai, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,11 +211,9 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
final boolean drawback = sa.getParent() != null;
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
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();
|
int computerHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||||
final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size();
|
final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size();
|
||||||
@@ -243,12 +241,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
numCards = Integer.parseInt(source.getSVar("PayX"));
|
numCards = Integer.parseInt(source.getSVar("PayX"));
|
||||||
} else {
|
} else {
|
||||||
numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
numCards = ComputerUtilMana.determineLeftoverMana(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);
|
|
||||||
source.setSVar("PayX", Integer.toString(numCards));
|
source.setSVar("PayX", Integer.toString(numCards));
|
||||||
assumeSafeX = true;
|
|
||||||
}
|
}
|
||||||
xPaid = true;
|
xPaid = true;
|
||||||
}
|
}
|
||||||
@@ -409,7 +402,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
if (computerHandSize + numCards > computerMaxHandSize && game.getPhaseHandler().isPlayerTurn(ai)) {
|
if (computerHandSize + numCards > computerMaxHandSize && game.getPhaseHandler().isPlayerTurn(ai)) {
|
||||||
if (xPaid) {
|
if (xPaid) {
|
||||||
numCards = computerMaxHandSize - computerHandSize;
|
numCards = computerMaxHandSize - computerHandSize;
|
||||||
if (sa.getHostCard().isInZone(ZoneType.Hand)) {
|
if (sa.getHostCard().getZone().is(ZoneType.Hand)) {
|
||||||
numCards++; // the card will be spent
|
numCards++; // the card will be spent
|
||||||
}
|
}
|
||||||
source.setSVar("PayX", Integer.toString(numCards));
|
source.setSVar("PayX", Integer.toString(numCards));
|
||||||
@@ -499,8 +492,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if ((computerHandSize + numCards > computerMaxHandSize)
|
if ((computerHandSize + numCards > computerMaxHandSize)
|
||||||
&& game.getPhaseHandler().isPlayerTurn(ai)
|
&& game.getPhaseHandler().isPlayerTurn(ai)
|
||||||
&& !sa.isTrigger()
|
&& !sa.isTrigger()) {
|
||||||
&& !assumeSafeX) {
|
|
||||||
// Don't draw too many cards and then risk discarding cards at
|
// Don't draw too many cards and then risk discarding cards at
|
||||||
// EOT
|
// EOT
|
||||||
if (!drawback) {
|
if (!drawback) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.google.common.collect.Iterables;
|
|||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
|
import forge.game.GlobalRuleChange;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
@@ -266,7 +267,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
final SpellAbility saTop = game.getStack().peekAbility();
|
final SpellAbility saTop = game.getStack().peekAbility();
|
||||||
final Card host = saTop.getHostCard();
|
final Card host = saTop.getHostCard();
|
||||||
if (saTop.getActivatingPlayer() != ai // from opponent
|
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 != null && (host.isInstant() || host.isSorcery())
|
||||||
&& !host.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")) { // valid target
|
&& !host.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")) { // valid target
|
||||||
final ApiType type = saTop.getApi();
|
final ApiType type = saTop.getApi();
|
||||||
@@ -282,7 +283,11 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final SpellAbility topStack = game.getStack().peekAbility();
|
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")) {
|
} else if (logic.equals("Fight")) {
|
||||||
return FightAi.canFightAi(ai, sa, 0, 0);
|
return FightAi.canFightAi(ai, sa, 0, 0);
|
||||||
} else if (logic.equals("Burn")) {
|
} else if (logic.equals("Burn")) {
|
||||||
@@ -296,9 +301,11 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (logic.contains(":")) {
|
if (logic.contains(":")) {
|
||||||
String[] k = logic.split(":");
|
String k[] = logic.split(":");
|
||||||
Integer i = Integer.valueOf(k[1]);
|
Integer i = Integer.valueOf(k[1]);
|
||||||
return ai.getCreaturesInPlay().size() >= i;
|
if (ai.getCreaturesInPlay().size() < i) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if (logic.equals("CastFromGraveThisTurn")) {
|
} else if (logic.equals("CastFromGraveThisTurn")) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
// everything is defined or targeted above, can't do anything there?
|
// everything is defined or targeted above, can't do anything there?
|
||||||
if (sa.hasParam("Defined") && !sa.usesTargeting()) {
|
if (sa.hasParam("Defined") && !sa.usesTargeting()) {
|
||||||
// TODO extend Logic for cards like Arena or Grothama
|
// TODO extend Logic for cards like Arena or Grothama
|
||||||
@@ -108,10 +108,6 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||||
if ("Always".equals(sa.getParam("AILogic"))) {
|
|
||||||
return true; // e.g. Hunt the Weak, the AI logic was already checked through canFightAi
|
|
||||||
}
|
|
||||||
|
|
||||||
return checkApiLogic(aiPlayer, sa);
|
return checkApiLogic(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +119,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//try to make a good trade or no trade
|
//try to make a good trade or no trade
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||||
@@ -196,36 +192,13 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
toughness += bonus;
|
toughness += bonus;
|
||||||
}
|
}
|
||||||
if ("PowerDmg".equals(sa.getParam("AILogic"))) {
|
if ("PowerDmg".equals(sa.getParam("AILogic"))) {
|
||||||
if ("2".equals(sa.getParam("TargetMax"))) {
|
if (FightAi.canKill(aiCreature, humanCreature, power)) {
|
||||||
// Band Together, uses up to two targets to deal damage to a single target
|
sa.getTargets().add(aiCreature);
|
||||||
// TODO: Generalize this so that other TargetMax values can be properly accounted for
|
if (!isChandrasIgnition) {
|
||||||
CardCollection aiCreaturesByPower = new CardCollection(aiCreatures);
|
tgtFight.resetTargets();
|
||||||
CardLists.sortByPowerDesc(aiCreaturesByPower);
|
tgtFight.getTargets().add(humanCreature);
|
||||||
Card maxPower = aiCreaturesByPower.getFirst();
|
|
||||||
if (maxPower != null && maxPower != aiCreature) {
|
|
||||||
power += maxPower.getNetPower(); // potential bonus from adding a second target
|
|
||||||
}
|
|
||||||
if (FightAi.canKill(aiCreature, humanCreature, power)) {
|
|
||||||
sa.getTargets().add(aiCreature);
|
|
||||||
if (maxPower != null) {
|
|
||||||
sa.getTargets().add(maxPower);
|
|
||||||
}
|
|
||||||
if (!isChandrasIgnition) {
|
|
||||||
tgtFight.resetTargets();
|
|
||||||
tgtFight.getTargets().add(humanCreature);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Other cards that use AILogic PowerDmg and a single target
|
|
||||||
if (FightAi.canKill(aiCreature, humanCreature, power)) {
|
|
||||||
sa.getTargets().add(aiCreature);
|
|
||||||
if (!isChandrasIgnition) {
|
|
||||||
tgtFight.resetTargets();
|
|
||||||
tgtFight.getTargets().add(humanCreature);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (FightAi.shouldFight(aiCreature, humanCreature, power, toughness)) {
|
if (FightAi.shouldFight(aiCreature, humanCreature, power, toughness)) {
|
||||||
@@ -252,7 +225,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
for (Trigger t : aiCreature.getTriggers()) {
|
for (Trigger t : aiCreature.getTriggers()) {
|
||||||
if (t.getMode() == TriggerType.SpellCast) {
|
if (t.getMode() == TriggerType.SpellCast) {
|
||||||
final Map<String, String> params = t.getMapParams();
|
final Map<String, String> params = t.getMapParams();
|
||||||
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))
|
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))
|
||||||
&& params.containsKey("Execute")) {
|
&& params.containsKey("Execute")) {
|
||||||
SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
|
SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
|
||||||
if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
|
if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
|
||||||
@@ -273,10 +246,10 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
if (!canKill(opponent, fighter, -pumpDefense)) { // can survive
|
if (!canKill(opponent, fighter, -pumpDefense)) { // can survive
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (MyRandom.getRandom().nextInt(20)<(opponent.getCMC() - fighter.getCMC())) { // trade
|
if (MyRandom.getRandom().nextInt(20)<(opponent.getCMC() - fighter.getCMC())) { // trade
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ public class FlipACoinAi extends SpellAbilityAi {
|
|||||||
if (AILogic.equals("Never")) {
|
if (AILogic.equals("Never")) {
|
||||||
return false;
|
return false;
|
||||||
} else if (AILogic.equals("PhaseOut")) {
|
} else if (AILogic.equals("PhaseOut")) {
|
||||||
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (AILogic.equals("KillOrcs")) {
|
} else if (AILogic.equals("KillOrcs")) {
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ public class FogAi extends SpellAbilityAi {
|
|||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
final Game game = aiPlayer.getGame();
|
final Game game = aiPlayer.getGame();
|
||||||
boolean chance;
|
boolean chance;
|
||||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer().getWeakestOpponent())) {
|
if (game.getPhaseHandler().isPlayerTurn(ComputerUtil.getOpponentFor(sa.getActivatingPlayer()))) {
|
||||||
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
|
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
|
||||||
} else {
|
} else {
|
||||||
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -8,7 +9,7 @@ import forge.game.spellability.TargetRestrictions;
|
|||||||
public class GameLossAi extends SpellAbilityAi {
|
public class GameLossAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
if (opp.cantLose()) {
|
if (opp.cantLose()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -33,16 +34,15 @@ public class GameLossAi extends SpellAbilityAi {
|
|||||||
// Phage the Untouchable
|
// Phage the Untouchable
|
||||||
// (Final Fortune would need to attach it's delayed trigger to a
|
// (Final Fortune would need to attach it's delayed trigger to a
|
||||||
// specific turn, which can't be done yet)
|
// specific turn, which can't be done yet)
|
||||||
Player opp = ai.getWeakestOpponent();
|
|
||||||
|
|
||||||
if (!mandatory && opp.cantLose()) {
|
if (!mandatory && ComputerUtil.getOpponentFor(ai).cantLose()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ public class GameWinAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
return !ai.cantWin();
|
if (ai.cantWin()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Check conditions are met on card (e.g. Coalition Victory)
|
// TODO Check conditions are met on card (e.g. Coalition Victory)
|
||||||
|
|
||||||
@@ -19,6 +21,7 @@ public class GameWinAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// In general, don't return true.
|
// In general, don't return true.
|
||||||
// But this card wins the game, I can make an exception for that
|
// But this card wins the game, I can make an exception for that
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -19,7 +20,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
final int myLife = aiPlayer.getLife();
|
final int myLife = aiPlayer.getLife();
|
||||||
Player opponent = aiPlayer.getWeakestOpponent();
|
Player opponent = ComputerUtil.getOpponentFor(aiPlayer);
|
||||||
final int hLife = opponent.getLife();
|
final int hLife = opponent.getLife();
|
||||||
|
|
||||||
if (!aiPlayer.canGainLife()) {
|
if (!aiPlayer.canGainLife()) {
|
||||||
@@ -75,7 +76,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
|||||||
final boolean mandatory) {
|
final boolean mandatory) {
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
|
|||||||
final boolean mandatory) {
|
final boolean mandatory) {
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||||
|
|||||||
@@ -106,9 +106,13 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return lifeCritical || activateForCost
|
if (!lifeCritical && !activateForCost
|
||||||
|| (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
&& (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||||
|| sa.hasParam("PlayerTurn") || SpellAbilityAi.isSorcerySpeed(sa);
|
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -300,7 +304,9 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
hasTgt = true;
|
hasTgt = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hasTgt;
|
if (!hasTgt) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,11 +51,6 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
if (tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
|
if (tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
return doTgt(ai, sa, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,11 +177,15 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final List<Player> tgtPlayers = sa.usesTargeting() && !sa.hasParam("Defined")
|
final List<Player> tgtPlayers = sa.usesTargeting() && !sa.hasParam("Defined")
|
||||||
? new FCollection<>(sa.getTargets().getTargetPlayers())
|
? new FCollection<Player>(sa.getTargets().getTargetPlayers())
|
||||||
: AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
: AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
// For cards like Foul Imp, ETB you lose life
|
if (!mandatory && tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
|
||||||
return mandatory || !tgtPlayers.contains(ai) || amount <= 0 || amount + 3 <= ai.getLife();
|
// For cards like Foul Imp, ETB you lose life
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -19,7 +20,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
// Ability_Cost abCost = sa.getPayCosts();
|
// Ability_Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final int myLife = ai.getLife();
|
final int myLife = ai.getLife();
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||||
final int hlife = opponent.getLife();
|
final int hlife = opponent.getLife();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
|
|
||||||
@@ -106,7 +107,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final int myLife = ai.getLife();
|
final int myLife = ai.getLife();
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||||
final int hlife = opponent.getLife();
|
final int hlife = opponent.getLife();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
|
|||||||
@@ -80,9 +80,12 @@ public class ManaEffectAi extends SpellAbilityAi {
|
|||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
return true; // handled elsewhere, does not meet the standard requirements
|
return true; // handled elsewhere, does not meet the standard requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
|
if (!(sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
|
||||||
&& sa.getSubAbility() == null && ComputerUtil.playImmediately(ai, sa);
|
&& sa.getSubAbility() == null && ComputerUtil.playImmediately(ai, sa))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
// return super.checkApiLogic(ai, sa);
|
// return super.checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.card.CardStateName;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityKey;
|
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
@@ -19,7 +15,6 @@ import forge.game.player.Player;
|
|||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementLayer;
|
import forge.game.replacement.ReplacementLayer;
|
||||||
import forge.game.replacement.ReplacementType;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
@@ -85,83 +80,65 @@ public class ManifestAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static boolean shouldManyfest(final Card card, final Player ai, final SpellAbility sa) {
|
|
||||||
final Game game = ai.getGame();
|
|
||||||
// check to ensure that there are no replacement effects that prevent creatures ETBing from library
|
|
||||||
// (e.g. Grafdigger's Cage)
|
|
||||||
Card topCopy = CardUtil.getLKICopy(card);
|
|
||||||
topCopy.turnFaceDownNoUpdate();
|
|
||||||
topCopy.setManifested(true);
|
|
||||||
|
|
||||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(topCopy);
|
|
||||||
repParams.put(AbilityKey.Origin, card.getZone().getZoneType());
|
|
||||||
repParams.put(AbilityKey.Destination, ZoneType.Battlefield);
|
|
||||||
repParams.put(AbilityKey.Source, sa.getHostCard());
|
|
||||||
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(ReplacementType.Moved, repParams, ReplacementLayer.Other);
|
|
||||||
if (!list.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (card.mayPlayerLook(ai)) {
|
|
||||||
// try to avoid manifest a non Permanent
|
|
||||||
if (!card.isPermanent())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// do not manifest a card with X in its cost
|
|
||||||
if (card.getManaCost().countX() > 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// try to avoid manifesting a creature with zero or less thoughness
|
|
||||||
if (card.isCreature() && card.getNetToughness() <= 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// card has ETBTrigger or ETBReplacement
|
|
||||||
if (card.hasETBTrigger(false) || card.hasETBReplacement()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final Card host = sa.getHostCard();
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("Choices") || sa.hasParam("ChoiceZone")) {
|
// Library is empty, no Manifest
|
||||||
ZoneType choiceZone = ZoneType.Hand;
|
final CardCollectionView library = ai.getCardsIn(ZoneType.Library);
|
||||||
if (sa.hasParam("ChoiceZone")) {
|
if (library.isEmpty())
|
||||||
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
|
return false;
|
||||||
}
|
|
||||||
CardCollection choices = new CardCollection(game.getCardsIn(choiceZone));
|
// try not to mill himself with Manifest
|
||||||
if (sa.hasParam("Choices")) {
|
if (library.size() < 5 && !ai.isCardInPlay("Laboratory Maniac")) {
|
||||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), ai, host);
|
return false;
|
||||||
}
|
}
|
||||||
if (choices.isEmpty()) {
|
|
||||||
return false;
|
// check to ensure that there are no replacement effects that prevent creatures ETBing from library
|
||||||
}
|
// (e.g. Grafdigger's Cage)
|
||||||
} else {
|
Card topCopy = CardUtil.getLKICopy(library.getFirst());
|
||||||
// Library is empty, no Manifest
|
topCopy.setState(CardStateName.FaceDown, false);
|
||||||
final CardCollectionView library = ai.getCardsIn(ZoneType.Library);
|
topCopy.setManifested(true);
|
||||||
if (library.isEmpty())
|
|
||||||
|
final Map<String, Object> repParams = Maps.newHashMap();
|
||||||
|
repParams.put("Event", "Moved");
|
||||||
|
repParams.put("Affected", topCopy);
|
||||||
|
repParams.put("Origin", ZoneType.Library);
|
||||||
|
repParams.put("Destination", ZoneType.Battlefield);
|
||||||
|
repParams.put("Source", sa.getHostCard());
|
||||||
|
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(repParams, ReplacementLayer.None);
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the AI can see the top card of the library, check it
|
||||||
|
final Card topCard = library.getFirst();
|
||||||
|
if (topCard.mayPlayerLook(ai)) {
|
||||||
|
// try to avoid manifest a non Permanent
|
||||||
|
if (!topCard.isPermanent())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// try not to mill himself with Manifest
|
// do not manifest a card with X in its cost
|
||||||
if (library.size() < 5 && !ai.isCardInPlay("Laboratory Maniac")) {
|
if (topCard.getManaCost().countX() > 0)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (!shouldManyfest(library.getFirst(), ai, sa)) {
|
// try to avoid manifesting a creature with zero or less thoughness
|
||||||
|
if (topCard.isCreature() && topCard.getNetToughness() <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// card has ETBTrigger or ETBReplacement
|
||||||
|
if (topCard.hasETBTrigger(false) || topCard.hasETBReplacement()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Probably should be a little more discerning on playing during OPPs turn
|
// Probably should be a little more discerning on playing during OPPs turn
|
||||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -176,23 +153,4 @@ public class ManifestAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
return MyRandom.getRandom().nextFloat() < .8;
|
return MyRandom.getRandom().nextFloat() < .8;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
|
||||||
if (Iterables.size(options) <= 1) {
|
|
||||||
return Iterables.getFirst(options, null);
|
|
||||||
}
|
|
||||||
CardCollection filtered = CardLists.filter(options, new Predicate<Card>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(Card input) {
|
|
||||||
return !shouldManyfest(input, ai, sa);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!filtered.isEmpty()) {
|
|
||||||
return ComputerUtilCard.getBestAI(filtered);
|
|
||||||
}
|
|
||||||
if (isOptional) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return Iterables.getFirst(options, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,13 +31,19 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
|
|
||||||
if (aiLogic.equals("Main1")) {
|
if (aiLogic.equals("Main1")) {
|
||||||
return !ph.getPhase().isBefore(PhaseType.MAIN2) || sa.hasParam("ActivationPhases")
|
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||||
|| ComputerUtil.castSpellInMain1(ai, sa);
|
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (aiLogic.equals("EndOfOppTurn")) {
|
} else if (aiLogic.equals("EndOfOppTurn")) {
|
||||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai);
|
if (!(ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (aiLogic.equals("LilianaMill")) {
|
} else if (aiLogic.equals("LilianaMill")) {
|
||||||
// Only mill if a "Raise Dead" target is available, in case of control decks with few creatures
|
// Only mill if a "Raise Dead" target is available, in case of control decks with few creatures
|
||||||
return CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES).size() >= 1;
|
if (CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES).size() < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -56,9 +62,11 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sa.getHostCard().isCreature() && sa.getPayCosts().hasTapCost()) {
|
if (sa.getHostCard().isCreature() && sa.getPayCosts().hasTapCost()) {
|
||||||
// creatures with a tap cost to mill (e.g. Doorkeeper) should be activated at the opponent's end step
|
if (!(ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai))) {
|
||||||
// because they are also potentially useful for combat
|
// creatures with a tap cost to mill (e.g. Doorkeeper) should be activated at the opponent's end step
|
||||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai);
|
// because they are also potentially useful for combat
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -92,7 +100,9 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int cardsToDiscard = getNumToDiscard(ai, sa);
|
final int cardsToDiscard = getNumToDiscard(ai, sa);
|
||||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||||
return cardsToDiscard > 0;
|
if (cardsToDiscard <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
|
|
||||||
if (abTgt != null) {
|
if (abTgt != null) {
|
||||||
final List<Card> list = determineGoodBlockers(definedAttacker, ai, ai.getWeakestOpponent(), sa, true,true);
|
final List<Card> list = determineGoodBlockers(definedAttacker, ai, ComputerUtil.getOpponentFor(ai), sa, true,true);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ public class PeekAndRevealAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ("Main2".equals(sa.getParam("AILogic"))) {
|
if ("Main2".equals(sa.getParam("AILogic"))) {
|
||||||
if (aiPlayer.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
if (aiPlayer.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// So far this only appears on Triggers, but will expand
|
// So far this only appears on Triggers, but will expand
|
||||||
// once things get converted from Dig + NoMove
|
// once things get converted from Dig + NoMove
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.game.card.*;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
@@ -13,6 +12,10 @@ import forge.card.CardType.Supertype;
|
|||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GlobalRuleChange;
|
import forge.game.GlobalRuleChange;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollection;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.mana.ManaCostBeingPaid;
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
@@ -38,7 +41,10 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for Main2 if possible
|
// Wait for Main2 if possible
|
||||||
return !ph.is(PhaseType.MAIN1) || !ph.isPlayerTurn(ai) || ComputerUtil.castPermanentInMain1(ai, sa) || sa.hasParam("WithoutManaCost");
|
if (ph.is(PhaseType.MAIN1) && ph.isPlayerTurn(ai) && !ComputerUtil.castPermanentInMain1(ai, sa) && !sa.hasParam("WithoutManaCost")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -192,13 +198,11 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else if (param.startsWith("MaxControlled")) {
|
} else if (param.startsWith("MaxControlled")) {
|
||||||
// Only cast unless there are X or more cards like this on the battlefield under AI control already,
|
// Only cast unless there are X or more cards like this on the battlefield under AI control already,
|
||||||
CardCollectionView valid = param.contains("Globally") ? ai.getGame().getCardsIn(ZoneType.Battlefield)
|
CardCollection ctrld = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(card.getName()));
|
||||||
: ai.getCardsIn(ZoneType.Battlefield);
|
|
||||||
CardCollection ctrld = CardLists.filter(valid, CardPredicates.nameEquals(card.getName()));
|
|
||||||
|
|
||||||
int numControlled = 0;
|
int numControlled = 0;
|
||||||
if (param.endsWith("WithoutOppAuras")) {
|
if (param.endsWith("WithoutOppAuras")) {
|
||||||
// Check that the permanent does not have any auras attached to it by the opponent (this assumes that if
|
// Check that the permanet does not have any auras attached to it by the opponent (this assumes that if
|
||||||
// the opponent cast an aura on the opposing permanent, it's not with good intentions, and thus it might
|
// the opponent cast an aura on the opposing permanent, it's not with good intentions, and thus it might
|
||||||
// be better to have a pristine copy of the card - might not always be a correct assumption, but sounds
|
// be better to have a pristine copy of the card - might not always be a correct assumption, but sounds
|
||||||
// like a reasonable default for some cards).
|
// like a reasonable default for some cards).
|
||||||
@@ -256,7 +260,9 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !dontCast;
|
if (dontCast) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
|
|
||||||
ComputerUtilCard.applyStaticContPT(game, copy, null);
|
ComputerUtilCard.applyStaticContPT(game, copy, null);
|
||||||
|
|
||||||
return copy.getNetToughness() > 0;
|
if (copy.getNetToughness() <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -223,9 +225,13 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
*/
|
*/
|
||||||
final Card copy = CardUtil.getLKICopy(sa.getHostCard());
|
final Card copy = CardUtil.getLKICopy(sa.getHostCard());
|
||||||
ComputerUtilCard.applyStaticContPT(game, copy, null);
|
ComputerUtilCard.applyStaticContPT(game, copy, null);
|
||||||
// AiPlayDecision.WouldBecomeZeroToughnessCreature
|
if (copy.getNetToughness() <= 0 && !copy.hasStartOfKeyword("etbCounter") && mana.countX() == 0
|
||||||
return copy.getNetToughness() > 0 || copy.hasStartOfKeyword("etbCounter") || mana.countX() != 0
|
&& !copy.hasETBTrigger(false) && !copy.hasETBReplacement() && !copy.hasSVar("NoZeroToughnessAI")) {
|
||||||
|| copy.hasETBTrigger(false) || copy.hasETBReplacement() || copy.hasSVar("NoZeroToughnessAI");
|
// AiPlayDecision.WouldBecomeZeroToughnessCreature
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ public class PermanentNoncreatureAi extends PermanentAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
return !"Never".equals(aiLogic) && !"DontCast".equals(aiLogic);
|
if ("Never".equals(aiLogic) || "DontCast".equals(aiLogic)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,8 +54,10 @@ public class PermanentNoncreatureAi extends PermanentAi {
|
|||||||
// TODO: consider replacing the condition with host.hasSVar("OblivionRing")
|
// TODO: consider replacing the condition with host.hasSVar("OblivionRing")
|
||||||
targets = CardLists.filterControlledBy(targets, ai.getOpponents());
|
targets = CardLists.filterControlledBy(targets, ai.getOpponents());
|
||||||
}
|
}
|
||||||
// AiPlayDecision.AnotherTime
|
if (targets.isEmpty()) {
|
||||||
return !targets.isEmpty();
|
// AiPlayDecision.AnotherTime
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user