Compare commits

..

7 Commits

Author SHA1 Message Date
Hanmac
da886202c0 SacrificeEffect now use Remember on SpellAbility 2018-12-26 10:30:56 +01:00
Hanmac
4ab45ae42b AbilityUtils: calcAmount use new getRemembered 2018-12-25 21:37:43 +01:00
Hanmac
bae8819630 Manifest: RememberManifested to SpellAbility 2018-12-25 21:37:43 +01:00
Hanmac
19fbe18235 CountersRemoveAllEffect: use SpellAbility Remember 2018-12-25 21:37:43 +01:00
Hanmac
5e7d542d36 CounterRemove: move remember to SA 2018-12-25 21:37:43 +01:00
Hanmac
5e32834fb8 WrappedAbility: fix SpellAbility remember for trigger 2018-12-25 21:36:54 +01:00
Hanmac
738e68f468 SpellAbility: add Remember to SpellAbility 2018-12-25 21:36:54 +01:00
5247 changed files with 20911 additions and 113414 deletions

7
.classpath Normal file
View 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
View File

@@ -231,8 +231,3 @@ forge-gui/tools/oracleScript.log
/release.properties
/target
/test-output
.settings
.classpath
.project
.vscode/settings.json
.vscode/launch.json

View File

@@ -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

View File

@@ -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
View 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>

View 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

View File

@@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View 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

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

674
LICENSE
View File

@@ -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
View File

@@ -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
View 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
View 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>

View File

@@ -0,0 +1,3 @@
eclipse.preferences.version=1
encoding//src/main/java=ISO-8859-1
encoding/<project>=UTF-8

View 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

View File

@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.30-SNAPSHOT</version>
<version>1.6.20-SNAPSHOT</version>
</parent>
<artifactId>forge-ai</artifactId>

View File

@@ -1,5 +1,5 @@
package forge.ai;
public enum AIOption {
USE_SIMULATION
USE_SIMULATION;
}

View File

@@ -81,7 +81,7 @@ public class AiAttackController {
this.defendingOpponent = choosePreferredDefenderPlayer();
this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<>();
this.attackers = new ArrayList<Card>();
for (Card c : myList) {
if (CombatUtil.canAttack(c, this.defendingOpponent)) {
attackers.add(c);
@@ -95,7 +95,7 @@ public class AiAttackController {
this.defendingOpponent = choosePreferredDefenderPlayer();
this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<>();
this.attackers = new ArrayList<Card>();
if (CombatUtil.canAttack(attacker, this.defendingOpponent)) {
attackers.add(attacker);
}
@@ -103,7 +103,8 @@ public class AiAttackController {
} // overloaded constructor to evaluate single specified attacker
public static List<Card> getOpponentCreatures(final Player defender) {
List<Card> defenders = new ArrayList<>(defender.getCreaturesInPlay());
List<Card> defenders = new ArrayList<Card>();
defenders.addAll(defender.getCreaturesInPlay());
Predicate<Card> canAnimate = new Predicate<Card>() {
@Override
public boolean apply(Card c) {
@@ -150,7 +151,7 @@ public class AiAttackController {
*
*/
public final static List<Card> sortAttackers(final List<Card> in) {
final List<Card> list = new ArrayList<>();
final List<Card> list = new ArrayList<Card>();
// Cards with triggers should come first (for Battle Cry)
for (final Card attacker : in) {
@@ -190,48 +191,14 @@ public class AiAttackController {
return false;
}
// the attacker will die to a triggered ability (e.g. Sarkhan the Masterless)
for (Card c : ai.getOpponents().getCardsIn(ZoneType.Battlefield)) {
for (Trigger t : c.getTriggers()) {
if (t.getMode() == TriggerType.Attacks) {
SpellAbility sa = t.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"))) {
return true;
}
final Player opp = this.defendingOpponent;
// Damage opponent if unblocked
final int dmgIfUnblocked = ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat, true);
if (dmgIfUnblocked > 0) {
boolean onlyIfExalted = false;
if (combat.getAttackers().isEmpty() && ai.countExaltedBonus() > 0
&& dmgIfUnblocked - ai.countExaltedBonus() == 0) {
// Make sure we're not counting on the Exalted bonus when the AI is planning to attack with more than one creature
onlyIfExalted = true;
}
if (!onlyIfExalted || this.attackers.size() == 1 || this.aiAggression == 6 /* 6 is Exalted attack */) {
return true;
}
if (ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat, true) > 0) {
return true;
}
// Poison opponent if unblocked
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) {
return true;
}
@@ -246,7 +213,7 @@ public class AiAttackController {
final CardCollectionView controlledByCompy = ai.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES);
for (final Card c : controlledByCompy) {
for (final Trigger trigger : c.getTriggers()) {
if (ComputerUtilCombat.combatTriggerWillTrigger(attacker, null, trigger, combat, this.attackers)) {
if (ComputerUtilCombat.combatTriggerWillTrigger(attacker, null, trigger, combat)) {
return true;
}
}
@@ -255,7 +222,7 @@ public class AiAttackController {
}
public final static List<Card> getPossibleBlockers(final List<Card> blockers, final List<Card> attackers) {
List<Card> possibleBlockers = new ArrayList<>(blockers);
List<Card> possibleBlockers = new ArrayList<Card>(blockers);
possibleBlockers = CardLists.filter(possibleBlockers, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
@@ -266,7 +233,7 @@ public class AiAttackController {
}
public final static boolean canBlockAnAttacker(final Card c, final List<Card> attackers, final boolean nextTurn) {
final List<Card> attackerList = new ArrayList<>(attackers);
final List<Card> attackerList = new ArrayList<Card>(attackers);
if (!c.isCreature()) {
return false;
}
@@ -279,7 +246,7 @@ public class AiAttackController {
}
public final static Card getCardCanBlockAnAttacker(final Card c, final List<Card> attackers, final boolean nextTurn) {
final List<Card> attackerList = new ArrayList<>(attackers);
final List<Card> attackerList = new ArrayList<Card>(attackers);
if (!c.isCreature()) {
return null;
}
@@ -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 method is used by getAttackers()
public final List<Card> notNeededAsBlockers(final Player ai, final List<Card> attackers) {
final List<Card> notNeededAsBlockers = new ArrayList<>(attackers);
final List<Card> notNeededAsBlockers = new ArrayList<Card>(attackers);
int fixedBlockers = 0;
final List<Card> vigilantes = new ArrayList<>();
final List<Card> vigilantes = new ArrayList<Card>();
//check for time walks
if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) {
return attackers;
@@ -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>() {
@Override
public boolean apply(final Card c) {
@@ -547,7 +514,8 @@ public class AiAttackController {
remainingAttackers.removeAll(unblockedAttackers);
for (Card blocker : this.blockers) {
if (blocker.canBlockAny()) {
if (blocker.hasKeyword("CARDNAME can block any number of creatures.")
|| blocker.hasKeyword("CARDNAME can block an additional ninety-nine creatures each combat.")) {
for (Card attacker : this.attackers) {
if (CombatUtil.canBlock(attacker, blocker)) {
remainingAttackers.remove(attacker);
@@ -563,19 +531,14 @@ public class AiAttackController {
if (remainingAttackers.isEmpty() || maxBlockersAfterCrew == 0) {
break;
}
int numExtraBlocks = blocker.canBlockAdditional();
if (numExtraBlocks > 0) {
while (numExtraBlocks-- > 0 && !remainingAttackers.isEmpty()) {
blockedAttackers.add(remainingAttackers.get(0));
remainingAttackers.remove(0);
maxBlockersAfterCrew--;
if (blocker.hasKeyword("CARDNAME can block an additional creature each combat.")) {
blockedAttackers.add(remainingAttackers.get(0));
remainingAttackers.remove(0);
maxBlockersAfterCrew--;
if (remainingAttackers.isEmpty()) {
break;
}
}
if (remainingAttackers.isEmpty()) {
break;
}
blockedAttackers.add(remainingAttackers.get(0));
remainingAttackers.remove(0);
maxBlockersAfterCrew--;
@@ -684,7 +647,7 @@ public class AiAttackController {
// Determine who will be attacked
GameEntity defender = this.chooseDefender(combat, bAssault);
List<Card> attackersLeft = new ArrayList<>(this.attackers);
List<Card> attackersLeft = new ArrayList<Card>(this.attackers);
// TODO probably use AttackConstraints instead of only GlobalAttackRestrictions?
GlobalAttackRestrictions restrict = GlobalAttackRestrictions.getGlobalRestrictions(ai, combat.getDefenders());
@@ -824,12 +787,12 @@ public class AiAttackController {
int humanForcesForAttritionalAttack = 0;
// examine the potential forces
final List<Card> nextTurnAttackers = new ArrayList<>();
final List<Card> nextTurnAttackers = new ArrayList<Card>();
int candidateCounterAttackDamage = 0;
final Player opp = this.defendingOpponent;
// get the potential damage and strength of the AI forces
final List<Card> candidateAttackers = new ArrayList<>();
final List<Card> candidateAttackers = new ArrayList<Card>();
int candidateUnblockedDamage = 0;
for (final Card pCard : this.myList) {
// if the creature can attack then it's a potential attacker this
@@ -888,7 +851,7 @@ public class AiAttackController {
final int outNumber = computerForces - humanForces;
for (Card blocker : this.blockers) {
if (blocker.canBlockAny()) {
if (blocker.hasKeyword("CARDNAME can block any number of creatures.")) {
aiLifeToPlayerDamageRatio--;
}
}
@@ -911,7 +874,7 @@ public class AiAttackController {
// get player life total
int humanLife = opp.getLife();
// get the list of attackers up to the first blocked one
final List<Card> attritionalAttackers = new ArrayList<>();
final List<Card> attritionalAttackers = new ArrayList<Card>();
for (int x = 0; x < (this.attackers.size() - humanForces); x++) {
attritionalAttackers.add(this.attackers.get(x));
}
@@ -1024,7 +987,7 @@ public class AiAttackController {
} // stay at home to block
if ( LOG_AI_ATTACKS )
System.out.println(this.aiAggression + " = ai aggression");
System.out.println(String.valueOf(this.aiAggression) + " = ai aggression");
// ****************
// Evaluation the end
@@ -1156,21 +1119,8 @@ public class AiAttackController {
// is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...)
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
|| "Blocked".equals(attacker.getSVar("HasAttackEffect"));
// contains only the defender's blockers that can actually block the attacker
CardCollection validBlockers = CardLists.filter(defenders, new Predicate<Card>() {
@Override
public boolean apply(Card defender) {
return CombatUtil.canBlock(attacker, defender);
}
});
// used to check that CanKillAllDangerous check makes sense in context where creatures with dangerous abilities are present
boolean dangerousBlockersPresent = !CardLists.filter(validBlockers, Predicates.or(
CardPredicates.hasKeyword(Keyword.WITHER), CardPredicates.hasKeyword(Keyword.INFECT),
CardPredicates.hasKeyword(Keyword.LIFELINK))).isEmpty();
// total power of the defending creatures, used in predicting whether a gang block can kill the attacker
int defPower = CardLists.getTotalPower(validBlockers, true);
int defPower = CardLists.getTotalPower(defenders, true);
if (!hasCombatEffect) {
for (KeywordInterface inst : attacker.getKeywords()) {
@@ -1187,9 +1137,10 @@ public class AiAttackController {
// number of factors about the attacking
// context that will be relevant to the attackers decision according to
// the selected strategy
for (final Card defender : validBlockers) {
for (final Card defender : defenders) {
// if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
if ((isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2)
&& CombatUtil.canBlock(attacker, defender)) {
numberOfPossibleBlockers += 1;
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterType.P1P1) == 0)) {
@@ -1227,7 +1178,7 @@ public class AiAttackController {
// - our creature will die for sure (chump attack)
// - our attack will not do anything special (no attack/combat effect to proc)
// - we can't deal damage to our opponent with sheer number of attackers and/or our attacker's power is 0 or less
if (attackerWillDie || (avoidAttackingIntoBlock && uselessAttack && noContributionToAttack)) {
if (attackerWillDie || (avoidAttackingIntoBlock && (uselessAttack || noContributionToAttack))) {
canKillAllDangerous = false;
}
}
@@ -1276,9 +1227,8 @@ public class AiAttackController {
if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = all out attacking");
return true;
case 4: // expecting to at least trade with something, or can attack "for free", expecting no counterattack
if (canKillAll || (dangerousBlockersPresent && canKillAllDangerous && !canBeKilledByOne) || !canBeBlocked
|| (defPower == 0 && !ComputerUtilCombat.lifeInDanger(ai, combat))) {
case 4: // expecting to at least trade with something
if (canKillAll || (canKillAllDangerous && !canBeKilledByOne) || !canBeBlocked) {
if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting to at least trade with something");
return true;
@@ -1286,7 +1236,7 @@ public class AiAttackController {
break;
case 3: // expecting to at least kill a creature of equal value or not be blocked
if ((canKillAll && isWorthLessThanAllKillers)
|| (((dangerousBlockersPresent && canKillAllDangerous) || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne)
|| ((canKillAllDangerous || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne)
|| !canBeBlocked) {
if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting to kill creature or cause damage, or is unblockable");
@@ -1295,7 +1245,7 @@ public class AiAttackController {
break;
case 2: // attack expecting to attract a group block or destroying a single blocker and surviving
if (!canBeBlocked || ((canKillAll || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne &&
((dangerousBlockersPresent && canKillAllDangerous) || !canBeKilled))) {
(canKillAllDangerous || !canBeKilled))) {
if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting to survive or attract group block");
return true;
@@ -1460,7 +1410,7 @@ public class AiAttackController {
if (artifact != null) {
return artifact;
}
return null;//should never get here
return null; //should never get here
}
private void doLightmineFieldAttackLogic(List<Card> attackersLeft, int numForcedAttackers, boolean playAggro) {

View File

@@ -22,6 +22,7 @@ import com.google.common.base.Predicates;
import forge.card.CardStateName;
import forge.game.CardTraitBase;
import forge.game.GameEntity;
import forge.game.GlobalRuleChange;
import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
@@ -147,7 +148,9 @@ public class AiBlockController {
final CardCollection attackers = combat.getAttackersOf(defender);
// Begin with the attackers that pose the biggest threat
CardLists.sortByPowerDesc(attackers);
sortedAttackers.addAll(attackers);
for (final Card c : attackers) {
sortedAttackers.add(c);
}
} else if (defender instanceof Player && defender.equals(ai)) {
firstAttacker = combat.getAttackersOf(defender);
}
@@ -160,7 +163,9 @@ public class AiBlockController {
}
} else {
// add creatures attacking the Player to the back of the list
sortedAttackers.addAll(firstAttacker);
for (final Card c : firstAttacker) {
sortedAttackers.add(c);
}
}
return sortedAttackers;
}
@@ -476,7 +481,8 @@ public class AiBlockController {
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, secondBlocker, combat, false);
List<Card> usableBlockersAsThird = new ArrayList<>(usableBlockers);
List<Card> usableBlockersAsThird = new ArrayList<>();
usableBlockersAsThird.addAll(usableBlockers);
usableBlockersAsThird.remove(secondBlocker);
// loop over the remaining blockers in search of a good third blocker candidate
@@ -767,12 +773,14 @@ public class AiBlockController {
blockers.removeAll(combat.getBlockers(attacker));
// Don't add any blockers that won't kill the attacker because the damage would be prevented by a static effect
blockers = CardLists.filter(blockers, new Predicate<Card>() {
@Override
public boolean apply(Card blocker) {
return !ComputerUtilCombat.isCombatDamagePrevented(blocker, attacker, blocker.getNetCombatDamage());
}
});
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) {
blockers = CardLists.filter(blockers, new Predicate<Card>() {
@Override
public boolean apply(Card blocker) {
return !ComputerUtilCombat.isCombatDamagePrevented(blocker, attacker, blocker.getNetCombatDamage());
}
});
}
// Try to use safe blockers first
if (blockers.size() > 0) {
@@ -851,7 +859,7 @@ public class AiBlockController {
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);
}
}
@@ -871,7 +879,7 @@ public class AiBlockController {
if (!chumpPWDefenders.isEmpty()) {
for (final Card attacker : attackers) {
GameEntity def = combat.getDefenderByAttacker(attacker);
if (def instanceof Card && threatenedPWs.contains(def)) {
if (def instanceof Card && threatenedPWs.contains((Card) def)) {
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
// don't bother trying to chump a trampling creature
continue;
@@ -906,7 +914,7 @@ public class AiBlockController {
pwDefenders.addAll(combat.getBlockers(pwAtk));
} else {
isFullyBlocked = false;
damageToPW += ComputerUtilCombat.predictDamageTo(pw, pwAtk.getNetCombatDamage(), pwAtk, true);
damageToPW += ComputerUtilCombat.predictDamageTo((Card) pw, pwAtk.getNetCombatDamage(), pwAtk, true);
}
}
if (!isFullyBlocked && damageToPW >= pw.getCounters(CounterType.LOYALTY)) {
@@ -1321,9 +1329,13 @@ public class AiBlockController {
&& ((Card) combat.getDefenderByAttacker(attacker)).isPlaneswalker();
boolean wantToTradeDownToSavePW = chanceToTradeDownToSaveWalker > 0;
return ((evalBlk <= evalAtk + 1) || (wantToSavePlaneswalker && wantToTradeDownToSavePW)) // "1" accounts for tapped.
if (((evalBlk <= evalAtk + 1) || (wantToSavePlaneswalker && wantToTradeDownToSavePW)) // "1" accounts for tapped.
&& powerParityOrHigher
&& (creatureParityOrAllowedDiff || wantToTradeWithCreatInHand)
&& (MyRandom.percentTrue(chance) || wantToSavePlaneswalker);
&& (MyRandom.percentTrue(chance) || wantToSavePlaneswalker)) {
return true;
}
return false;
}
}

View File

@@ -137,7 +137,7 @@ public class AiCardMemory {
Set<Card> memorySet = getMemorySet(set);
return memorySet != null && memorySet.contains(c);
return memorySet == null ? false : memorySet.contains(c);
}
/**
@@ -150,15 +150,12 @@ public class AiCardMemory {
*/
public boolean isRememberedCardByName(String cardName, MemorySet set) {
Set<Card> memorySet = getMemorySet(set);
Iterator<Card> it = memorySet.iterator();
if (memorySet != null) {
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName)) {
return true;
}
while (it.hasNext()) {
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) {
Set<Card> memorySet = getMemorySet(set);
Iterator<Card> it = memorySet.iterator();
if (memorySet != null) {
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
return true;
}
while (it.hasNext()) {
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)
return false;
Set<Card> memorySet = getMemorySet(set);
if (memorySet != null) {
memorySet.add(c);
}
getMemorySet(set).add(c);
return true;
}
@@ -227,12 +216,7 @@ public class AiCardMemory {
return false;
}
Set<Card> memorySet = getMemorySet(set);
if (memorySet != null) {
memorySet.remove(c);
}
getMemorySet(set).remove(c);
return true;
}
@@ -245,15 +229,12 @@ public class AiCardMemory {
*/
public boolean forgetAnyCardWithName(String cardName, MemorySet set) {
Set<Card> memorySet = getMemorySet(set);
Iterator<Card> it = memorySet.iterator();
if (memorySet != null) {
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName)) {
return forgetCard(c, set);
}
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName)) {
return forgetCard(c, set);
}
}
@@ -270,15 +251,12 @@ public class AiCardMemory {
*/
public boolean forgetAnyCardWithName(String cardName, MemorySet set, Player owner) {
Set<Card> memorySet = getMemorySet(set);
Iterator<Card> it = memorySet.iterator();
if (memorySet != null) {
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
return forgetCard(c, set);
}
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
return forgetCard(c, set);
}
}
@@ -291,16 +269,14 @@ public class AiCardMemory {
* @return true, if the given memory set contains no remembered cards.
*/
public boolean isMemorySetEmpty(MemorySet set) {
return set == null || getMemorySet(set).isEmpty();
return getMemorySet(set).isEmpty();
}
/**
* Clears the given memory set.
*/
public void clearMemorySet(MemorySet set) {
if (set != null) {
getMemorySet(set).clear();
}
getMemorySet(set).clear();
}
/**

View File

@@ -18,10 +18,12 @@
package forge.ai;
import com.esotericsoftware.minlog.Log;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.ai.ability.ChangeZoneAi;
import forge.ai.ability.ExploreAi;
import forge.ai.simulation.SpellAbilityPicker;
@@ -48,7 +50,6 @@ import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.replacement.ReplaceMoved;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
@@ -59,7 +60,6 @@ import forge.item.PaperCard;
import forge.util.Aggregates;
import forge.util.Expressions;
import forge.util.MyRandom;
import forge.util.ComparatorUtil;
import forge.util.collect.FCollectionView;
import io.sentry.Sentry;
import io.sentry.event.BreadcrumbBuilder;
@@ -610,15 +610,7 @@ public class AiController {
ComputerUtilAbility.getAvailableCards(game, player);
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player);
try {
Collections.sort(all, saComparator); // put best spells first
}
catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage());
String assertex = ComparatorUtil.verifyTransitivity(saComparator, all);
Sentry.capture(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
}
Collections.sort(all, saComparator); // put best spells first
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
ApiType saApi = sa.getApi();
@@ -665,10 +657,10 @@ public class AiController {
return false;
}
AiCardMemory.MemorySet memSet = null;
AiCardMemory.MemorySet memSet;
if (phaseType == null && forNextSpell) {
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL;
} else if (phaseType != null) {
} else {
switch (phaseType) {
case MAIN2:
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2;
@@ -1020,7 +1012,7 @@ public class AiController {
p += 9;
}
// sort planeswalker abilities with most costly first
if (sa.isPwAbility()) {
if (sa.getRestrictions().isPwAbility()) {
final CostPart cost = sa.getPayCosts().getCostParts().get(0);
if (cost instanceof CostRemoveCounter) {
p += cost.convertAmount() == null ? 1 : cost.convertAmount();
@@ -1052,10 +1044,9 @@ public class AiController {
}
public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa, final CardCollectionView exclude) {
boolean noFiltering = (sa != null) && "DiscardCMCX".equals(sa.getParam("AILogic")); // list AI logic for which filtering is taken care of elsewhere
CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
hand.removeAll(exclude);
if ((uTypes != null) && (sa != null) && !noFiltering) {
if ((uTypes != null) && (sa != null)) {
hand = CardLists.getValidCards(hand, uTypes, sa.getActivatingPlayer(), sa.getHostCard(), sa);
}
return getCardsToDiscard(numDiscard, numDiscard, hand, sa);
@@ -1075,14 +1066,6 @@ public class AiController {
min = 1;
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) {
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
final int CMC = 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")) {
@@ -1228,7 +1211,7 @@ public class AiController {
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
ApiType api = sa.getApi();
// Abilities without api may also use this routine, However they should provide a unique mode value ?? How could this work?
// Abilities without api may also use this routine, However they should provide a unique mode value
if (api == null) {
String exMsg = String.format("AI confirmAction does not know what to decide about %s mode (api is null).",
mode);
@@ -1581,14 +1564,7 @@ public class AiController {
if (all == null || all.isEmpty())
return null;
try {
Collections.sort(all, saComparator); // put best spells first
}
catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage());
String assertex = ComparatorUtil.verifyTransitivity(saComparator, all);
Sentry.capture(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
}
Collections.sort(all, saComparator); // put best spells first
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
// Don't add Counterspells to the "normal" playcard lookups
@@ -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
return true;
}
return false;
}
@@ -1677,14 +1654,14 @@ public class AiController {
hostCard = game.getCardState(hostCard);
}
if (effect.hasParam("AICheckSVar")) {
if (effect.getMapParams().containsKey("AICheckSVar")) {
System.out.println("aiShouldRun?" + sa);
final String svarToCheck = effect.getParam("AICheckSVar");
final String svarToCheck = effect.getMapParams().get("AICheckSVar");
String comparator = "GE";
int compareTo = 1;
if (effect.hasParam("AISVarCompare")) {
final String fullCmp = effect.getParam("AISVarCompare");
if (effect.getMapParams().containsKey("AISVarCompare")) {
final String fullCmp = effect.getMapParams().get("AISVarCompare");
comparator = fullCmp.substring(0, 2);
final String strCmpTo = fullCmp.substring(2);
try {
@@ -1706,11 +1683,16 @@ public class AiController {
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
}
System.out.println("aiShouldRun?" + left + comparator + compareTo);
return Expressions.compare(left, comparator, compareTo);
} else if (effect.hasParam("AICheckDredge")) {
if (Expressions.compare(left, comparator, compareTo)) {
return true;
}
} else if (effect.getMapParams().containsKey("AICheckDredge")) {
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
} else return sa != null && doTrigger(sa, false);
} else if (sa != null && doTrigger(sa, false)) {
return true;
}
return false;
}
public List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand) {
@@ -1789,7 +1771,7 @@ public class AiController {
+ MyRandom.getRandom().nextInt(3);
return Math.max(remaining, min) / 2;
} else if ("LowestLoseLife".equals(logic)) {
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, ComputerUtil.getOpponentFor(player).getLife())) + 1;
} else if ("HighestGetCounter".equals(logic)) {
return MyRandom.getRandom().nextInt(3);
} else if (source.hasSVar("EnergyToPay")) {
@@ -1802,6 +1784,75 @@ public class AiController {
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) {
if (sa == null || sa.getApi() == null) {
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
// TODO: consider enhancing support for tapXType<Any/...> in UnlessCost to get rid of this hack
if ("BowToMyCommand".equals(sa.getParam("AILogic"))) {
if (!sa.getHostCard().isInZone(ZoneType.Command)) {
if (!sa.getHostCard().getZone().is(ZoneType.Command)) {
// Make sure that other opponents do not tap for an already abandoned scheme
result.clear();
break;
@@ -2089,47 +2140,50 @@ public class AiController {
// AI-specific restrictions specified as activation parameters in spell abilities
if (sa.hasParam("AILifeThreshold")) {
return player.getLife() > Integer.parseInt(sa.getParam("AILifeThreshold"));
if (player.getLife() <= Integer.parseInt(sa.getParam("AILifeThreshold"))) {
return false;
}
}
return true;
}
public ReplacementEffect chooseSingleReplacementEffect(List<ReplacementEffect> list) {
public ReplacementEffect chooseSingleReplacementEffect(List<ReplacementEffect> list,
Map<String, Object> runParams) {
// no need to choose anything
if (list.size() <= 1) {
return Iterables.getFirst(list, null);
}
ReplacementType mode = Iterables.getFirst(list, null).getMode();
if (runParams.containsKey("Event")) {
// replace lifegain effects
if ("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 (mode.equals(ReplacementType.GainLife)) {
List<ReplacementEffect> noGain = filterListByAiLogic(list, "NoLife");
List<ReplacementEffect> loseLife = filterListByAiLogic(list, "LoseLife");
List<ReplacementEffect> doubleLife = filterListByAiLogic(list, "DoubleLife");
List<ReplacementEffect> lichDraw = filterListByAiLogic(list, "LichDraw");
if (!noGain.isEmpty()) {
// no lifegain is better than lose life
return Iterables.getFirst(noGain, null);
} else if (!loseLife.isEmpty()) {
// 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 ("DamageDone".equals(runParams.get("Event"))) {
List<ReplacementEffect> prevention = filterList(list, CardTraitPredicates.hasParam("Prevention"));
if (!noGain.isEmpty()) {
// no lifegain is better than lose life
return Iterables.getFirst(noGain, null);
} else if (!loseLife.isEmpty()) {
// 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);
// TODO when Protection is done as ReplacementEffect do them
// before normal prevention
if (!prevention.isEmpty()) {
return Iterables.getFirst(prevention, null);
}
}
}

View File

@@ -56,7 +56,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostChooseCreatureType cost) {
String choice = player.getController().chooseSomeType("Creature", ability, CardType.getAllCreatureTypes(),
Lists.newArrayList());
Lists.<String>newArrayList());
return PaymentDecision.type(choice);
}
@@ -475,7 +475,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
if (ability.getPayCosts().hasTapCost() && typeList.contains(ability.getHostCard())) {
c--;
}
source.setSVar("ChosenX", "Number$" + c);
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
} else {
if (!isVehicle) {
c = AbilityUtils.calculateAmount(source, amount, ability);
@@ -809,7 +809,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
final String sVar = ability.getSVar(amount);
if (sVar.equals("XChoice")) {
c = AbilityUtils.calculateAmount(source, "ChosenX", ability);
source.setSVar("ChosenX", "Number$" + c);
source.setSVar("ChosenX", "Number$" + String.valueOf(c));
} else if (amount.equals("All")) {
c = source.getCounters(cost.counter);
} 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 {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
@@ -865,7 +863,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
}
typeList = CardLists.filter(typeList, Presets.TAPPED);
c = typeList.size();
source.setSVar("ChosenX", "Number$" + c);
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
} else {
c = AbilityUtils.calculateAmount(source, amount, ability);
}

View File

@@ -17,5 +17,5 @@ public enum AiPlayDecision {
WouldBecomeZeroToughnessCreature,
WouldDestroyWorldEnchantment,
BadEtbEffects,
CurseEffects
CurseEffects;
}

View File

@@ -39,7 +39,7 @@ import java.util.Map;
* @version $Id: AIProfile.java 20169 2013-03-08 08:24:17Z Agetian $
*/
public class AiProfileUtil {
private static Map<String, Map<AiProps, String>> loadedProfiles = new HashMap<>();
private static Map<String, Map<AiProps, String>> loadedProfiles = new HashMap<String, Map<AiProps, String>>();
private static String AI_PROFILE_DIR;
private static final String AI_PROFILE_EXT = ".ai";
@@ -74,7 +74,7 @@ public class AiProfileUtil {
* @param profileName a profile to load.
*/
private static final Map<AiProps, String> loadProfile(final String profileName) {
Map<AiProps, String> profileMap = new HashMap<>();
Map<AiProps, String> profileMap = new HashMap<AiProps, String>();
List<String> lines = FileUtil.readFile(buildFileName(profileName));
for (String line : lines) {
@@ -122,7 +122,7 @@ public class AiProfileUtil {
*/
public static List<String> getAvailableProfiles()
{
final List<String> availableProfiles = new ArrayList<>();
final List<String> availableProfiles = new ArrayList<String>();
final File dir = new File(AI_PROFILE_DIR);
final String[] children = dir.list();
@@ -146,7 +146,7 @@ public class AiProfileUtil {
* available profiles including special random profile tags.
*/
public static List<String> getProfilesDisplayList() {
final List<String> availableProfiles = new ArrayList<>();
final List<String> availableProfiles = new ArrayList<String>();
availableProfiles.add(AI_PROFILE_RANDOM_MATCH);
availableProfiles.add(AI_PROFILE_RANDOM_DUEL);
availableProfiles.addAll(getAvailableProfiles());

View File

@@ -129,10 +129,7 @@ public enum AiProps { /** */
FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS("true"),
FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY("1"),
FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT("5"),
FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("100"),
BLINK_RELOAD_PLANESWALKER_CHANCE("30"), /** */
BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY("2"), /** */
BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF("2"); /** */
FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("100"); /** */
// Experimental features, must be promoted or removed after extensive testing and, ideally, defaulting
// <-- There are no experimental options here -->

View File

@@ -29,7 +29,6 @@ import forge.card.MagicColor;
import forge.card.mana.ManaCostShard;
import forge.game.*;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect;
@@ -44,12 +43,10 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementLayer;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.trigger.WrappedAbility;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
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) {
game.getStack().freezeStack();
final Card source = sa.getHostCard();
source.setSplitStateToPlayAbility(sa);
if (sa.isSpell() && !source.isCopiedSpell()) {
sa = AbilityUtils.addSpliceEffects(sa);
if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty() && ai.getController().isAI()) {
// we need to reconsider and retarget the SA after additional SAs have been added onto it via splice,
// otherwise the AI will fail to add the card to stack and that'll knock it out of the game
sa.resetTargets();
if (((PlayerControllerAi) ai.getController()).getAi().canPlaySa(sa) != AiPlayDecision.WillPlay) {
// for whatever reason the AI doesn't want to play the thing with the spliced subs anymore,
// proceeding past this point may result in an illegal play
return false;
if (source.getType().hasStringType("Arcane")) {
sa = AbilityUtils.addSpliceEffects(sa);
if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty() && ai.getController().isAI()) {
// we need to reconsider and retarget the SA after additional SAs have been added onto it via splice,
// otherwise the AI will fail to add the card to stack and that'll knock it out of the game
sa.resetTargets();
if (((PlayerControllerAi) ai.getController()).getAi().canPlaySa(sa) != AiPlayDecision.WillPlay) {
// for whatever reason the AI doesn't want to play the thing with the spliced subs anymore,
// 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));
}
if (!sa.isCopied()) {
if (sa.isCopied()) {
sa.resetPaidHash();
}
sa = GameActionUtil.addExtraKeywordCost(sa);
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
CharmEffect.makeChoices(sa);
}
@@ -212,7 +208,7 @@ public class ComputerUtil {
}
// 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);
if (!ComputerUtilCost.canPayCost(sa, ai))
return false;
@@ -224,9 +220,6 @@ public class ComputerUtil {
sa.setLastStateGraveyard(game.getLastStateGraveyard());
sa.setHostCard(game.getAction().moveToStack(source, sa));
}
sa = GameActionUtil.addExtraKeywordCost(sa);
final Cost cost = sa.getPayCosts();
if (cost == null) {
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) {
SpellAbility newSA = sa.copyWithNoManaCost();
final SpellAbility newSA = sa.copyWithNoManaCost();
newSA.setActivatingPlayer(ai);
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) {
return false;
}
newSA = GameActionUtil.addExtraKeywordCost(newSA);
final Card source = newSA.getHostCard();
if (newSA.isSpell() && !source.isCopiedSpell()) {
source.setCastSA(newSA);
@@ -284,7 +275,7 @@ public class ComputerUtil {
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);
// TODO: We should really restrict what doesn't use the Stack
if (ComputerUtilCost.canPayCost(sa, ai)) {
@@ -296,8 +287,6 @@ public class ComputerUtil {
sa.setHostCard(game.getAction().moveToStack(source, sa));
}
sa = GameActionUtil.addExtraKeywordCost(sa);
final Cost cost = sa.getPayCosts();
if (cost == null) {
ComputerUtilMana.payManaCost(ai, sa);
@@ -425,7 +414,7 @@ public class ComputerUtil {
int mana = ComputerUtilMana.getAvailableManaEstimate(ai, false);
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
// 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));
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
}
@@ -556,7 +545,7 @@ public class ComputerUtil {
final Card target, final int amount) {
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
}
@@ -577,7 +566,7 @@ public class ComputerUtil {
final Card target, final int amount) {
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
}
@@ -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) {
final CardCollection typeList =
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
typeList.remove(target);
}
@@ -756,7 +745,7 @@ public class ComputerUtil {
if (source.hasParam("Exploit")) {
for (Trigger t : host.getTriggers()) {
if (t.getMode() == TriggerType.Exploited) {
final String execute = t.getParam("Execute");
final String execute = t.getMapParams().get("Execute");
if (execute == null) {
continue;
}
@@ -808,7 +797,7 @@ public class ComputerUtil {
final int max = Math.min(remaining.size(), amount);
if (exceptSelf && max < remaining.size()) {
if (exceptSelf) {
removedSelf = remaining.remove(source.getHostCard());
}
@@ -1237,7 +1226,7 @@ public class ComputerUtil {
//Fill the graveyard for Threshold
if (checkThreshold) {
for (StaticAbility stAb : buffedCard.getStaticAbilities()) {
if ("Threshold".equals(stAb.getParam("Condition"))) {
if ("Threshold".equals(stAb.getMapParams().get("Condition"))) {
return true;
}
}
@@ -1265,9 +1254,9 @@ public class ComputerUtil {
// returns true if the AI should stop using the ability
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
}
@@ -1516,7 +1505,7 @@ public class ComputerUtil {
*/
public static List<GameObject> predictThreatenedObjects(final Player ai, final SpellAbility sa, boolean top) {
final Game game = ai.getGame();
final List<GameObject> objects = new ArrayList<>();
final List<GameObject> objects = new ArrayList<GameObject>();
if (game.getStack().isEmpty()) {
return objects;
}
@@ -1526,9 +1515,6 @@ public class ComputerUtil {
// iterate from top of stack to find SpellAbility, including sub-abilities,
// that does not match "sa"
SpellAbility spell = si.getSpellAbility(true), sub = spell.getSubAbility();
if (spell.isWrapper()) {
spell = ((WrappedAbility) spell).getWrappedAbility();
}
while (sub != null && sub != sa) {
sub = sub.getSubAbility();
}
@@ -1545,8 +1531,8 @@ public class ComputerUtil {
private static Iterable<? extends GameObject> predictThreatenedObjects(final Player aiPlayer, final SpellAbility saviour,
final SpellAbility topStack) {
Iterable<? extends GameObject> objects = new ArrayList<>();
final List<GameObject> threatened = new ArrayList<>();
Iterable<? extends GameObject> objects = new ArrayList<GameObject>();
final List<GameObject> threatened = new ArrayList<GameObject>();
ApiType saviourApi = saviour == null ? null : saviour.getApi();
int toughness = 0;
boolean grantIndestructible = false;
@@ -1576,7 +1562,7 @@ public class ComputerUtil {
}
} else {
objects = topStack.getTargets().getTargets();
final List<GameObject> canBeTargeted = new ArrayList<>();
final List<GameObject> canBeTargeted = new ArrayList<GameObject>();
for (Object o : objects) {
if (o instanceof Card) {
final Card c = (Card) o;
@@ -1599,7 +1585,7 @@ public class ComputerUtil {
toughness = saviorWithSubs.hasParam("NumDef") ?
AbilityUtils.calculateAmount(saviorWithSubs.getHostCard(), saviorWithSubs.getParam("NumDef"), saviour) : 0;
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")) {
grantIndestructible = true;
}
@@ -1614,7 +1600,7 @@ public class ComputerUtil {
}
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);
} else {
return threatened;
@@ -1632,7 +1618,7 @@ public class ComputerUtil {
final SpellAbility sub = topStack.getSubAbility();
boolean noRegen = false;
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) {
if (kw.contains("can't be regenerated")) {
noRegen = true;
@@ -1928,8 +1914,8 @@ public class ComputerUtil {
return true;
}
if (game.getPhaseHandler().inCombat() &&
ComputerUtilCombat.combatantWouldBeDestroyed(ai, source, game.getCombat())) {
return true;
ComputerUtilCombat.combatantWouldBeDestroyed(ai, source, game.getCombat())) {
return true;
}
} else if (zone.getZoneType() == ZoneType.Exile && sa.getMayPlay() != null) {
// 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) {
// 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
public static int scoreHand(CardCollectionView handList, Player ai) {
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
int currentHandSize = handList.size();
int finalHandSize = currentHandSize - cardsToReturn;
// don't mulligan when already too low
if (finalHandSize < aic.getIntProperty(AiProps.MULLIGAN_THRESHOLD)) {
return finalHandSize;
if (handList.size() < aic.getIntProperty(AiProps.MULLIGAN_THRESHOLD)) {
return handList.size();
}
CardCollectionView library = ai.getZone(ZoneType.Library).getCards();
@@ -1963,14 +1942,17 @@ public class ComputerUtil {
// no land deck, can't do anything better
if (landsInDeck == 0) {
return finalHandSize;
return handList.size();
}
final CardCollectionView lands = CardLists.filter(handList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getManaCost().getCMC() <= 0 && !c.hasSVar("NeedsToPlay")
&& (c.getType().isLand() || c.getType().isArtifact());
if (c.getManaCost().getCMC() > 0 || c.hasSVar("NeedsToPlay")
|| (!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>() {
@Override
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
public static boolean wantMulligan(Player ai, int cardsToReturn) {
public static boolean wantMulligan(Player ai) {
final CardCollectionView handList = ai.getCardsIn(ZoneType.Hand);
return scoreHand(handList, ai, cardsToReturn) <= 0;
return scoreHand(handList, ai) <= 0;
}
public static CardCollection getPartialParisCandidates(Player ai) {
@@ -2055,7 +2040,7 @@ public class ComputerUtil {
//Too many lands!
//Init
int cntColors = MagicColor.WUBRG.length;
List<CardCollection> numProducers = new ArrayList<>(cntColors);
List<CardCollection> numProducers = new ArrayList<CardCollection>(cntColors);
for (byte col : MagicColor.WUBRG) {
numProducers.add(col, new CardCollection());
}
@@ -2182,7 +2167,10 @@ public class ComputerUtil {
CardCollection goodChoices = CardLists.filter(validCards, new Predicate<Card>() {
@Override
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()) {
@@ -2218,7 +2206,7 @@ public class ComputerUtil {
public static String chooseSomeType(Player ai, String kindOfType, String logic, List<String> invalidTypes) {
if (invalidTypes == null) {
invalidTypes = ImmutableList.of();
invalidTypes = ImmutableList.<String>of();
}
final Game game = ai.getGame();
@@ -2285,7 +2273,8 @@ public class ComputerUtil {
chosen = ComputerUtilCard.getMostProminentType(list, valid);
} else if (logic.equals("MostNeededType")) {
// 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 possibleCards = ai.getAllCards();
@@ -2403,7 +2392,7 @@ public class ComputerUtil {
}
// if source is not on the battlefield anymore, choose +1/+1
// ones
if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
if (!game.getCardState(source).getZone().is(ZoneType.Battlefield)) {
return opponent ? "Feather" : "Quill";
}
// if no hand cards, try to mill opponent
@@ -2435,7 +2424,7 @@ public class ComputerUtil {
}
// 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";
}
@@ -2484,7 +2473,7 @@ public class ComputerUtil {
}
// 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";
}
// TODO add Lifegain to +1/+1 counters trigger
@@ -2538,7 +2527,8 @@ public class ComputerUtil {
@Override
public boolean apply(final Card c) {
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;
}
@@ -2589,7 +2579,7 @@ public class ComputerUtil {
int damage = 0;
final Game game = player.getGame();
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)) {
theTriggers.addAll(c.getTriggers());
@@ -2681,7 +2671,7 @@ public class ComputerUtil {
public static int getDamageFromETB(final Player player, final Card permanent) {
int damage = 0;
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)) {
theTriggers.addAll(card.getTriggers());
@@ -2779,7 +2769,7 @@ public class ComputerUtil {
// Iceberg does use Ice as Storage
|| (type == CounterType.ICE && !"Iceberg".equals(c.getName()))
// 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,
// and also on Chronozoa
|| (type == CounterType.TIME && (!c.isInPlay() || "Chronozoa".equals(c.getName())))
@@ -2850,14 +2840,14 @@ public class ComputerUtil {
}
// Run any applicable replacement effects.
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(player);
repParams.put(AbilityKey.LifeGained, 1);
repParams.put(AbilityKey.Source, source);
final Map<String, Object> repParams = Maps.newHashMap();
repParams.put("Event", "GainLife");
repParams.put("Affected", player);
repParams.put("LifeGained", 1);
repParams.put("Source", source);
List<ReplacementEffect> list = player.getGame().getReplacementHandler().getReplacementList(
ReplacementType.GainLife,
repParams,
ReplacementLayer.Other);
List<ReplacementEffect> list = player.getGame().getReplacementHandler().getReplacementList(repParams,
ReplacementLayer.None);
if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "NoLife"))) {
return false;
@@ -2866,6 +2856,7 @@ public class ComputerUtil {
} else if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "LichDraw"))) {
return false;
}
return true;
}
@@ -2880,15 +2871,14 @@ public class ComputerUtil {
}
// Run any applicable replacement effects.
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(player);
repParams.put(AbilityKey.LifeGained, n);
repParams.put(AbilityKey.Source, source);
final Map<String, Object> repParams = Maps.newHashMap();
repParams.put("Event", "GainLife");
repParams.put("Affected", player);
repParams.put("LifeGained", n);
repParams.put("Source", source);
List<ReplacementEffect> list = player.getGame().getReplacementHandler().getReplacementList(
ReplacementType.GainLife,
repParams,
ReplacementLayer.Other
);
List<ReplacementEffect> list = player.getGame().getReplacementHandler().getReplacementList(repParams,
ReplacementLayer.None);
if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "NoLife"))) {
// no life gain is not negative
@@ -2998,23 +2988,14 @@ public class ComputerUtil {
if (sa.hasParam("AITgts")) {
CardCollection list;
String aiTgts = sa.getParam("AITgts");
if (aiTgts.startsWith("BetterThan")) {
int value = 0;
if (aiTgts.endsWith("Source")) {
value = ComputerUtilCard.evaluateCreature(source);
if (source.isEnchanted()) {
for (Card enc : source.getEnchantedBy()) {
if (enc.getController().equals(ai)) {
value += 100; // is 100 per AI's own aura enough?
}
if (sa.getParam("AITgts").equals("BetterThanSource")) {
int value = ComputerUtilCard.evaluateCreature(source);
if (source.isEnchanted()) {
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;
list = CardLists.filter(srcList, new Predicate<Card>() {

View File

@@ -95,15 +95,9 @@ public class ComputerUtilAbility {
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
final List<SpellAbility> newAbilities = Lists.newArrayList();
List<SpellAbility> originListWithAddCosts = Lists.newArrayList();
for (SpellAbility sa : originList) {
// If this spell has alternative additional costs, add them instead of the unmodified SA itself
sa.setActivatingPlayer(player);
originListWithAddCosts.addAll(GameActionUtil.getAdditionalCostSpell(sa));
}
for (SpellAbility sa : originListWithAddCosts) {
// determine which alternative costs are cheaper than the original and prioritize them
List<SpellAbility> saAltCosts = GameActionUtil.getAlternativeCosts(sa, player);
List<SpellAbility> priorityAltSa = Lists.newArrayList();

View File

@@ -228,7 +228,7 @@ public class ComputerUtilCard {
Card cheapest = null;
for (Card c : all) {
if (cheapest == null || c.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
if (cheapest == null || cheapest.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
cheapest = c;
}
}
@@ -368,7 +368,7 @@ public class ComputerUtilCard {
}
if (hasEnchantmants || hasArtifacts) {
final List<Card> ae = CardLists.filter(list, Predicates.and(Predicates.or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.ENCHANTMENTS), new Predicate<Card>() {
final List<Card> ae = CardLists.filter(list, Predicates.and(Predicates.<Card>or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.ENCHANTMENTS), new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return !card.hasSVar("DoNotDiscardIfAble");
@@ -521,7 +521,7 @@ public class ComputerUtilCard {
*/
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
AiBlockController aiBlk = new AiBlockController(ai);
final Player opp = ai.getWeakestOpponent();
final Player opp = ComputerUtil.getOpponentFor(ai);
Combat combat = new Combat(opp);
//Use actual attackers if available, else consider all possible attackers
Combat currentCombat = ai.getGame().getCombat();
@@ -564,7 +564,7 @@ public class ComputerUtilCard {
AiBlockController aiBlk = new AiBlockController(ai);
Combat combat = new Combat(ai);
combat.addAttacker(attacker, ai);
final List<Card> attackers = new ArrayList<>();
final List<Card> attackers = new ArrayList<Card>();
attackers.add(attacker);
aiBlk.assignBlockersGivenAttackers(combat, attackers);
return ComputerUtilCombat.attackerWouldBeDestroyed(ai, attacker, combat);
@@ -788,7 +788,7 @@ public class ComputerUtilCard {
public static List<String> getColorByProminence(final List<Card> list) {
int cntColors = MagicColor.WUBRG.length;
final List<Pair<Byte,Integer>> map = new ArrayList<>();
final List<Pair<Byte,Integer>> map = new ArrayList<Pair<Byte,Integer>>();
for(int i = 0; i < cntColors; i++) {
map.add(MutablePair.of(MagicColor.WUBRG[i], 0));
}
@@ -809,7 +809,7 @@ public class ComputerUtilCard {
});
// will this part be once dropped?
List<String> result = new ArrayList<>(cntColors);
List<String> result = new ArrayList<String>(cntColors);
for(Pair<Byte, Integer> idx : map) { // fetch color names in the same order
result.add(MagicColor.toLongString(idx.getKey()));
}
@@ -881,10 +881,10 @@ public class ComputerUtilCard {
}
};
public static List<String> chooseColor(SpellAbility sa, int min, int max, List<String> colorChoices) {
List<String> chosen = new ArrayList<>();
List<String> chosen = new ArrayList<String>();
Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
Player opp = ai.getWeakestOpponent();
Player opp = ComputerUtil.getOpponentFor(ai);
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic");
@@ -926,7 +926,7 @@ public class ComputerUtilCard {
}
else if (logic.equals("MostProminentInComputerDeckButGreen")) {
List<String> prominence = ComputerUtilCard.getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai));
if (prominence.get(0).equals(MagicColor.Constant.GREEN)) {
if (prominence.get(0) == MagicColor.Constant.GREEN) {
chosen.add(prominence.get(1));
} else {
chosen.add(prominence.get(0));
@@ -974,7 +974,7 @@ public class ComputerUtilCard {
public static boolean useRemovalNow(final SpellAbility sa, final Card c, final int dmg, ZoneType destination) {
final Player ai = sa.getActivatingPlayer();
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
final Player opp = ai.getWeakestOpponent();
final Player opp = ComputerUtil.getOpponentFor(ai);
final Game game = ai.getGame();
final PhaseHandler ph = game.getPhaseHandler();
final PhaseType phaseType = ph.getPhase();
@@ -1128,14 +1128,14 @@ public class ComputerUtilCard {
// assume it either benefits the player or disrupts the opponent
for (final StaticAbility stAb : c.getStaticAbilities()) {
final Map<String, String> params = stAb.getMapParams();
if (params.get("Mode").equals("Continuous") && stAb.isIntrinsic()) {
if (params.get("Mode").equals("Continuous") && stAb.isIntrinsic() && !stAb.isTemporary()) {
priority = true;
break;
}
}
if (!priority) {
for (final Trigger t : c.getTriggers()) {
if (t.isIntrinsic()) {
if (t.isIntrinsic() && !t.isTemporary()) {
// has a triggered ability, could be benefitting the opponent or disrupting the AI
priority = true;
break;
@@ -1213,7 +1213,6 @@ public class ComputerUtilCard {
final Game game = ai.getGame();
final PhaseHandler phase = game.getPhaseHandler();
final Combat combat = phase.getCombat();
final boolean main1Preferred = "Main1IfAble".equals(sa.getParam("AILogic")) && phase.is(PhaseType.MAIN1, ai);
final boolean isBerserk = "Berserk".equals(sa.getParam("AILogic"));
final boolean loseCardAtEOT = "Sacrifice".equals(sa.getParam("AtEOT")) || "Exile".equals(sa.getParam("AtEOT"))
|| "Destroy".equals(sa.getParam("AtEOT")) || "ExileCombat".equals(sa.getParam("AtEOT"));
@@ -1251,7 +1250,7 @@ public class ComputerUtilCard {
// will the creature attack (only relevant for sorcery speed)?
if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& phase.isPlayerTurn(ai)
&& SpellAbilityAi.isSorcerySpeed(sa) || main1Preferred
&& SpellAbilityAi.isSorcerySpeed(sa)
&& power > 0
&& ComputerUtilCard.doesCreatureAttackAI(ai, c)) {
return true;
@@ -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);
List<Card> oppCreatures = opp.getCreaturesInPlay();
float chance = 0;
@@ -1294,14 +1293,14 @@ public class ComputerUtilCard {
// cast it during Declare Blockers, thus ruining its attacker
if (holdCombatTricks && sa.getApi() == ApiType.Pump
&& sa.hasParam("NumAtt") && sa.getHostCard() != null
&& sa.getHostCard().isInZone(ZoneType.Hand)
&& sa.getHostCard().getZone() != null && sa.getHostCard().getZone().is(ZoneType.Hand)
&& c.getNetPower() > 0 // too obvious if attacking with a 0-power creature
&& sa.getHostCard().isInstant() // only do it for instant speed spells in hand
&& ComputerUtilMana.hasEnoughManaSourcesToCast(sa, ai)) {
combatTrick = true;
final List<String> kws = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & "))
: Lists.newArrayList();
: Lists.<String>newArrayList();
for (String kw : kws) {
if (!kw.equals("Trample") && !kw.equals("First Strike") && !kw.equals("Double Strike")) {
combatTrick = false;
@@ -1457,10 +1456,6 @@ public class ComputerUtilCard {
}
if (totalPowerUnblocked >= opp.getLife()) {
return true;
} else if (totalPowerUnblocked > dmg && sa.getHostCard() != null && sa.getHostCard().isInPlay()) {
if (sa.getPayCosts() != 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);
@@ -1571,7 +1566,7 @@ public class ComputerUtilCard {
Card pumped = CardFactory.copyCard(c, true);
pumped.setSickness(c.hasSickness());
final long timestamp = c.getGame().getNextTimestamp();
final List<String> kws = new ArrayList<>();
final List<String> kws = new ArrayList<String>();
for (String kw : keywords) {
if (kw.startsWith("HIDDEN")) {
pumped.addHiddenExtrinsicKeyword(kw);
@@ -1601,12 +1596,12 @@ public class ComputerUtilCard {
}
pumped.addNewPT(c.getCurrentPower(), c.getCurrentToughness(), timestamp);
pumped.setPTBoost(c.getPTBoostTable());
pumped.addPTBoost(power + berserkPower, toughness, timestamp, null);
pumped.addTempPowerBoost(c.getTempPowerBoost() + power + berserkPower);
pumped.addTempToughnessBoost(c.getTempToughnessBoost() + toughness);
pumped.addChangedCardKeywords(kws, null, false, false, timestamp);
Set<CounterType> types = c.getCounters().keySet();
for(CounterType ct : types) {
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, true, null);
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, true);
}
//Copies tap-state and extra keywords (auras, equipment, etc.)
if (c.isTapped()) {
@@ -1648,9 +1643,7 @@ public class ComputerUtilCard {
}
list.add(vCard); // account for the static abilities that may be present on the card itself
for (final Card c : list) {
// remove old boost that might be copied
for (final StaticAbility stAb : c.getStaticAbilities()) {
vCard.removePTBoost(c.getTimestamp(), stAb.getId());
final Map<String, String> params = stAb.getMapParams();
if (!params.get("Mode").equals("Continuous")) {
continue;
@@ -1665,25 +1658,26 @@ public class ComputerUtilCard {
if (!vCard.isValid(valid, c.getController(), c, null)) {
continue;
}
int att = 0;
if (params.containsKey("AddPower")) {
String addP = params.get("AddPower");
int att = 0;
if (addP.equals("AffectedX")) {
att = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addP));
} else {
att = AbilityUtils.calculateAmount(c, addP, stAb);
}
vCard.addTempPowerBoost(att);
}
int def = 0;
if (params.containsKey("AddToughness")) {
String addT = params.get("AddToughness");
int def = 0;
if (addT.equals("AffectedY")) {
def = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addT));
} else {
def = AbilityUtils.calculateAmount(c, addT, stAb);
}
vCard.addTempToughnessBoost(def);
}
vCard.addPTBoost(att, def, c.getTimestamp(), stAb.getId());
}
}
}
@@ -1742,7 +1736,7 @@ public class ComputerUtilCard {
if (!c.isCreature()) {
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 false;
@@ -1848,45 +1842,8 @@ public class ComputerUtilCard {
String needsToPlayName = isRightSplit ? "SplitNeedsToPlay" : "NeedsToPlay";
String needsToPlayVarName = isRightSplit ? "SplitNeedsToPlayVar" : "NeedsToPlayVar";
// TODO: if there are ever split cards with Evoke or Kicker, factor in the right split option above
if (sa != null) {
if (sa.isEvoke()) {
// if the spell is evoked, will use NeedsToPlayEvoked if available (otherwise falls back to NeedsToPlay)
if (card.hasSVar("NeedsToPlayEvoked")) {
needsToPlayName = "NeedsToPlayEvoked";
}
if (card.hasSVar("NeedsToPlayEvokedVar")) {
needsToPlayVarName = "NeedsToPlayEvokedVar";
}
} else if (sa.isKicked()) {
// if the spell is kicked, uses NeedsToPlayKicked if able and locks out the regular NeedsToPlay check
// for unkicked spells, uses NeedsToPlay
if (card.hasSVar("NeedsToPlayKicked")) {
needsToPlayName = "NeedsToPlayKicked";
} else {
needsToPlayName = "UNUSED";
}
if (card.hasSVar("NeedsToPlayKickedVar")) {
needsToPlayVarName = "NeedsToPlayKickedVar";
} else {
needsToPlayVarName = "UNUSED";
}
}
}
if (card.hasSVar(needsToPlayName)) {
final String needsToPlay = card.getSVar(needsToPlayName);
// A special case which checks that this creature will attack if it's the AI's turn
if (needsToPlay.equalsIgnoreCase("WillAttack")) {
if (sa != null && game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return ComputerUtilCard.doesSpecifiedCreatureAttackAI(sa.getActivatingPlayer(), card) ?
AiPlayDecision.WillPlay : AiPlayDecision.BadEtbEffects;
} else {
return AiPlayDecision.WillPlay; // not our turn, skip this check for the possible Flash use etc.
}
}
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card, null);

View File

@@ -28,8 +28,8 @@ import com.google.common.collect.Maps;
import forge.game.CardTraitBase;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
@@ -42,7 +42,6 @@ import forge.game.phase.Untap;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementLayer;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.AbilityActivated;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
@@ -85,7 +84,7 @@ public class ComputerUtilCombat {
return Iterables.any(defenders, new Predicate<GameEntity>() {
@Override public boolean apply(final GameEntity input) {
return ComputerUtilCombat.canAttackNextTurn(attacker, input);
}
};
});
} // canAttackNextTurn(Card)
@@ -120,7 +119,11 @@ public class ComputerUtilCombat {
}
// 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)
/**
@@ -191,9 +194,11 @@ public class ComputerUtilCombat {
return 0;
}
// ask ReplacementDamage directly
if (isCombatDamagePrevented(attacker, attacked, damage)) {
return 0;
if (!attacked.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) {
// ask ReplacementDamage directly
if (isCombatDamagePrevented(attacker, attacked, damage)) {
return 0;
}
}
damage += ComputerUtilCombat.predictPowerBonusOfAttacker(attacker, null, combat, withoutAbilities);
@@ -764,10 +769,6 @@ public class ComputerUtilCombat {
*/
public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger,
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 Map<String, String> trigParams = trigger.getMapParams();
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
@@ -878,19 +876,20 @@ public class ComputerUtilCombat {
} else if (mode == TriggerType.DamageDone) {
willTrigger = true;
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
&& (!trigParams.containsKey("ValidTarget")
|| CardTraitBase.matchesValid(attacker, trigParams.get("ValidTarget").split(","), source)))) {
return false;
|| CardTraitBase.matchesValid(attacker, trigParams.get("ValidTarget").split(","), source))) {
return true;
}
if (!(CardTraitBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), source)
if (CardTraitBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), source)
&& attacker.getNetCombatDamage() > 0
&& (!trigParams.containsKey("ValidTarget")
|| CardTraitBase.matchesValid(defender, trigParams.get("ValidTarget").split(","), source)))) {
return false;
|| CardTraitBase.matchesValid(defender, trigParams.get("ValidTarget").split(","), source))) {
return true;
}
}
return false;
}
return willTrigger;
@@ -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)) {
theTriggers.addAll(card.getTriggers());
}
@@ -1099,7 +1098,7 @@ public class ComputerUtilCombat {
}
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)) {
theTriggers.addAll(card.getTriggers());
}
@@ -1283,7 +1282,7 @@ public class ComputerUtilCombat {
}
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)) {
theTriggers.addAll(card.getTriggers());
}
@@ -1408,7 +1407,7 @@ public class ComputerUtilCombat {
if (att.matches("[0-9][0-9]?") || att.matches("-" + "[0-9][0-9]?")) {
power += Integer.parseInt(att);
} else {
String bonus = source.getSVar(att);
String bonus = new String(source.getSVar(att));
if (bonus.contains("TriggerCount$NumBlockers")) {
bonus = TextUtil.fastReplace(bonus, "TriggerCount$NumBlockers", "Number$1");
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
@@ -1456,7 +1455,7 @@ public class ComputerUtilCombat {
continue;
}
if (ability.hasParam("Adapt") && attacker.getCounters(CounterType.P1P1) > 0) {
if (ability.hasParam("Adapt") && blocker != null && blocker.getCounters(CounterType.P1P1) > 0) {
continue;
}
@@ -1504,7 +1503,7 @@ public class ComputerUtilCombat {
}
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)) {
theTriggers.addAll(card.getTriggers());
}
@@ -1649,7 +1648,7 @@ public class ComputerUtilCombat {
if (def.matches("[0-9][0-9]?") || def.matches("-" + "[0-9][0-9]?")) {
toughness += Integer.parseInt(def);
} else {
String bonus = source.getSVar(def);
String bonus = new String(source.getSVar(def));
if (bonus.contains("TriggerCount$NumBlockers")) {
bonus = TextUtil.fastReplace(bonus, "TriggerCount$NumBlockers", "Number$1");
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
@@ -1693,7 +1692,7 @@ public class ComputerUtilCombat {
continue;
}
if (ability.hasParam("Adapt") && attacker.getCounters(CounterType.P1P1) > 0) {
if (ability.hasParam("Adapt") && blocker.getCounters(CounterType.P1P1) > 0) {
continue;
}
@@ -1725,7 +1724,7 @@ public class ComputerUtilCombat {
}
// 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)) {
theTriggers.addAll(card.getTriggers());
}
@@ -1886,8 +1885,10 @@ public class ComputerUtilCombat {
// consider Damage Prevention/Replacement
defenderDamage = predictDamageTo(attacker, defenderDamage, possibleAttackerPrevention, blocker, true);
attackerDamage = predictDamageTo(blocker, attackerDamage, possibleDefenderPrevention, attacker, true);
if (defenderDamage > 0 && isCombatDamagePrevented(blocker, attacker, defenderDamage)) {
return false;
if (!attacker.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) {
if (defenderDamage > 0 && isCombatDamagePrevented(blocker, attacker, defenderDamage)) {
return false;
}
}
final int defenderLife = ComputerUtilCombat.getDamageToKill(blocker)
@@ -1940,7 +1941,7 @@ public class ComputerUtilCombat {
return defenderDamage >= attackerLife;
} // defender no double strike
return false;// should never arrive here
return false; // should never arrive here
} // canDestroyAttacker
// For AI safety measures like Regeneration
@@ -2003,7 +2004,7 @@ public class ComputerUtilCombat {
}
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)) {
theTriggers.addAll(card.getTriggers());
}
@@ -2120,11 +2121,13 @@ public class ComputerUtilCombat {
attackerDamage = predictDamageTo(blocker, attackerDamage, possibleDefenderPrevention, attacker, true);
// Damage prevention might come from a static effect
if (isCombatDamagePrevented(attacker, blocker, attackerDamage)) {
attackerDamage = 0;
}
if (isCombatDamagePrevented(blocker, attacker, defenderDamage)) {
defenderDamage = 0;
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) {
if (isCombatDamagePrevented(attacker, blocker, attackerDamage)) {
attackerDamage = 0;
}
if (isCombatDamagePrevented(blocker, attacker, defenderDamage)) {
defenderDamage = 0;
}
}
if (combat != null) {
@@ -2185,7 +2188,7 @@ public class ComputerUtilCombat {
return attackerDamage >= defenderLife;
} // attacker no double strike
return false;// should never arrive here
return false; // should never arrive here
} // canDestroyBlocker
@@ -2392,7 +2395,7 @@ public class ComputerUtilCombat {
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
for (final ReplacementEffect re : ca.getReplacementEffects()) {
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;
}
// Immortal Coil prevents the damage but has a similar negative effect
@@ -2574,21 +2577,20 @@ public class ComputerUtilCombat {
}
public static boolean isCombatDamagePrevented(final Card attacker, final GameEntity target, final int damage) {
if (!attacker.canDamagePrevented(true)) {
return false;
}
final Game game = attacker.getGame();
// first try to replace the damage
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(target);
repParams.put(AbilityKey.DamageSource, attacker);
repParams.put(AbilityKey.DamageAmount, damage);
repParams.put(AbilityKey.IsCombat, true);
repParams.put(AbilityKey.Prevention, true);
final Map<String, Object> repParams = Maps.newHashMap();
repParams.put("Event", "DamageDone");
repParams.put("Affected", target);
repParams.put("DamageSource", attacker);
repParams.put("DamageAmount", damage);
repParams.put("IsCombat", true);
repParams.put("Prevention", true);
// repParams.put("PreventMap", preventMap);
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(
ReplacementType.DamageDone, repParams, ReplacementLayer.Other);
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(repParams,
ReplacementLayer.None);
return !list.isEmpty();
}

View File

@@ -110,15 +110,6 @@ public class ComputerUtilCost {
&& !source.hasKeyword(Keyword.UNDYING)) {
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;
@@ -424,7 +415,7 @@ public class ComputerUtilCost {
continue;
}
final int remainingLife = ai.getLife();
final int lifeCost = part.convertAmount();
final int lifeCost = ((CostPayLife) part).convertAmount();
if ((remainingLife - lifeCost) < 10) {
return false; //Don't pay life if it would put AI under 10 life
} else if ((remainingLife / lifeCost) < 4) {
@@ -468,7 +459,7 @@ public class ComputerUtilCost {
continue;
try {
extraManaNeeded += Integer.parseInt(parts[0]);
extraManaNeeded += Integer.parseInt(snem);
} catch (final NumberFormatException e) {
System.out.println("wrong SpellsNeedExtraMana SVar format on " + c);
}
@@ -490,7 +481,7 @@ public class ComputerUtilCost {
}
// Try not to lose Planeswalker if not threatened
if (sa.isPwAbility()) {
if (sa.getRestrictions().isPwAbility()) {
for (final CostPart part : sa.getPayCosts().getCostParts()) {
if (part instanceof CostRemoveCounter) {
if (part.convertAmount() != null && part.convertAmount() == sa.getHostCard().getCurrentLoyalty()) {
@@ -552,7 +543,7 @@ public class ComputerUtilCost {
final Card source = sa.getHostCard();
final String aiLogic = sa.getParam("UnlessAI");
boolean payForOwnOnly = "OnlyOwn".equals(aiLogic);
boolean payOwner = sa.hasParam("UnlessAI") && aiLogic.startsWith("Defined");
boolean payOwner = sa.hasParam("UnlessAI") ? aiLogic.startsWith("Defined") : false;
boolean payNever = "Never".equals(aiLogic);
boolean shockland = "Shockland".equals(aiLogic);
boolean isMine = sa.getActivatingPlayer().equals(payer);
@@ -594,11 +585,6 @@ public class ComputerUtilCost {
if (c == null || c.isUntapped()) {
return false;
}
} else if ("RiskFactor".equals(aiLogic)) {
final Player activator = sa.getActivatingPlayer();
if (!activator.canDraw() || activator.hasKeyword("You can't draw more than one card each turn.")) {
return false;
}
} else if ("MorePowerful".equals(aiLogic)) {
final int sourceCreatures = sa.getActivatingPlayer().getCreaturesInPlay().size();
final int payerCreatures = payer.getCreaturesInPlay().size();
@@ -640,7 +626,7 @@ public class ComputerUtilCost {
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
&& (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2)
&& (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1)
&& (!source.getName().equals("Chain of Vapor") || (payer.getWeakestOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
&& (!source.getName().equals("Chain of Vapor") || (ComputerUtil.getOpponentFor(payer).getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
}
public static Set<String> getAvailableManaColors(Player ai, Card additionalLand) {

View File

@@ -4,7 +4,6 @@ import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.*;
import forge.ai.ability.AnimateAi;
import forge.card.CardStateName;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaAtom;
@@ -13,7 +12,6 @@ import forge.card.mana.ManaCostParser;
import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
@@ -26,7 +24,6 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.AbilitySub;
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.
public static boolean hasEnoughManaSourcesToCast(final SpellAbility sa, final Player ai) {
if(ai == null || sa == null)
return false;
sa.setActivatingPlayer(ai);
return payManaCost(sa, ai, true, 0, false);
}
@@ -174,7 +169,8 @@ public class ComputerUtilMana {
}
return ability1.compareTo(ability2);
} else {
}
else {
return preOrder;
}
}
@@ -324,17 +320,11 @@ public class ComputerUtilMana {
SpellAbility saPayment = chooseManaAbility(cost, sa, ai, toPay, saList, true);
if (saPayment == null) {
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2)) {
if (!toPay.isPhyrexian() || !ai.canPayLife(2)) {
break; // cannot pay
}
if (toPay.isPhyrexian()) {
cost.payPhyrexian();
} else if (lifeInsteadOfBlack) {
cost.decreaseShard(ManaCostShard.BLACK, 1);
}
cost.payPhyrexian();
continue;
}
@@ -347,10 +337,6 @@ public class ComputerUtilMana {
payMultipleMana(cost, manaProduced, ai);
// 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();
while (itSa.hasNext()) {
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) {
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
List<Mana> manaSpentToPay = test ? new ArrayList<>() : sa.getPayingMana();
List<Mana> manaSpentToPay = test ? new ArrayList<Mana>() : sa.getPayingMana();
boolean purePhyrexian = cost.containsOnlyPhyrexianMana();
int testEnergyPool = ai.getCounters(CounterType.ENERGY);
@@ -394,8 +380,6 @@ public class ComputerUtilMana {
while (!cost.isPaid()) {
toPay = getNextShardToPay(cost);
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
Collection<SpellAbility> saList = null;
if (hasConverge &&
(toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) {
@@ -446,8 +430,7 @@ public class ComputerUtilMana {
}
if (saPayment == null) {
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2)
|| (ai.getLife() <= 2 && !ai.cantLoseForZeroOrLessLife())) {
if (!toPay.isPhyrexian() || !ai.canPayLife(2) || (ai.getLife() <= 2 && !ai.cantLoseForZeroOrLessLife())) {
break; // cannot pay
}
@@ -462,12 +445,7 @@ public class ComputerUtilMana {
}
}
if (toPay.isPhyrexian()) {
cost.payPhyrexian();
} else if (lifeInsteadOfBlack) {
cost.decreaseShard(ManaCostShard.BLACK, 1);
}
cost.payPhyrexian();
if (!test) {
ai.payLife(2, sa.getHostCard());
}
@@ -494,10 +472,6 @@ public class ComputerUtilMana {
payMultipleMana(cost, manaProduced, ai);
// 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();
while (itSa.hasNext()) {
SpellAbility srcSa = itSa.next();
@@ -528,10 +502,6 @@ public class ComputerUtilMana {
if (hasConverge) { // hack to prevent converge re-using sources
// 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();
while (itSa.hasNext()) {
SpellAbility srcSa = itSa.next();
@@ -895,12 +865,10 @@ public class ComputerUtilMana {
// For combat tricks, always obey mana reservation
if (curPhase == PhaseType.COMBAT_DECLARE_BLOCKERS || curPhase == PhaseType.CLEANUP) {
if (!(ai.getGame().getPhaseHandler().isPlayerTurn(ai))) {
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
}
else
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
} else if (!(ai.getGame().getPhaseHandler().isPlayerTurn(ai)) && (curPhase == PhaseType.COMBAT_DECLARE_BLOCKERS || curPhase == PhaseType.CLEANUP)) {
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
} else {
if ((AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_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
// Else calculate it as appropriate.
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 (xSvar.equals("PayX") && (card.hasSVar(xSvar) || card.getState(CardStateName.Original).hasSVar(xSvar))) {
if (!sa.getSVar(xSvar).isEmpty() || card.hasSVar(xSvar)) {
if (xSvar.equals("PayX") && card.hasSVar(xSvar)) {
// X SVar may end up being an empty string when copying a spell with no cost (e.g. Jhoira Avatar)
String xValue = card.hasSVar(xSvar) ? card.getSVar(xSvar) : card.getState(CardStateName.Original).getSVar(xSvar);
manaToAdd = xValue.isEmpty() ? 0 : Integer.parseInt(xValue) * cost.getXcounter(); // X
String xValue = card.getSVar(xSvar);
manaToAdd = xValue.isEmpty() ? 0 : Integer.parseInt(card.getSVar(xSvar)) * cost.getXcounter(); // X
} else {
manaToAdd = AbilityUtils.calculateAmount(card, xSvar, sa) * cost.getXcounter();
}
@@ -1363,13 +1331,12 @@ public class ComputerUtilMana {
final ListMultimap<Integer, SpellAbility> manaMap = ArrayListMultimap.create();
final Game game = ai.getGame();
List<ReplacementEffect> replacementEffects = new ArrayList<>();
List<ReplacementEffect> replacementEffects = new ArrayList<ReplacementEffect>();
for (final Player p : game.getPlayers()) {
for (final Card crd : p.getAllCards()) {
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
if (replacementEffect.requirementsCheck(game)
&& replacementEffect.getMode() == ReplacementType.ProduceMana
&& replacementEffect.hasParam("ManaReplacement")
&& replacementEffect.getMapParams().containsKey("ManaReplacement")
&& replacementEffect.zonesCheck(game.getZoneOf(crd))) {
replacementEffects.add(replacementEffect);
}
@@ -1409,16 +1376,17 @@ public class ComputerUtilMana {
AbilityManaPart mp = m.getManaPart();
// setup produce mana replacement effects
final Map<AbilityKey, Object> repParams = AbilityKey.newMap();
repParams.put(AbilityKey.Mana, mp.getOrigProduced());
repParams.put(AbilityKey.Affected, sourceCard);
repParams.put(AbilityKey.Player, ai);
repParams.put(AbilityKey.AbilityMana, m);
final Map<String, Object> repParams = new HashMap<>();
repParams.put("Event", "ProduceMana");
repParams.put("Mana", mp.getOrigProduced());
repParams.put("Affected", sourceCard);
repParams.put("Player", ai);
repParams.put("AbilityMana", m);
for (final ReplacementEffect replacementEffect : replacementEffects) {
if (replacementEffect.canReplace(repParams)) {
Card crd = replacementEffect.getHostCard();
String repType = crd.getSVar(replacementEffect.getParam("ManaReplacement"));
String repType = crd.getSVar(replacementEffect.getMapParams().get("ManaReplacement"));
if (repType.contains("Chosen")) {
repType = TextUtil.fastReplace(repType, "Chosen", MagicColor.toShortString(crd.getChosenColor()));
}
@@ -1569,7 +1537,7 @@ public class ComputerUtilMana {
* @return map between creatures and shards to convoke
*/
public static Map<Card, ManaCostShard> getConvokeOrImproviseFromList(final ManaCost cost, List<Card> list, boolean improvise) {
final Map<Card, ManaCostShard> convoke = new HashMap<>();
final Map<Card, ManaCostShard> convoke = new HashMap<Card, ManaCostShard>();
Card convoked = null;
if (!improvise) {
for (ManaCostShard toPay : cost) {

View File

@@ -173,7 +173,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
if (c.hasKeyword("CARDNAME can't attack or block.")) {
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()) {
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
} else {

View File

@@ -25,7 +25,6 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.SpellAbility;
import forge.game.ability.AbilityKey;
import forge.game.trigger.TriggerType;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
@@ -42,7 +41,7 @@ import java.util.*;
import java.util.Map.Entry;
public abstract class GameState {
private static final Map<ZoneType, String> ZONES = new HashMap<>();
private static final Map<ZoneType, String> ZONES = new HashMap<ZoneType, String>();
static {
ZONES.put(ZoneType.Battlefield, "battlefield");
ZONES.put(ZoneType.Hand, "hand");
@@ -67,15 +66,14 @@ public abstract class GameState {
private boolean puzzleCreatorState = false;
private final Map<ZoneType, String> humanCardTexts = new EnumMap<>(ZoneType.class);
private final Map<ZoneType, String> aiCardTexts = new EnumMap<>(ZoneType.class);
private final Map<ZoneType, String> humanCardTexts = new EnumMap<ZoneType, String>(ZoneType.class);
private final Map<ZoneType, String> aiCardTexts = new EnumMap<ZoneType, String>(ZoneType.class);
private final Map<Integer, Card> idToCard = new HashMap<>();
private final Map<Card, Integer> cardToAttachId = new HashMap<>();
private final Map<Card, Integer> cardToEnchantPlayerId = new HashMap<>();
private final Map<Card, Integer> markedDamage = new HashMap<>();
private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>();
private final Map<Card, CardCollection> cardToChosenCards = new HashMap<>();
private final Map<Card, String> cardToChosenType = new HashMap<>();
private final Map<Card, List<String>> cardToRememberedId = 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 precastAI = null;
private String putOnStackHuman = null;
private String putOnStackAI = null;
private int turn = 1;
private boolean removeSummoningSickness = false;
// Targeting for precast spells in a game state (mostly used by Puzzle Mode game states)
private final int TARGET_NONE = -1; // untargeted spell (e.g. Joraga Invocation)
private final int TARGET_HUMAN = -2;
@@ -218,10 +211,6 @@ public abstract class GameState {
// Remember the IDs of imprinted cards
cardsReferencedByID.add(i);
}
for (Card i : card.getChosenCards()) {
// Remember the IDs of chosen cards
cardsReferencedByID.add(i);
}
if (game.getCombat() != null && game.getCombat().isAttacking(card)) {
// Remember the IDs of attacked planeswalkers
GameEntity def = game.getCombat().getDefenderByAttacker(card);
@@ -255,7 +244,7 @@ public abstract class GameState {
newText.append(";");
}
if (c.isToken()) {
newText.append("t:").append(new TokenInfo(c).toString());
newText.append("t:" + new TokenInfo(c).toString());
} else {
if (c.getPaperCard() == null) {
return;
@@ -323,17 +312,6 @@ public abstract class GameState {
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();
for (Object obj : c.getRemembered()) {
if (obj instanceof Card) {
@@ -378,7 +356,7 @@ public abstract class GameState {
newText.append("|Attacking");
GameEntity def = c.getGame().getCombat().getDefenderByAttacker(c);
if (def instanceof Card) {
newText.append(":").append(def.getId());
newText.append(":" + def.getId());
}
}
}
@@ -454,10 +432,6 @@ public abstract class GameState {
turn = Integer.parseInt(categoryValue);
}
else if (categoryName.equals("removesummoningsickness")) {
removeSummoningSickness = categoryValue.equalsIgnoreCase("true");
}
else if (categoryName.endsWith("life")) {
if (isHuman)
humanLife = Integer.parseInt(categoryValue);
@@ -539,13 +513,6 @@ public abstract class GameState {
precastAI = categoryValue;
}
else if (categoryName.endsWith("putonstack")) {
if (isHuman)
putOnStackHuman = categoryValue;
else
putOnStackAI = categoryValue;
}
else if (categoryName.endsWith("manapool")) {
if (isHuman)
humanManaPool = categoryValue;
@@ -585,7 +552,6 @@ public abstract class GameState {
cardToExiledWithId.clear();
markedDamage.clear();
cardToChosenClrs.clear();
cardToChosenCards.clear();
cardToChosenType.clear();
cardToScript.clear();
cardAttackMap.clear();
@@ -625,16 +591,11 @@ public abstract class GameState {
game.getTriggerHandler().setSuppressAllTriggers(false);
// SAs added to stack cause triggers to fire, as if the relevant SAs were cast
handleAddSAsToStack(game);
// Combat only works for 1v1 matches for now (which are the only matches dev mode supports anyway)
// Note: triggers may fire during combat declarations ("whenever X attacks, ...", etc.)
if (newPhase == PhaseType.COMBAT_DECLARE_ATTACKERS || newPhase == PhaseType.COMBAT_DECLARE_BLOCKERS) {
boolean toDeclareBlockers = newPhase == PhaseType.COMBAT_DECLARE_BLOCKERS;
if (newPlayerTurn != null) {
handleCombat(game, newPlayerTurn, newPlayerTurn.getSingleOpponent(), toDeclareBlockers);
}
handleCombat(game, newPlayerTurn, newPlayerTurn.getSingleOpponent(), toDeclareBlockers);
}
game.getStack().setResolving(false);
@@ -644,25 +605,19 @@ public abstract class GameState {
game.getPhaseHandler().devAdvanceToPhase(advPhase);
}
if (removeSummoningSickness) {
for (Card card : game.getCardsInGame()) {
card.setSickness(false);
}
}
game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
}
private String processManaPool(ManaPool manaPool) {
StringBuilder mana = new StringBuilder();
String mana = "";
for (final byte c : MagicColor.WUBRGC) {
int amount = manaPool.getAmountOfColor(c);
for (int i = 0; i < amount; i++) {
mana.append(MagicColor.toShortString(c)).append(" ");
mana += MagicColor.toShortString(c) + " ";
}
}
return mana.toString().trim();
return mana.trim();
}
private void updateManaPool(Player p, String manaDef, boolean clearPool, boolean persistent) {
@@ -720,10 +675,10 @@ public abstract class GameState {
for (final Card c : combat.getAttackers()) {
attackedTarget.add(combat.getDefenderByAttacker(c));
}
final Map<AbilityKey, Object> runParams = Maps.newEnumMap(AbilityKey.class);
runParams.put(AbilityKey.Attackers, combat.getAttackers());
runParams.put(AbilityKey.AttackingPlayer, combat.getAttackingPlayer());
runParams.put(AbilityKey.AttackedTarget, attackedTarget);
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Attackers", combat.getAttackers());
runParams.put("AttackingPlayer", combat.getAttackingPlayer());
runParams.put("AttackedTarget", attackedTarget);
game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false);
}
@@ -819,9 +774,6 @@ public abstract class GameState {
}
private void executeScript(Game game, Card c, String sPtr) {
executeScript(game, c, sPtr, false);
}
private void executeScript(Game game, Card c, String sPtr, boolean putOnStack) {
int tgtID = TARGET_NONE;
if (sPtr.contains("->")) {
String tgtDef = sPtr.substring(sPtr.lastIndexOf("->") + 2);
@@ -894,22 +846,16 @@ public abstract class GameState {
}
}
if (sa != null) {
sa.setActivatingPlayer(c.getController());
}
sa.setActivatingPlayer(c.getController());
handleScriptedTargetingForSA(game, sa, tgtID);
if (putOnStack) {
game.getStack().addAndUnfreeze(sa);
} else {
sa.resolve();
sa.resolve();
// resolve subabilities
SpellAbility subSa = sa.getSubAbility();
while (subSa != null) {
subSa.resolve();
subSa = subSa.getSubAbility();
}
// resolve subabilities
SpellAbility subSa = sa.getSubAbility();
while (subSa != null) {
subSa.resolve();
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) {
precastSpellFromCard(spellDef, activator, game, false);
}
private void precastSpellFromCard(String spellDef, final Player activator, final Game game, final boolean putOnStack) {
int tgtID = TARGET_NONE;
String scriptID = "";
@@ -977,7 +902,7 @@ public abstract class GameState {
SpellAbility sa = null;
if (!scriptID.isEmpty()) {
executeScript(game, c, scriptID, putOnStack);
executeScript(game, c, scriptID);
return;
}
@@ -986,11 +911,7 @@ public abstract class GameState {
handleScriptedTargetingForSA(game, sa, tgtID);
if (putOnStack) {
game.getStack().addAndUnfreeze(sa);
} else {
sa.resolve();
}
sa.resolve();
}
private void handleMarkedDamage() {
@@ -1026,12 +947,6 @@ public abstract class GameState {
Card c = entry.getKey();
c.setNamedCard(entry.getValue());
}
// Chosen cards
for (Entry<Card, CardCollection> entry : cardToChosenCards.entrySet()) {
Card c = entry.getKey();
c.setChosenCards(entry.getValue());
}
}
private void handleCardAttachments() {
@@ -1062,11 +977,11 @@ public abstract class GameState {
}
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(",");
for (final String counterPair : allCounterStrings) {
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);
}
Map<ZoneType, CardCollectionView> playerCards = new EnumMap<>(ZoneType.class);
Map<ZoneType, CardCollectionView> playerCards = new EnumMap<ZoneType, CardCollectionView>(ZoneType.class);
for (Entry<ZoneType, String> kv : cardTexts.entrySet()) {
String value = kv.getValue();
playerCards.put(kv.getKey(), processCardsForZone(value.isEmpty() ? new String[0] : value.split(";"), p));
@@ -1092,7 +1007,7 @@ public abstract class GameState {
for (Entry<ZoneType, CardCollectionView> kv : playerCards.entrySet()) {
PlayerZone zone = p.getZone(kv.getKey());
if (kv.getKey() == ZoneType.Battlefield) {
List<Card> cards = new ArrayList<>();
List<Card> cards = new ArrayList<Card>();
for (final Card c : kv.getValue()) {
if (c.isToken()) {
cards.add(c);
@@ -1108,7 +1023,7 @@ public abstract class GameState {
Map<CounterType, Integer> counters = c.getCounters();
// Note: Not clearCounters() since we want to keep the counters
// var as-is.
c.setCounters(Maps.newEnumMap(CounterType.class));
c.setCounters(Maps.<CounterType, Integer>newEnumMap(CounterType.class));
if (c.isAura()) {
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
// (will be overridden later, so the actual value shouldn't matter)
@@ -1132,6 +1047,7 @@ public abstract class GameState {
zone.setCards(kv.getValue());
}
}
}
/**
@@ -1192,7 +1108,7 @@ public abstract class GameState {
} else if (info.startsWith("SummonSick")) {
c.setSickness(true);
} else if (info.startsWith("FaceDown")) {
c.turnFaceDown(true);
c.setState(CardStateName.FaceDown, true);
if (info.endsWith("Manifested")) {
c.setManifested(true);
}
@@ -1217,14 +1133,6 @@ public abstract class GameState {
// TODO: improve this for game states with more than two players
String tgt = info.substring(info.indexOf(':') + 1);
cardToEnchantPlayerId.put(c, tgt.equalsIgnoreCase("AI") ? TARGET_AI : TARGET_HUMAN);
} else if (info.startsWith("Owner:")) {
// TODO: improve this for game states with more than two players
Player human = player.getGame().getPlayers().get(0);
Player ai = player.getGame().getPlayers().get(1);
String owner = info.substring(info.indexOf(':') + 1);
Player controller = c.getController();
c.setOwner(owner.equalsIgnoreCase("AI") ? ai : human);
c.setController(controller, c.getGame().getNextTimestamp());
} else if (info.startsWith("Ability:")) {
String abString = info.substring(info.indexOf(':') + 1).toLowerCase();
c.addSpellAbility(AbilityFactory.getAbility(abilityString.get(abString), c));
@@ -1235,13 +1143,6 @@ public abstract class GameState {
cardToChosenClrs.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
} else if (info.startsWith("ChosenType:")) {
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:")) {
cardToNamedCard.put(c, info.substring(info.indexOf(':') + 1));
} else if (info.startsWith("ExecuteScript:")) {

View File

@@ -25,7 +25,6 @@ import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
import forge.game.combat.Combat;
import forge.game.cost.*;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix;
import forge.game.mana.ManaCostBeingPaid;
@@ -92,7 +91,7 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public List<PaperCard> sideboard(Deck deck, GameType gameType, String message) {
public List<PaperCard> sideboard(Deck deck, GameType gameType) {
// AI does not know how to sideboard
return null;
}
@@ -163,22 +162,10 @@ public class PlayerControllerAi extends PlayerController {
@Override
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) {
if (delayedReveal != null) {
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix());
}
FCollection<T> remaining = new FCollection<>(optionList);
List<T> selecteds = new ArrayList<>();
T selected;
do {
selected = chooseSingleEntityForEffect(remaining, null, sa, title, selecteds.size()>=min, targetedPlayer);
if ( selected != null ) {
remaining.remove(selected);
selecteds.add(selected);
}
} while ( (selected != null ) && (selecteds.size() < max) );
return selecteds;
// this isn't used
return null;
}
@Override
@@ -354,7 +341,8 @@ public class PlayerControllerAi extends PlayerController {
@Override
public boolean apply(Card card) {
// need a custom predicate here since Volrath's Shapeshifter may have a different name OTB
return card.getOriginalState(CardStateName.Original).getName().equals("Volrath's Shapeshifter");
return card.getName().equals("Volrath's Shapeshifter")
|| card.getStates().contains(CardStateName.OriginalText) && card.getState(CardStateName.OriginalText).getName().equals("Volrath's Shapeshifter");
}
}).isEmpty()) {
int bestValue = 0;
@@ -492,7 +480,7 @@ public class PlayerControllerAi extends PlayerController {
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
return new CardCollection(toDiscard);
}
return getAi().getCardsToDiscard(num, null, sa);
return getAi().getCardsToDiscard(num, (String[])null, sa);
}
@@ -523,66 +511,15 @@ public class PlayerControllerAi extends PlayerController {
return brains.aiShouldRun(replacementEffect, effectSA);
}
@Override
public boolean mulliganKeepHand(Player firstPlayer, int cardsToReturn) {
return !ComputerUtil.wantMulligan(player, cardsToReturn);
}
@Override
public CardCollectionView getCardsToMulligan(Player firstPlayer) {
if (!ComputerUtil.wantMulligan(player, 0)) {
if (!ComputerUtil.wantMulligan(player)) {
return null;
}
return player.getCardsIn(ZoneType.Hand);
}
@Override
public CardCollectionView londonMulliganReturnCards(final Player mulliganingPlayer, int cardsToReturn) {
// TODO This is better than it was before, but still suboptimal (but fast).
// Maybe score a bunch of hands based on projected hand size and return the "duds"
CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
int numLandsDesired = (mulliganingPlayer.getStartingHandSize() - cardsToReturn) / 2;
CardCollection toReturn = new CardCollection();
for (int i = 0; i < cardsToReturn; i++) {
hand.removeAll(toReturn);
CardCollection landsInHand = CardLists.filter(hand, Presets.LANDS);
int numLandsInHand = landsInHand.size() - CardLists.filter(toReturn, Presets.LANDS).size();
// If we're flooding with lands, get rid of the worst land we have
if (numLandsInHand > 0 && numLandsInHand > numLandsDesired) {
CardCollection producingLands = CardLists.filter(landsInHand, Presets.LANDS_PRODUCING_MANA);
CardCollection nonProducingLands = CardLists.filter(landsInHand, Predicates.not(Presets.LANDS_PRODUCING_MANA));
Card worstLand = nonProducingLands.isEmpty() ? ComputerUtilCard.getWorstLand(producingLands)
: ComputerUtilCard.getWorstLand(nonProducingLands);
toReturn.add(worstLand);
continue;
}
// See if we'd scry something to the bottom in this situation. If we want to, probably get rid of it.
CardCollection scryBottom = new CardCollection();
for (Card c : hand) {
// Lands are evaluated separately above, factoring in the number of cards to be returned to the library
if (!c.isLand() && !toReturn.contains(c) && !willPutCardOnTop(c)) {
scryBottom.add(c);
}
}
if (!scryBottom.isEmpty()) {
CardLists.sortByCmcDesc(scryBottom);
toReturn.add(scryBottom.getFirst()); // assume the max CMC one is worse since we're not guaranteed to have lands for it
continue;
}
// If we don't want to scry anything to the bottom, remove the worst card that we have in order to satisfy
// the requirement
toReturn.add(ComputerUtilCard.getWorstAI(hand));
}
return CardCollection.getView(toReturn);
}
@Override
public void declareAttackers(Player attacker, Combat combat) {
brains.declareAttackers(attacker, combat);
@@ -612,7 +549,7 @@ public class PlayerControllerAi extends PlayerController {
@Override
public CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard) {
return brains.getCardsToDiscard(numDiscard, null, null);
return brains.getCardsToDiscard(numDiscard, (String[])null, null);
}
@Override
@@ -669,7 +606,7 @@ public class PlayerControllerAi extends PlayerController {
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
}
return SpellApiToAi.Converter.get(api).chooseNumber(player, sa, min, max, params);
}
};
@Override
public int chooseNumber(SpellAbility sa, String title, List<Integer> options, Player relatedPlayer) {
@@ -729,15 +666,14 @@ public class PlayerControllerAi extends PlayerController {
return true;
} else {
Card rem = (Card) source.getFirstRemembered();
if (!rem.isInZone(ZoneType.Battlefield)) {
if (!rem.getZone().is(ZoneType.Battlefield)) {
return true;
}
}
break;
case "BetterTgtThanRemembered":
if (source.getRememberedCount() > 0) {
Card rem = (Card) source.getFirstRemembered();
if (!rem.isInZone(ZoneType.Battlefield)) {
if (!rem.getZone().is(ZoneType.Battlefield)) {
return true;
}
for (Card c : source.getController().getCreaturesInPlay()) {
@@ -747,7 +683,6 @@ public class PlayerControllerAi extends PlayerController {
}
return false;
}
break;
default:
break;
}
@@ -866,8 +801,8 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public ReplacementEffect chooseSingleReplacementEffect(String prompt, List<ReplacementEffect> possibleReplacers) {
return brains.chooseSingleReplacementEffect(possibleReplacers);
public ReplacementEffect chooseSingleReplacementEffect(String prompt, List<ReplacementEffect> possibleReplacers, Map<String, Object> runParams) {
return brains.chooseSingleReplacementEffect(possibleReplacers, runParams);
}
@Override
@@ -941,10 +876,6 @@ public class PlayerControllerAi extends PlayerController {
final Card source = sa.getHostCard();
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
emptyAbility.setActivatingPlayer(player);
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
for (String sVar : sa.getSVars()) {
emptyAbility.setSVar(sVar, sa.getSVar(sVar));
}
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
return true;
@@ -996,6 +927,11 @@ public class PlayerControllerAi extends PlayerController {
return true;
}
@Override
public Map<GameEntity, CounterType> chooseProliferation(SpellAbility sa) {
return brains.chooseProliferation(sa);
}
@Override
public boolean chooseTargetsFor(SpellAbility currentAbility) {
return brains.doTrigger(currentAbility, true);
@@ -1072,7 +1008,7 @@ public class PlayerControllerAi extends PlayerController {
}
});
} 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) {
if (sa.hasParam("AILogic")) {
CardCollectionView aiLibrary = player.getCardsIn(ZoneType.Library);
CardCollectionView oppLibrary = player.getWeakestOpponent().getCardsIn(ZoneType.Library);
CardCollectionView oppLibrary = ComputerUtil.getOpponentFor(player).getCardsIn(ZoneType.Library);
final Card source = sa.getHostCard();
final String logic = sa.getParam("AILogic");
@@ -1154,7 +1090,7 @@ public class PlayerControllerAi extends PlayerController {
@Override
public List<Card> chooseCardsForZoneChange(
ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList, int min, int max,
ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList,
DelayedReveal delayedReveal, String selectPrompt, Player decider) {
// this isn't used
return null;
@@ -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)
Cost fullCost = opt.getCost().copy().add(costSoFar);
SpellAbility fullCostSa = chosen.copyWithDefinedCost(fullCost);
// Playability check for Kicker
if (opt.getType() == OptionalCost.Kicker1 || opt.getType() == OptionalCost.Kicker2) {
SpellAbility kickedSaCopy = fullCostSa.copy();
kickedSaCopy.addOptionalCost(opt.getType());
Card copy = CardUtil.getLKICopy(chosen.getHostCard());
copy.addOptionalCostPaid(opt.getType());
if (ComputerUtilCard.checkNeedsToPlayReqs(copy, kickedSaCopy) != AiPlayDecision.WillPlay) {
continue; // don't choose kickers we don't want to play
}
}
if (ComputerUtilCost.canPayCost(fullCostSa, player)) {
chosenOptCosts.add(opt);
costSoFar.add(opt.getCost());
@@ -1240,44 +1164,4 @@ public class PlayerControllerAi extends PlayerController {
return chosenOptCosts;
}
@Override
public boolean confirmMulliganScry(Player p) {
// Always true?
return true;
}
@Override
public int chooseNumberForKeywordCost(SpellAbility sa, Cost cost, KeywordInterface keyword, String prompt,
int max) {
// TODO: improve the logic depending on the keyword and the playability of the cost-modified SA (enough targets present etc.)
int chosenAmount = 0;
Cost costSoFar = sa.getPayCosts() != 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;
}
}

View File

@@ -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 paidCMC = cost.getConvertedManaCost();
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
return paidCMC == 3 && numManaSrcs < 3;
if (paidCMC == 3 && numManaSrcs < 3) {
// if it's a CMC 3 spell and we're more than one mana source short for it, might be worth it anyway
return true;
}
return false;
}
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() {
@@ -327,7 +335,7 @@ public class SpecialCardAi {
boolean canTrample = source.hasKeyword(Keyword.TRAMPLE);
if (!isBlocking && combat.getDefenderByAttacker(source) instanceof Card) {
int loyalty = combat.getDefenderByAttacker(source).getCounters(CounterType.LOYALTY);
int loyalty = ((Card)combat.getDefenderByAttacker(source)).getCounters(CounterType.LOYALTY);
int totalDamageToPW = 0;
for (Card atk : (combat.getAttackersOf(combat.getDefenderByAttacker(source)))) {
if (combat.isUnblocked(atk)) {
@@ -403,7 +411,11 @@ public class SpecialCardAi {
Pair<Integer, Integer> predictedPT = getPumpedPT(ai, source.getNetCombatDamage(), source.getNetToughness());
int oppT = Aggregates.sum(potentialBlockers, CardPredicates.Accessors.fnGetNetToughness);
return potentialBlockers.isEmpty() || (source.hasKeyword(Keyword.TRAMPLE) && predictedPT.getLeft() - oppT >= oppLife);
if (potentialBlockers.isEmpty() || (source.hasKeyword(Keyword.TRAMPLE) && predictedPT.getLeft() - oppT >= oppLife)) {
return true;
}
return false;
}
public static Pair<Integer, Integer> getPumpedPT(Player ai, int power, int toughness) {
@@ -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
public static class GuiltyConscience {
public static Card getBestAttachTarget(final Player ai, final SpellAbility sa, final List<Card> list) {
@@ -556,7 +525,10 @@ public class SpecialCardAi {
@Override
public boolean apply(final Card c) {
// Don't enchant creatures that can survive
return c.canBeDestroyed() && c.getNetCombatDamage() >= c.getNetToughness() && !c.isEnchantedBy("Guilty Conscience");
if (!c.canBeDestroyed() || c.getNetCombatDamage() < c.getNetToughness() || c.isEnchantedBy("Guilty Conscience")) {
return false;
}
return true;
}
});
chosen = ComputerUtilCard.getBestCreatureAI(creatures);
@@ -904,6 +876,7 @@ public class SpecialCardAi {
// Only activate in AI's own turn (sans the exception above)
return false;
}
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?
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
public static class SurvivalOfTheFittest {
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
// easier to cast right now, but only if the best card in the library is at least CMC 3
// (probably not worth it to grab low mana cost cards this way)
if (maxCMC != null && bestInLib != null && maxCMC.getCMC() < bestInLib.getCMC() && bestInLib.getCMC() >= 3) {
if (maxCMC != null && maxCMC.getCMC() < bestInLib.getCMC() && bestInLib.getCMC() >= 3) {
return maxCMC;
}
// We appear to be playing Reanimator (or we have a reanimator card in hand already), so it's
@@ -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
return aiHandSize < HAND_SIZE_THRESHOLD || maxOppHandSize - aiHandSize > HAND_SIZE_THRESHOLD;
if (aiHandSize < HAND_SIZE_THRESHOLD || maxOppHandSize - aiHandSize > HAND_SIZE_THRESHOLD) {
// use in case we're getting low on cards or if we're significantly behind our opponent in cards in hand
return true;
}
return false;
}
}
@@ -1318,7 +1261,9 @@ public class SpecialCardAi {
if (topGY == null
|| !topGY.isCreature()
|| ComputerUtilCard.evaluateCreature(creatHand) > ComputerUtilCard.evaluateCreature(topGY) + 80) {
return numCreatsInHand > 1 || !ComputerUtilMana.canPayManaCost(creatHand.getSpellPermanent(), ai, 0);
if (numCreatsInHand > 1 || !ComputerUtilMana.canPayManaCost(creatHand.getSpellPermanent(), ai, 0)) {
return true;
}
}
}
@@ -1440,6 +1385,7 @@ public class SpecialCardAi {
// Only activate in AI's own turn (sans the exception above)
return false;
}
return true;
}
}

View File

@@ -247,7 +247,7 @@ public abstract class SpellAbilityAi {
protected static boolean isSorcerySpeed(final SpellAbility sa) {
return (sa.getRootAbility().isSpell() && sa.getHostCard().isSorcery())
|| (sa.getRootAbility().isAbility() && sa.getRestrictions().isSorcerySpeed())
|| (sa.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;
}
if (sa.isPwAbility() && phase.is(PhaseType.MAIN2)) {
if (sa.getRestrictions().isPwAbility() && phase.is(PhaseType.MAIN2)) {
return true;
}
if (sa.isSpell() && !sa.isBuyBackAbility()) {

View File

@@ -21,7 +21,6 @@ public enum SpellApiToAi {
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
.put(ApiType.AddPhase, AddPhaseAi.class)
.put(ApiType.AddTurn, AddTurnAi.class)
.put(ApiType.Amass, AmassAi.class)
.put(ApiType.Animate, AnimateAi.class)
.put(ApiType.AnimateAll, AnimateAllAi.class)
.put(ApiType.Attach, AttachAi.class)
@@ -33,10 +32,8 @@ public enum SpellApiToAi {
.put(ApiType.BidLife, BidLifeAi.class)
.put(ApiType.Bond, BondAi.class)
.put(ApiType.Branch, AlwaysPlayAi.class)
.put(ApiType.CantUntapTurn, CantUntapTurnAi.class)
.put(ApiType.ChangeCombatants, CannotPlayAi.class)
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
.put(ApiType.ChangeX, AlwaysPlayAi.class)
.put(ApiType.ChangeZone, ChangeZoneAi.class)
.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class)
.put(ApiType.Charm, CharmAi.class)
@@ -63,7 +60,6 @@ public enum SpellApiToAi {
.put(ApiType.Destroy, DestroyAi.class)
.put(ApiType.DestroyAll, DestroyAllAi.class)
.put(ApiType.Dig, DigAi.class)
.put(ApiType.DigMultiple, DigMultipleAi.class)
.put(ApiType.DigUntil, DigUntilAi.class)
.put(ApiType.Discard, DiscardAi.class)
.put(ApiType.DrainMana, DrainManaAi.class)
@@ -71,7 +67,6 @@ public enum SpellApiToAi {
.put(ApiType.EachDamage, DamageEachAi.class)
.put(ApiType.Effect, EffectAi.class)
.put(ApiType.Encode, EncodeAi.class)
.put(ApiType.EndCombatPhase, EndTurnAi.class)
.put(ApiType.EndTurn, EndTurnAi.class)
.put(ApiType.ExchangeLife, LifeExchangeAi.class)
.put(ApiType.ExchangeLifeVariant, LifeExchangeVariantAi.class)

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -21,7 +22,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Player opp = ai.getWeakestOpponent();
final Player opp = ComputerUtil.getOpponentFor(ai);
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
@@ -45,7 +46,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getWeakestOpponent();
final Player opp = ComputerUtil.getOpponentFor(ai);
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
@@ -56,9 +57,12 @@ public class ActivateAbilityAi extends SpellAbilityAi {
} else {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
return defined.contains(opp);
if (!defined.contains(opp)) {
return false;
}
}
return true;
} else {
sa.resetTargets();
sa.getTargets().add(opp);
@@ -83,7 +87,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
}
} else {
sa.resetTargets();
sa.getTargets().add(ai.getWeakestOpponent());
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
}
return randomReturn;

View File

@@ -67,8 +67,10 @@ public class AddTurnAi extends SpellAbilityAi {
return false;
}
}
// TODO: improve ai for Sage of Hours
return StringUtils.isNumeric(sa.getParam("NumTurns"));
if (!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
// Human more turns.
}

View File

@@ -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);
}
}

View File

@@ -10,7 +10,6 @@ import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.cost.CostPutCounter;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -78,13 +77,13 @@ public class AnimateAi extends SpellAbilityAi {
num = (num == null) ? "1" : num;
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
ai.getWeakestOpponent(), topStack.getHostCard(), topStack);
ComputerUtil.getOpponentFor(ai), topStack.getHostCard(), topStack);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
ComputerUtilCard.sortByEvaluateCreature(list);
if (!list.isEmpty() && list.size() == nToSac && ComputerUtilCost.canPayCost(sa, ai)) {
Card animatedCopy = becomeAnimated(source, sa);
list.add(animatedCopy);
list = CardLists.getValidCards(list, valid.split(","), ai.getWeakestOpponent(), topStack.getHostCard(),
list = CardLists.getValidCards(list, valid.split(","), ComputerUtil.getOpponentFor(ai), topStack.getHostCard(),
topStack);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
if (ComputerUtilCard.evaluateCreature(animatedCopy) < ComputerUtilCard.evaluateCreature(list.get(0))
@@ -110,16 +109,11 @@ public class AnimateAi extends SpellAbilityAi {
if (ph.is(PhaseType.MAIN2) && !sa.hasParam("Permanent") && !sa.hasParam("UntilYourNextTurn")) {
return false;
}
// Don't animate if the AI won't attack anyway or use as a potential blocker
// Don't animate if the AI won't attack anyway
Player opponent = ai.getWeakestOpponent();
// Activating as a potential blocker is only viable if it's an ability activated from a permanent, otherwise
// the AI will waste resources
boolean activateAsPotentialBlocker = sa.hasParam("UntilYourNextTurn")
&& ai.getGame().getPhaseHandler().getNextTurn() != ai
&& source.isPermanent();
if (ph.isPlayerTurn(ai) && ai.getLife() < 6 && opponent.getLife() > 6
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)
&& !sa.hasParam("AILogic") && !sa.hasParam("Permanent") && !activateAsPotentialBlocker) {
&& !sa.hasParam("AILogic") && !sa.hasParam("Permanent")) {
return false;
}
return true;
@@ -207,16 +201,21 @@ public class AnimateAi extends SpellAbilityAi {
return bFlag; // All of the defined stuff is animated, not very useful
} else {
sa.resetTargets();
return animateTgtAI(sa);
if (!animateTgtAI(sa)) {
return false;
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
if (sa.usesTargeting()) {
sa.resetTargets();
return animateTgtAI(sa);
if (!animateTgtAI(sa)) {
return false;
}
}
return true;
@@ -246,11 +245,6 @@ public class AnimateAi extends SpellAbilityAi {
private boolean animateTgtAI(final SpellAbility sa) {
final Player ai = sa.getActivatingPlayer();
final PhaseHandler ph = ai.getGame().getPhaseHandler();
final boolean alwaysActivatePWAbility = sa.hasParam("Planeswalker")
&& sa.getPayCosts() != null
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class)
&& sa.getTargetRestrictions() != null
&& sa.getTargetRestrictions().getMinTargets(sa.getHostCard(), sa) == 0;
final CardType types = new CardType();
if (sa.hasParam("Types")) {
@@ -270,7 +264,7 @@ public class AnimateAi extends SpellAbilityAi {
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
// list is empty, no possible targets
if (list.isEmpty() && !alwaysActivatePWAbility) {
if (list.isEmpty()) {
return false;
}
@@ -323,7 +317,7 @@ public class AnimateAi extends SpellAbilityAi {
}
// data is empty, no good targets
if (data.isEmpty() && !alwaysActivatePWAbility) {
if (data.isEmpty()) {
return false;
}
@@ -372,16 +366,10 @@ public class AnimateAi extends SpellAbilityAi {
Integer power = null;
if (sa.hasParam("Power")) {
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
if (power == 0 && "PTByCMC".equals(sa.getParam("AILogic"))) {
power = card.getManaCost().getCMC();
}
}
Integer toughness = null;
if (sa.hasParam("Toughness")) {
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
if (toughness == 0 && "PTByCMC".equals(sa.getParam("AILogic"))) {
toughness = card.getManaCost().getCMC();
}
}
final CardType types = new CardType();
@@ -467,19 +455,26 @@ public class AnimateAi extends SpellAbilityAi {
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
final List<SpellAbility> removedAbilities = Lists.newArrayList();
boolean clearAbilities = sa.hasParam("OverwriteAbilities");
boolean clearSpells = sa.hasParam("OverwriteSpells");
boolean removeAll = sa.hasParam("RemoveAllAbilities");
boolean removeIntrinsic = sa.hasParam("RemoveIntrinsicAbilities");
if (clearSpells) {
removedAbilities.addAll(Lists.newArrayList(card.getSpells()));
}
if (sa.hasParam("RemoveThisAbility") && !removedAbilities.contains(sa)) {
removedAbilities.add(sa);
if (clearAbilities || clearSpells || removeAll) {
for (final SpellAbility ab : card.getSpellAbilities()) {
if (removeAll
|| (ab.isIntrinsic() && removeIntrinsic && !ab.isBasicLandAbility())
|| (ab.isAbility() && clearAbilities)
|| (ab.isSpell() && clearSpells)) {
card.removeSpellAbility(ab);
removedAbilities.add(ab);
}
}
}
// give abilities
@@ -487,7 +482,9 @@ public class AnimateAi extends SpellAbilityAi {
if (abilities.size() > 0) {
for (final String s : abilities) {
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) {
for (final String s : triggers) {
final String actualTrigger = source.getSVar(s);
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, false);
addedTriggers.add(parsedTrigger);
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, source, false);
addedTriggers.add(card.addTrigger(parsedTrigger));
}
}
@@ -506,35 +503,31 @@ public class AnimateAi extends SpellAbilityAi {
if (replacements.size() > 0) {
for (final String s : replacements) {
final String actualReplacement = source.getSVar(s);
final ReplacementEffect parsedReplacement = ReplacementHandler.parseReplacement(actualReplacement, card, false);
addedReplacements.add(parsedReplacement);
final ReplacementEffect parsedReplacement = ReplacementHandler.parseReplacement(actualReplacement,
source, false);
addedReplacements.add(card.addReplacementEffect(parsedReplacement));
}
}
// give static abilities (should only be used by cards to give
// itself a static ability)
final List<StaticAbility> addedStaticAbilities = Lists.newArrayList();
if (stAbs.size() > 0) {
for (final String s : stAbs) {
final String actualAbility = source.getSVar(s);
addedStaticAbilities.add(new StaticAbility(actualAbility, card));
// suppress triggers from the animated card
final List<Trigger> removedTriggers = Lists.newArrayList();
if (sa.hasParam("OverwriteTriggers") || removeAll || removeIntrinsic) {
for (final Trigger trigger : card.getTriggers()) {
if (removeIntrinsic && !trigger.isIntrinsic()) {
continue;
}
trigger.setSuppressed(true);
removedTriggers.add(trigger);
}
}
if (removeAll || removeIntrinsic
|| !addedAbilities.isEmpty() || !removedAbilities.isEmpty() || !addedTriggers.isEmpty()
|| !addedReplacements.isEmpty() || !addedStaticAbilities.isEmpty()) {
card.addChangedCardTraits(addedAbilities, removedAbilities, addedTriggers, addedReplacements,
addedStaticAbilities, removeAll, false, removeIntrinsic, timestamp);
}
// give static abilities (should only be used by cards to give
// itself a static ability)
if (stAbs.size() > 0) {
for (final String s : stAbs) {
final String actualAbility = source.getSVar(s);
final StaticAbility stAb = card.addStaticAbility(actualAbility);
if ("Continuous".equals(stAb.getParam("Mode"))) {
if ("Continuous".equals(stAb.getMapParams().get("Mode"))) {
for (final StaticAbilityLayer layer : stAb.getLayers()) {
StaticAbilityContinuous.applyContinuousAbility(stAb, new CardCollection(card), layer);
}
@@ -555,6 +548,30 @@ public class AnimateAi extends SpellAbilityAi {
card.setSVar(name, actualsVar);
}
}
// suppress static abilities from the animated card
final List<StaticAbility> removedStatics = Lists.newArrayList();
if (sa.hasParam("OverwriteStatics") || removeAll || removeIntrinsic) {
for (final StaticAbility stAb : card.getStaticAbilities()) {
if (removeIntrinsic && !stAb.isIntrinsic()) {
continue;
}
stAb.setTemporarilySuppressed(true);
removedStatics.add(stAb);
}
}
// suppress static abilities from the animated card
final List<ReplacementEffect> removedReplacements = Lists.newArrayList();
if (sa.hasParam("OverwriteReplacements") || removeAll || removeIntrinsic) {
for (final ReplacementEffect re : card.getReplacementEffects()) {
if (removeIntrinsic && !re.isIntrinsic()) {
continue;
}
re.setTemporarilySuppressed(true);
removedReplacements.add(re);
}
}
ComputerUtilCard.applyStaticContPT(game, card, null);
}

View File

@@ -8,7 +8,7 @@ public class AnimateAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return "Always".equals(sa.getParam("AILogic"));
return false;
} // end animateAllCanPlayAI()
@Override

View File

@@ -31,7 +31,10 @@ import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
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 {
@@ -120,7 +123,9 @@ public class AttachAi extends SpellAbilityAi {
return !(c.hasProtectionFrom(source) || c.hasKeyword(Keyword.SHROUD) || c.hasKeyword(Keyword.HEXPROOF));
}
});
return !targets.isEmpty();
if (targets.isEmpty()) {
return false;
}
}
return true;
@@ -228,13 +233,8 @@ public class AttachAi extends SpellAbilityAi {
boolean alternativeConsiderations = hasFloatMana || willDiscardNow || willDieNow || willRespondToStack || willCastAtEOT || willCastEarly;
if (!alternativeConsiderations) {
if (combat == null ||
game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
return combat.isAttacking(attachTarget) || combat.isBlocking(attachTarget);
if (!alternativeConsiderations && (combat == null || game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) || (!combat.isAttacking(attachTarget) && !combat.isBlocking(attachTarget))) {
return false;
}
return true;
@@ -448,7 +448,7 @@ public class AttachAi extends SpellAbilityAi {
*/
private static Player attachToPlayerAIPreferences(final Player aiPlayer, final SpellAbility sa,
final boolean mandatory) {
List<Player> targetable = new ArrayList<>();
List<Player> targetable = new ArrayList<Player>();
for (final Player player : aiPlayer.getGame().getPlayers()) {
if (sa.canTarget(player)) {
targetable.add(player);
@@ -852,7 +852,7 @@ public class AttachAi extends SpellAbilityAi {
int totToughness = 0;
int totPower = 0;
final List<String> keywords = new ArrayList<>();
final List<String> keywords = new ArrayList<String>();
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
final Map<String, String> stabMap = stAbility.getMapParams();
@@ -872,11 +872,15 @@ public class AttachAi extends SpellAbilityAi {
String kws = stabMap.get("AddKeyword");
if (kws != null) {
keywords.addAll(Arrays.asList(kws.split(" & ")));
for (final String kw : kws.split(" & ")) {
keywords.add(kw);
}
}
kws = stabMap.get("AddHiddenKeyword");
if (kws != null) {
keywords.addAll(Arrays.asList(kws.split(" & ")));
for (final String kw : kws.split(" & ")) {
keywords.add(kw);
}
}
}
}
@@ -899,7 +903,7 @@ public class AttachAi extends SpellAbilityAi {
Card c = null;
if (prefList == null || prefList.isEmpty()) {
prefList = new ArrayList<>(list);
prefList = new ArrayList<Card>(list);
} else {
c = ComputerUtilCard.getBestAI(prefList);
if (c != null) {
@@ -953,7 +957,7 @@ public class AttachAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Card card = sa.getHostCard();
// Check if there are any valid targets
List<GameObject> targets = new ArrayList<>();
List<GameObject> targets = new ArrayList<GameObject>();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
@@ -976,7 +980,9 @@ public class AttachAi extends SpellAbilityAi {
return false;
}
// don't equip creatures that don't gain anything
return !card.hasSVar("NonStackingAttachEffect") || !newTarget.isEquippedBy(card.getName());
if (card.hasSVar("NonStackingAttachEffect") && newTarget.isEquippedBy(card.getName())) {
return false;
}
}
}
@@ -1143,10 +1149,8 @@ public class AttachAi extends SpellAbilityAi {
int totToughness = 0;
int totPower = 0;
final List<String> keywords = new ArrayList<>();
final List<String> keywords = new ArrayList<String>();
boolean grantingAbilities = false;
boolean grantingExtraBlock = false;
boolean grantingCantUntap = false;
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
final Map<String, String> stabMap = stAbility.getMapParams();
@@ -1165,16 +1169,18 @@ public class AttachAi extends SpellAbilityAi {
totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), stAbility);
grantingAbilities |= stabMap.containsKey("AddAbility");
grantingExtraBlock |= stabMap.containsKey("CanBlockAmount") || stabMap.containsKey("CanBlockAny");
grantingCantUntap |= stabMap.containsKey("CantUntap");
String kws = stabMap.get("AddKeyword");
if (kws != null) {
keywords.addAll(Arrays.asList(kws.split(" & ")));
for (final String kw : kws.split(" & ")) {
keywords.add(kw);
}
}
kws = stabMap.get("AddHiddenKeyword");
if (kws != null) {
keywords.addAll(Arrays.asList(kws.split(" & ")));
for (final String kw : kws.split(" & ")) {
keywords.add(kw);
}
}
}
}
@@ -1196,30 +1202,19 @@ public class AttachAi extends SpellAbilityAi {
}
//only add useful keywords unless P/T bonus is significant
if (totToughness + totPower < 4 && (!keywords.isEmpty() || grantingExtraBlock)) {
if (totToughness + totPower < 4 && !keywords.isEmpty()) {
final int pow = totPower;
final boolean extraBlock = grantingExtraBlock;
final boolean cantUntap = grantingCantUntap;
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!keywords.isEmpty()) {
for (final String keyword : keywords) {
if (isUsefulAttachKeyword(keyword, c, sa, pow)) {
return true;
}
for (final String keyword : keywords) {
if (isUsefulAttachKeyword(keyword, c, sa, pow)) {
return true;
}
if (c.hasKeyword(Keyword.INFECT) && pow >= 2) {
// consider +2 power a significant bonus on Infect creatures
return true;
}
}
if (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;
}
@@ -1351,7 +1346,7 @@ public class AttachAi extends SpellAbilityAi {
CardCollection prefList = list;
// Filter AI-specific targets if provided
prefList = ComputerUtil.filterAITgts(sa, aiPlayer, list, true);
prefList = ComputerUtil.filterAITgts(sa, aiPlayer, (CardCollection)list, true);
Card c = attachGeneralAI(aiPlayer, sa, prefList, mandatory, attachSource, sa.getParam("AILogic"));
@@ -1555,52 +1550,86 @@ public class AttachAi extends SpellAbilityAi {
}
if (evasive) {
return card.getNetCombatDamage() + powerBonus > 0
&& ComputerUtilCombat.canAttackNextTurn(card)
&& canBeBlocked;
if (card.getNetCombatDamage() + powerBonus <= 0
|| !ComputerUtilCombat.canAttackNextTurn(card)
|| !canBeBlocked) {
return false;
}
} else if (keyword.equals("Haste")) {
return card.hasSickness() && ph.isPlayerTurn(sa.getActivatingPlayer()) && !card.isTapped()
&& card.getNetCombatDamage() + powerBonus > 0
&& !card.hasKeyword("CARDNAME can attack as though it had haste.")
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& ComputerUtilCombat.canAttackNextTurn(card);
if (!card.hasSickness() || !ph.isPlayerTurn(sa.getActivatingPlayer()) || card.isTapped()
|| card.getNetCombatDamage() + powerBonus <= 0
|| card.hasKeyword("CARDNAME can attack as though it had haste.")
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| !ComputerUtilCombat.canAttackNextTurn(card)) {
return false;
}
} else if (keyword.endsWith("Indestructible")) {
return true;
} else if (keyword.endsWith("Deathtouch") || keyword.endsWith("Wither")) {
return card.getNetCombatDamage() + powerBonus > 0
&& ((canBeBlocked && ComputerUtilCombat.canAttackNextTurn(card))
|| CombatUtil.canBlock(card, true));
if (card.getNetCombatDamage() + powerBonus <= 0
|| ((!canBeBlocked || !ComputerUtilCombat.canAttackNextTurn(card))
&& !CombatUtil.canBlock(card, true))) {
return false;
}
} else if (keyword.equals("Double Strike") || keyword.equals("Lifelink")) {
return card.getNetCombatDamage() + powerBonus > 0
&& (ComputerUtilCombat.canAttackNextTurn(card) || CombatUtil.canBlock(card, true));
if (card.getNetCombatDamage() + powerBonus <= 0
|| (!ComputerUtilCombat.canAttackNextTurn(card) && !CombatUtil.canBlock(card, true))) {
return false;
}
} else if (keyword.equals("First Strike")) {
return card.getNetCombatDamage() + powerBonus > 0 && !card.hasKeyword(Keyword.DOUBLE_STRIKE)
&& (ComputerUtilCombat.canAttackNextTurn(card) || CombatUtil.canBlock(card, true));
if (card.getNetCombatDamage() + powerBonus <= 0 || card.hasKeyword(Keyword.DOUBLE_STRIKE)
|| (!ComputerUtilCombat.canAttackNextTurn(card) && !CombatUtil.canBlock(card, true))) {
return false;
}
} else if (keyword.startsWith("Flanking")) {
return card.getNetCombatDamage() + powerBonus > 0
&& ComputerUtilCombat.canAttackNextTurn(card)
&& canBeBlocked;
if (card.getNetCombatDamage() + powerBonus <= 0
|| !ComputerUtilCombat.canAttackNextTurn(card)
|| !canBeBlocked) {
return false;
}
} else if (keyword.startsWith("Bushido")) {
return (canBeBlocked && ComputerUtilCombat.canAttackNextTurn(card))
|| CombatUtil.canBlock(card, true);
if ((!canBeBlocked || !ComputerUtilCombat.canAttackNextTurn(card))
&& !CombatUtil.canBlock(card, true)) {
return false;
}
} else if (keyword.equals("Trample")) {
return card.getNetCombatDamage() + powerBonus > 1
&& canBeBlocked
&& ComputerUtilCombat.canAttackNextTurn(card);
if (card.getNetCombatDamage() + powerBonus <= 1
|| !canBeBlocked
|| !ComputerUtilCombat.canAttackNextTurn(card)) {
return false;
}
} else if (keyword.equals("Infect")) {
return card.getNetCombatDamage() + powerBonus > 0
&& ComputerUtilCombat.canAttackNextTurn(card);
if (card.getNetCombatDamage() + powerBonus <= 0
|| !ComputerUtilCombat.canAttackNextTurn(card)) {
return false;
}
} else if (keyword.equals("Vigilance")) {
return card.getNetCombatDamage() + powerBonus > 0
&& ComputerUtilCombat.canAttackNextTurn(card)
&& CombatUtil.canBlock(card, true);
if (card.getNetCombatDamage() + powerBonus <= 0
|| !ComputerUtilCombat.canAttackNextTurn(card)
|| !CombatUtil.canBlock(card, true)) {
return false;
}
} else if (keyword.equals("Reach")) {
return !card.hasKeyword(Keyword.FLYING) && CombatUtil.canBlock(card, true);
if (card.hasKeyword(Keyword.FLYING) || !CombatUtil.canBlock(card, true)) {
return false;
}
} else if (keyword.endsWith("CARDNAME can block an additional creature each combat.")) {
if (!CombatUtil.canBlock(card, true) || card.hasKeyword("CARDNAME can block any number of creatures.")
|| card.hasKeyword("CARDNAME can block an additional ninety-nine creatures each combat.")) {
return false;
}
} else if (keyword.equals("CARDNAME can attack as though it didn't have defender.")) {
return card.hasKeyword(Keyword.DEFENDER) && card.getNetCombatDamage() + powerBonus > 0;
if (!card.hasKeyword(Keyword.DEFENDER) || card.getNetCombatDamage() + powerBonus <= 0) {
return false;
}
} else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) {
return !card.hasKeyword(Keyword.SHROUD) && !card.hasKeyword(Keyword.HEXPROOF);
} else return !keyword.equals("Defender");
if (card.hasKeyword(Keyword.SHROUD) || card.hasKeyword(Keyword.HEXPROOF)) {
return false;
}
} else if (keyword.equals("Defender")) {
return false;
}
return true;
}
/**
@@ -1621,11 +1650,17 @@ public class AttachAi extends SpellAbilityAi {
if (keyword.endsWith("CARDNAME can't attack.") || keyword.equals("Defender")
|| keyword.endsWith("CARDNAME can't attack or block.")) {
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
if (!ComputerUtilCombat.canAttackNextTurn(card) || card.getNetCombatDamage() < 1) {
return false;
}
} else if (keyword.endsWith("CARDNAME attacks each turn if able.") || keyword.endsWith("CARDNAME attacks each combat if able.")) {
return ComputerUtilCombat.canAttackNextTurn(card) && CombatUtil.canBlock(card, true) && !ai.getCreaturesInPlay().isEmpty();
if (!ComputerUtilCombat.canAttackNextTurn(card) || !CombatUtil.canBlock(card, true) || ai.getCreaturesInPlay().isEmpty()) {
return false;
}
} else if (keyword.endsWith("CARDNAME can't block.") || keyword.contains("CantBlock")) {
return CombatUtil.canBlock(card, true);
if (!CombatUtil.canBlock(card, true)) {
return false;
}
} else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) {
for (SpellAbility ability : card.getSpellAbilities()) {
if (ability.isAbility()) {
@@ -1634,10 +1669,18 @@ public class AttachAi extends SpellAbilityAi {
}
return false;
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")) {
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
if (!ComputerUtilCombat.canAttackNextTurn(card) || card.getNetCombatDamage() < 1) {
return false;
}
} else if (keyword.endsWith("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|| keyword.endsWith("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 2;
if (!ComputerUtilCombat.canAttackNextTurn(card) || card.getNetCombatDamage() < 2) {
return false;
}
} else if (keyword.endsWith("CARDNAME doesn't untap during your untap step.")) {
if (card.isUntapped()) {
return false;
}
}
return true;
}
@@ -1661,8 +1704,12 @@ public class AttachAi extends SpellAbilityAi {
return true;
}
// useless to equip a creature that can't attack or block.
return !sa.getHostCard().isEquipment() || !ComputerUtilCard.isUselessCreature(ai, c);
if (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) {

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
@@ -16,7 +17,7 @@ public class BalanceAi extends SpellAbilityAi {
int diff = 0;
// TODO Add support for multiplayer logic
final Player opp = aiPlayer.getWeakestOpponent();
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
if (tgt != null) {
sa.resetTargets();
if (tgt.canTgtCreature()) {
List<Card> list = CardLists.getTargetableCards(aiPlayer.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), sa);
List<Card> list = CardLists.getTargetableCards(ComputerUtil.getOpponentFor(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
if (list.isEmpty()) {
return false;

View File

@@ -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);
}
}

View File

@@ -10,7 +10,6 @@ import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
@@ -19,7 +18,6 @@ import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.cost.CostDiscard;
import forge.game.cost.CostPart;
import forge.game.cost.CostPutCounter;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -28,7 +26,6 @@ import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
@@ -73,29 +70,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
return false;
}
}
} else if (aiLogic.equals("NoSameCreatureType")) {
final List<ZoneType> origin = Lists.newArrayList();
if (sa.hasParam("Origin")) {
origin.addAll(ZoneType.listValueOf(sa.getParam("Origin")));
} else if (sa.hasParam("TgtZone")) {
origin.addAll(ZoneType.listValueOf(sa.getParam("TgtZone")));
}
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(origin),
sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
final List<String> creatureTypes = Lists.newArrayList();
for (Card c : list) {
creatureTypes.addAll(c.getType().getCreatureTypes());
}
for (String type : creatureTypes) {
int freq = Collections.frequency(creatureTypes, type);
if (freq > 1) {
return false;
}
}
return true;
}
return super.checkAiLogic(ai, sa, aiLogic);
@@ -122,8 +96,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
return SpecialCardAi.LivingDeath.consider(aiPlayer, sa);
} else if (aiLogic.equals("TheScarabGod")) {
return SpecialCardAi.TheScarabGod.consider(aiPlayer, sa);
} else if (aiLogic.equals("SorinVengefulBloodlord")) {
return SpecialCardAi.SorinVengefulBloodlord.consider(aiPlayer, sa);
} else if (aiLogic.equals("Intuition")) {
// This logic only fills the multiple cards array, the decision to play is made
// separately in hiddenOriginCanPlayAI later.
@@ -230,7 +202,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
ZoneType origin = null;
final Player opponent = ai.getWeakestOpponent();
final Player opponent = ComputerUtil.getOpponentFor(ai);
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
if (sa.hasParam("Origin")) {
@@ -344,11 +316,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
for (final Player p : pDefined) {
CardCollectionView list = p.getCardsIn(origin);
// remove cards that won't be seen if library can't be searched
if (!ai.canSearchLibraryWith(sa, p)) {
list = CardLists.filter(list, Predicates.not(CardPredicates.inZone(ZoneType.Library)));
}
if (type != null && p == ai) {
// AI only "knows" about his information
list = CardLists.getValidCards(list, type, source.getController(), source);
@@ -356,7 +323,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
@Override
public boolean apply(final Card c) {
if (c.getType().isLegendary()) {
return !ai.isCardInPlay(c.getName());
if (ai.isCardInPlay(c.getName())) {
return false;
}
}
return true;
}
@@ -438,7 +407,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// if putting cards from hand to library and parent is drawing cards
// make sure this will actually do something:
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = aiPlayer.getWeakestOpponent();
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
if (tgt != null && tgt.canTgtPlayer()) {
boolean isCurse = sa.isCurse();
if (isCurse && sa.canTarget(opp)) {
@@ -483,7 +452,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
}
List<ZoneType> origin = new ArrayList<>();
List<ZoneType> origin = new ArrayList<ZoneType>();
if (sa.hasParam("Origin")) {
origin = ZoneType.listValueOf(sa.getParam("Origin"));
}
@@ -499,7 +468,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
Iterable<Player> pDefined;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if ((tgt != null) && tgt.canTgtPlayer()) {
final Player opp = ai.getWeakestOpponent();
final Player opp = ComputerUtil.getOpponentFor(ai);
if (sa.isCurse()) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
@@ -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
final CardCollectionView combined = CardCollection.combine(ai.getCardsIn(ZoneType.Battlefield), ai.getCardsIn(ZoneType.Hand));
final List<String> basics = new ArrayList<>();
final List<String> basics = new ArrayList<String>();
// what types can I go get?
for (final String name : MagicColor.Constant.BASIC_LANDS) {
@@ -618,7 +587,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
*/
private static Card chooseCreature(final Player ai, CardCollection list) {
// Creating a new combat for testing purposes.
final Player opponent = ai.getWeakestOpponent();
final Player opponent = ComputerUtil.getOpponentFor(ai);
Combat combat = new Combat(opponent);
for (Card att : opponent.getCreaturesInPlay()) {
combat.addAttacker(att, ai);
@@ -702,8 +671,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
// only use blink or bounce effects
if (!(destination.equals(ZoneType.Exile)
&& (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone || "DelayedBlink".equals(sa.getParam("AILogic"))))
if (!(destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone))
&& !destination.equals(ZoneType.Hand)) {
return false;
}
@@ -737,7 +705,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
final AbilitySub subAb = sa.getSubAbility();
return subAb == null || SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(ai, subAb);
if (subAb != null && !SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(ai, subAb)) {
return false;
}
return true;
}
/*
@@ -859,7 +831,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
list = CardLists.getTargetableCards(list, sa);
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, list, true);
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, true);
if (sa.hasParam("AITgtsOnlyBetterThanSelf")) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
@@ -930,7 +902,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
&& !currCombat.getBlockers(attacker).isEmpty()) {
ComputerUtilCard.sortByEvaluateCreature(blockers);
Combat combat = new Combat(ai);
combat.addAttacker(attacker, ai.getWeakestOpponent());
combat.addAttacker(attacker, ComputerUtil.getOpponentFor(ai));
for (Card blocker : blockers) {
combat.addBlocker(attacker, blocker);
}
@@ -959,7 +931,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// if it's blink or bounce, try to save my about to die stuff
final boolean blink = (destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger
|| "DelayedBlink".equals(sa.getParam("AILogic")) || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered"))));
|| (subApi == ApiType.ChangeZone && subAffected.equals("Remembered"))));
if ((destination.equals(ZoneType.Hand) || blink) && (tgt.getMinTargets(sa.getHostCard(), sa) <= 1)) {
// save my about to die stuff
Card tobounce = canBouncePermanent(ai, sa, list);
@@ -990,7 +962,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
@Override
public boolean apply(final Card c) {
for (Card aura : c.getEnchantedBy()) {
return aura.getController().isOpponentOf(ai);
if (aura.getController().isOpponentOf(ai)) {
return true;
} else {
return false;
}
}
if (blink) {
return c.isToken();
@@ -1103,11 +1079,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
}
boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.getTargetRestrictions() != null
&& sa.getTargetRestrictions().getMinTargets(source, sa) == 0 && sa.getPayCosts() != null
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class);
if (list.isEmpty() && !doWithoutTarget) {
if (list.isEmpty()) {
return false;
}
@@ -1189,11 +1161,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (!mandatory) {
sa.resetTargets();
}
if (!doWithoutTarget) {
return false;
} else {
break;
}
return false;
} else {
if (!sa.isTrigger() && !ComputerUtil.shouldCastLessThanMax(ai, source)) {
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);
sa.getTargets().add(choice);
}
@@ -1266,7 +1225,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
// filter out untargetables
CardCollectionView aiPermanents = CardLists
.filterControlledBy(list, ai);
CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS);
// Felidar Guardian + Saheeli Rai combo support
if (sa.getHostCard().getName().equals("Felidar Guardian")) {
@@ -1282,7 +1240,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
final List<GameObject> objects = ComputerUtil
.predictThreatenedObjects(ai, sa);
final List<Card> threatenedTargets = new ArrayList<>();
final List<Card> threatenedTargets = new ArrayList<Card>();
for (final Card c : aiPermanents) {
if (objects.contains(c)) {
@@ -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;
}
@@ -1462,12 +1393,16 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (!list.isEmpty()) {
final Card attachedTo = list.get(0);
// This code is for the Dragon auras
return !attachedTo.getController().isOpponentOf(ai);
if (attachedTo.getController().isOpponentOf(ai)) {
return false;
}
}
}
} else if (isPreferredTarget(ai, sa, mandatory, true)) {
// do nothing
} else return isUnpreferredTarget(ai, sa, mandatory);
} else if (!isUnpreferredTarget(ai, sa, mandatory)) {
return false;
}
return true;
}
@@ -1486,8 +1421,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
} else if ("WorstCard".equals(logic)) {
return ComputerUtilCard.getWorstAI(fetchList);
} else if ("BestCard".equals(logic)) {
return ComputerUtilCard.getBestAI(fetchList); // generally also means the most expensive one or close to it
} else if ("Mairsil".equals(logic)) {
return SpecialCardAi.MairsilThePretender.considerCardFromList(fetchList);
} else if ("SurvivalOfTheFittest".equals(logic)) {
@@ -1519,7 +1452,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
@Override
public boolean apply(final Card c) {
if (c.getType().isLegendary()) {
return !decider.isCardInPlay(c.getName());
if (decider.isCardInPlay(c.getName())) {
return false;
}
}
return true;
}
@@ -1528,7 +1463,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
fetchList = CardLists.filter(fetchList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !ComputerUtilCard.isCardRemAIDeck(c) && !ComputerUtilCard.isCardRemRandomDeck(c);
if (ComputerUtilCard.isCardRemAIDeck(c) || ComputerUtilCard.isCardRemRandomDeck(c)) {
return false;
}
return true;
}
});
}
@@ -1700,7 +1638,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
@Override
public boolean apply(final Card c) {
if (c.getType().isLegendary()) {
return !ai.isCardInPlay(c.getName());
if (ai.isCardInPlay(c.getName())) {
return false;
}
}
return true;
}
@@ -1790,8 +1730,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
public boolean doReturnCommanderLogic(SpellAbility sa, Player aiPlayer) {
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>)sa.getReplacingObject(AbilityKey.OriginalParams);
SpellAbility causeSa = (SpellAbility)originalParams.get(AbilityKey.Cause);
Map<String, Object> originalParams = (Map<String, Object>)sa.getReplacingObject("OriginalParams");
SpellAbility causeSa = (SpellAbility)originalParams.get("Cause");
SpellAbility causeSub = null;
// Squee, the Immortal: easier to recast it (the call below has to be "contains" since SA is an intrinsic effect)
@@ -1806,16 +1746,20 @@ public class ChangeZoneAi extends SpellAbilityAi {
&& "Battlefield".equals(causeSub.getParam("Destination"))) {
// A blink effect implemented using ChangeZone API
return false;
} else // This is an intrinsic effect that blinks the card (e.g. Obzedat, Ghost Council), no need to
// return the commander to the Command zone.
if (subApi == ApiType.DelayedTrigger) {
} else if (subApi == ApiType.DelayedTrigger) {
SpellAbility exec = causeSub.getAdditionalAbility("Execute");
if (exec != null && exec.getApi() == ApiType.ChangeZone) {
// A blink effect implemented using a delayed trigger
return !"Exile".equals(exec.getParam("Origin")) || !"Battlefield".equals(exec.getParam("Destination"));
if ("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))
|| !causeSa.getActivatingPlayer().equals(aiPlayer);
} else if (causeSa.getHostCard() != null && causeSa.getHostCard().equals((Card)sa.getReplacingObject("Card"))
&& causeSa.getActivatingPlayer().equals(aiPlayer)) {
// This is an intrinsic effect that blinks the card (e.g. Obzedat, Ghost Council), no need to
// return the commander to the Command zone.
return false;
}
}
// Normally we want the commander back in Command zone to recast him later

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.*;
@@ -58,11 +57,6 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
CardCollectionView oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
CardCollectionView computerType = ai.getCardsIn(origin);
// remove cards that won't be seen in AI's own library if it can't be searched
if (!ai.canSearchLibraryWith(sa, ai)) {
computerType = CardLists.filter(computerType, Predicates.not(CardPredicates.inZone(ZoneType.Library)));
}
// Ugin check need to be done before filterListByType because of ChosenX
// Ugin AI: always try to sweep before considering +1
if (sourceName.equals("Ugin, the Spirit Dragon")) {
@@ -335,8 +329,11 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
return true;
// if AI creature is better than Human Creature
return ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard
.evaluateCreatureList(humanCards);
if (ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard
.evaluateCreatureList(humanCards)) {
return true;
}
return false;
}
return true;
}
@@ -438,21 +435,29 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
if (sa.getParam("GainControl") != null) {
// Check if the cards are valuable enough
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
return (ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
.evaluateCreatureList(humanType)) >= 1;
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
.evaluateCreatureList(humanType)) < 1) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else return (ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
.evaluatePermanentList(humanType)) >= 1;
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
.evaluatePermanentList(humanType)) < 1) {
return false;
}
} else {
// don't activate if human gets more back than AI does
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
return ComputerUtilCard.evaluateCreatureList(computerType) > ComputerUtilCard
.evaluateCreatureList(humanType);
if (ComputerUtilCard.evaluateCreatureList(computerType) <= ComputerUtilCard
.evaluateCreatureList(humanType)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else return ComputerUtilCard.evaluatePermanentList(computerType) > ComputerUtilCard
.evaluatePermanentList(humanType);
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= ComputerUtilCard
.evaluatePermanentList(humanType)) {
return false;
}
}
}

View File

@@ -8,6 +8,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
@@ -71,15 +72,21 @@ public class ChooseCardAi extends SpellAbilityAi {
choices = CardLists.filterControlledBy(choices, ai.getOpponents());
}
if (aiLogic.equals("AtLeast1") || aiLogic.equals("OppPreferred")) {
return !choices.isEmpty();
if (choices.isEmpty()) {
return false;
}
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
return choices.size() >= 2;
if (choices.size() < 2) {
return false;
}
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) {
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
choices = CardLists.getValidCards(choices, filter, host.getController(), host);
return !choices.isEmpty();
if (choices.isEmpty()) {
return false;
}
} else if (aiLogic.equals("Never")) {
return false;
} else if (aiLogic.equals("NeedsPrevention")) {
@@ -97,7 +104,9 @@ public class ChooseCardAi extends SpellAbilityAi {
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
}
});
return !choices.isEmpty();
if (choices.isEmpty()) {
return false;
}
} else if (aiLogic.equals("Ashiok")) {
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
for (int i = loyalty; i >= 0; i--) {
@@ -109,12 +118,16 @@ public class ChooseCardAi extends SpellAbilityAi {
}
}
return !choices.isEmpty();
if (choices.isEmpty()) {
return false;
}
} 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")) {
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay();
CardCollection oppCreatures = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
@@ -127,8 +140,10 @@ public class ChooseCardAi extends SpellAbilityAi {
aiCreatures.remove(chosen);
int minGain = 200;
return (ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) < ComputerUtilCard
.evaluateCreatureList(oppCreatures);
if ((ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) >= ComputerUtilCard
.evaluateCreatureList(oppCreatures)) {
return false;
}
}
return true;
}

View File

@@ -41,7 +41,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(ai.getWeakestOpponent());
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
} else {
sa.getTargets().add(ai);
}

View File

@@ -52,13 +52,16 @@ public class ChooseColorAi extends SpellAbilityAi {
}
if ("Addle".equals(sourceName)) {
return !ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && !ai.getWeakestOpponent().getCardsIn(ZoneType.Hand).isEmpty();
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).isEmpty()) {
return false;
}
return true;
}
if (logic.equals("MostExcessOpponentControls")) {
for (byte color : MagicColor.WUBRG) {
CardCollectionView ailist = ai.getCardsIn(ZoneType.Battlefield);
CardCollectionView opplist = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
CardCollectionView opplist = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
ailist = CardLists.filter(ailist, CardPredicates.isColor(color));
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));

View File

@@ -33,7 +33,9 @@ public class ChooseDirectionAi extends SpellAbilityAi {
CardCollection right = CardLists.filterControlledBy(all, game.getNextPlayerAfter(ai, Direction.Right));
int leftValue = Aggregates.sum(left, CardPredicates.Accessors.fnGetCmc);
int rightValue = Aggregates.sum(right, CardPredicates.Accessors.fnGetCmc);
return aiValue <= leftValue && aiValue <= rightValue;
if (aiValue > leftValue || aiValue > rightValue) {
return false;
}
}
}
return true;

View File

@@ -1,14 +1,28 @@
package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
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.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.CardUtil;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
@@ -21,9 +35,6 @@ import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.collect.FCollection;
import java.util.List;
import java.util.Map;
public class ChooseGenericEffectAi extends SpellAbilityAi {
@@ -39,8 +50,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
return true;
}
}
} else if ("GideonBlackblade".equals(aiLogic)) {
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
}
return false;
}
@@ -86,8 +95,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
return spells.get(0);
} else if ("Random".equals(logic)) {
return Aggregates.random(spells);
} else if ("GideonBlackblade".equals(logic)) {
return SpecialCardAi.GideonBlackblade.chooseSpellAbility(player, sa, spells);
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() {
@Override
@@ -103,7 +110,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
Cost unless = new Cost(unlessCost, false);
SpellAbility paycost = new SpellAbility.EmptySa(sa.getHostCard(), player);
paycost.setPayCosts(unless);
if (ComputerUtilCost.willPayUnlessCost(sp, player, unless, false, new FCollection<>(player))
if (ComputerUtilCost.willPayUnlessCost(sp, player, unless, false, new FCollection<Player>(player))
&& ComputerUtilCost.canPayCost(paycost, player)) {
return sp;
}
@@ -385,7 +392,9 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
final Player opp = player.getWeakestOpponent();
if (opp != null) {
// TODO add predict Combat Damage?
return opp.getLife() < copy.getNetPower();
if (opp.getLife() < copy.getNetPower()) {
return true;
}
}
// haste might not be good enough?

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -16,7 +17,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
Player opp = aiPlayer.getWeakestOpponent();
Player opp = ComputerUtil.getOpponentFor(aiPlayer);
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {

View File

@@ -7,6 +7,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
@@ -67,7 +68,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
Player opp = ai.getWeakestOpponent();
Player opp = ComputerUtil.getOpponentFor(ai);
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {
@@ -97,7 +98,10 @@ public class ChooseSourceAi extends SpellAbilityAi {
return false;
}
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
return ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) > 0;
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
return false;
}
return true;
}
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
return false;
@@ -116,7 +120,9 @@ public class ChooseSourceAi extends SpellAbilityAi {
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
}
});
return !choices.isEmpty();
if (choices.isEmpty()) {
return false;
}
}
}

View File

@@ -12,6 +12,7 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
@@ -20,6 +21,7 @@ public class CloneAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Game game = source.getGame();
@@ -37,13 +39,27 @@ public class CloneAi extends SpellAbilityAi {
// TODO - add some kind of check for during human turn to answer
// "Can I use this to block something?"
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
PhaseHandler phase = game.getPhaseHandler();
// don't use instant speed clone abilities outside computers
// Combat_Begin step
if (!phase.is(PhaseType.COMBAT_BEGIN)
&& phase.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
return false;
}
PhaseHandler phase = game.getPhaseHandler();
// don't use instant speed clone abilities outside humans
// Combat_Declare_Attackers_InstantAbility step
if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) {
return false;
}
if (!sa.usesTargeting()) {
// don't activate during main2 unless this effect is permanent
if (phase.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) {
return false;
}
if (null == tgt) {
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
boolean bFlag = false;
@@ -170,38 +186,19 @@ public class CloneAi extends SpellAbilityAi {
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
Player targetedPlayer) {
final Card host = sa.getHostCard();
final Player ctrl = host.getController();
final Card cloneTarget = getCloneTarget(sa);
final boolean isOpp = cloneTarget.getController().isOpponentOf(sa.getActivatingPlayer());
final boolean isVesuva = "Vesuva".equals(host.getName());
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
// TODO: rewrite this block so that this is done somehow more elegantly
if (canCloneLegendary) {
filter = filter.replace(".nonLegendary+", ".").replace(".nonLegendary", "");
}
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
if (!newOptions.isEmpty()) {
options = newOptions;
}
if (sa.hasParam("AiChoiceLogic")) {
final String logic = sa.getParam("AiChoiceLogic");
if ("BestOppCtrl".equals(logic)) {
options = CardLists.filterControlledBy(options, ctrl.getOpponents());
}
}
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
Card choice = ComputerUtilCard.getBestAI(options);
if (isVesuva && "Vesuva".equals(choice.getName())) {
choice = null;
}
@@ -209,41 +206,4 @@ public class CloneAi extends SpellAbilityAi {
return choice;
}
protected Card getCloneTarget(final SpellAbility sa) {
final Card host = sa.getHostCard();
Card tgtCard = host;
if (sa.hasParam("CloneTarget")) {
final List<Card> cloneTargets = AbilityUtils.getDefinedCards(host, sa.getParam("CloneTarget"), sa);
if (!cloneTargets.isEmpty()) {
tgtCard = cloneTargets.get(0);
}
} else if (sa.hasParam("Choices") && sa.usesTargeting()) {
tgtCard = sa.getTargets().getFirstTargetedCard();
}
return tgtCard;
}
/*
* (non-Javadoc)
* @see forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player, forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
*/
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
// don't use instant speed clone abilities outside computers
// Combat_Begin step
if (!ph.is(PhaseType.COMBAT_BEGIN)
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
return false;
}
// don't use instant speed clone abilities outside humans
// Combat_Declare_Attackers_InstantAbility step
if (!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.isPlayerTurn(ai) || ph.getCombat().getAttackers().isEmpty()) {
return false;
}
// don't activate during main2 unless this effect is permanent
return !ph.is(PhaseType.MAIN2) || sa.hasParam("Permanent");
}
}

View File

@@ -3,6 +3,7 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
@@ -29,7 +30,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
sa.resetTargets();
CardCollection list =
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on
// purpose
list = CardLists.filter(list, new Predicate<Card>() {

View File

@@ -84,7 +84,9 @@ public class ControlGainAi extends SpellAbilityAi {
if (sa.hasParam("AllValid")) {
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), opponents);
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
return !tgtCards.isEmpty();
if (tgtCards.isEmpty()) {
return false;
}
}
return true;
} else {
@@ -245,7 +247,7 @@ public class ControlGainAi extends SpellAbilityAi {
break;
}
}
}
};
if (t != null) {
sa.getTargets().add(t);
@@ -294,12 +296,15 @@ public class ControlGainAi extends SpellAbilityAi {
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
}
return !lose.contains("EOT")
|| !game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
if (lose.contains("EOT")
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
} else {
return this.canPlayAI(ai, sa);
}
return true;
} // pumpDrawbackAI()
@Override

View File

@@ -22,8 +22,9 @@ import java.util.List;
public class CopyPermanentAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// Card source = sa.getHostCard();
// TODO - I'm sure someone can do this AI better
Card source = sa.getHostCard();
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
String aiLogic = sa.getParamOrDefault("AILogic", "");
@@ -37,11 +38,6 @@ public class CopyPermanentAi extends SpellAbilityAi {
return ph.is(PhaseType.END_OF_TURN);
} else if ("AtOppEOT".equals(aiLogic)) {
return ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn() != aiPlayer;
} else if ("DuplicatePerms".equals(aiLogic)) {
final List<Card> valid = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
if (valid.size() < 2) {
return false;
}
}
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
@@ -50,7 +46,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
if (sa.hasParam("Defined")) {
// If there needs to be an imprinted card, don't activate the ability if nothing was imprinted yet (e.g. Mimic Vat)
if (sa.getParam("Defined").equals("Imprinted.ExiledWithSource") && source.getImprintedCards().isEmpty()) {
if (sa.getParam("Defined").equals("Imprinted.ExiledWithSource") && sa.getHostCard().getImprintedCards().isEmpty()) {
return false;
}
}
@@ -64,32 +60,9 @@ public class CopyPermanentAi extends SpellAbilityAi {
if (sa.usesTargeting() && sa.hasParam("TargetingPlayer")) {
sa.resetTargets();
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
sa.setTargetingPlayer(targetingPlayer);
return targetingPlayer.getController().chooseTargetsFor(sa);
} else if (sa.getTargetRestrictions() != null && sa.getTargetRestrictions().canTgtPlayer()) {
if (!sa.isCurse()) {
if (sa.canTarget(aiPlayer)) {
sa.getTargets().add(aiPlayer);
return true;
} else {
for (Player p : aiPlayer.getTeamMates(true)) {
if (sa.canTarget(p)) {
sa.getTargets().add(p);
return true;
}
}
return false;
}
} else {
for (Player p : aiPlayer.getOpponents()) {
if (sa.canTarget(p)) {
sa.getTargets().add(p);
return true;
}
}
return false;
}
} else {
return this.doTriggerAINoCost(aiPlayer, sa, false);
}
@@ -101,7 +74,6 @@ public class CopyPermanentAi extends SpellAbilityAi {
final Player activator = sa.getActivatingPlayer();
final Game game = host.getGame();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final boolean canCopyLegendary = sa.hasParam("NonLegendary");
// ////
@@ -142,7 +114,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (!c.getType().isLegendary() || canCopyLegendary) || !c.getController().equals(aiPlayer);
return !c.getType().isLegendary() || !c.getController().equals(aiPlayer);
}
});
Card choice;
@@ -208,8 +180,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
private CardCollection getBetterOptions(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional) {
final Card host = sa.getHostCard();
final Player ctrl = host.getController();
final boolean canCopyLegendary = sa.hasParam("NonLegendary");
final String filter = canCopyLegendary ? "Permanent" : "Permanent.YouDontCtrl,Permanent.nonLegendary";
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
// TODO add filter to not select Legendary from Other Player when ai already have a Legendary with that name
return CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
}

View File

@@ -90,7 +90,9 @@ public class CountersMoveAi extends SpellAbilityAi {
}
// for Simic Fluxmage and other
return ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN);
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
return false;
}
} else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
// 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
if ("Self".equals(sa.getParam("Source"))) {
return host == null || host.getNetToughness() - 1 > 0;
if (host != null && host.getNetToughness() - 1 <= 0) {
return false;
}
}
}
return true;
@@ -189,7 +193,9 @@ public class CountersMoveAi extends SpellAbilityAi {
// check for some specific AI preferences
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
@@ -201,7 +207,9 @@ public class CountersMoveAi extends SpellAbilityAi {
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if (sa.usesTargeting()) {
sa.resetTargets();
return moveTgtAI(ai, sa);
if (!moveTgtAI(ai, sa)) {
return false;
}
}
return true;
@@ -279,7 +287,10 @@ public class CountersMoveAi extends SpellAbilityAi {
// do not steal a P1P1 from Undying if it would die
// this way
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;
}

View File

@@ -48,7 +48,6 @@ public class CountersMultiplyAi extends SpellAbilityAi {
if (!c.canReceiveCounters(counterType)) {
return false;
}
} else {
for (Map.Entry<CounterType, Integer> e : c.getCounters().entrySet()) {
// has negative counter it would double
@@ -97,7 +96,10 @@ public class CountersMultiplyAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return !sa.usesTargeting() || setTargets(ai, sa) || mandatory;
if (sa.usesTargeting() && !setTargets(ai, sa) && !mandatory) {
return false;
}
return true;
}
private CounterType getCounterType(SpellAbility sa) {

View File

@@ -1,20 +1,15 @@
package forge.ai.ability;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardUtil;
import forge.game.card.CounterType;
import forge.game.player.Player;
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>() {
@Override
public boolean apply(final Card crd) {
if (!crd.hasCounters()) {
if (crd.hasCounters()) {
return false;
}
if (crd.isPlaneswalker()) {
return true;
}
// iterate only over existing counters
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
@@ -65,11 +56,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
if (!crd.hasCounters()) {
return false;
}
if (crd.isPlaneswalker()) {
if (crd.hasCounters()) {
return false;
}
@@ -85,7 +72,10 @@ public class CountersProliferateAi extends SpellAbilityAi {
}
return !cperms.isEmpty() || !hperms.isEmpty() || opponentPoison || allyExpOrEnergy;
if (cperms.isEmpty() && hperms.isEmpty() && !opponentPoison && !allyExpOrEnergy) {
return false;
}
return true;
}
@Override
@@ -102,68 +92,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if ("Always".equals(sa.getParam("AILogic"))) {
return true;
}
return checkApiLogic(ai, sa);
return canPlayAI(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;
}
}

View File

@@ -220,8 +220,6 @@ public class CountersPutAi extends SpellAbilityAi {
if ("Never".equals(logic)) {
return false;
} else if ("AristocratCounters".equals(logic)) {
return PumpAi.doAristocratWithCountersLogic(sa, ai);
} else if ("PayEnergy".equals(logic)) {
return true;
} else if ("PayEnergyConservatively".equals(logic)) {
@@ -313,20 +311,13 @@ public class CountersPutAi extends SpellAbilityAi {
return false;
}
if (sa.hasParam("Adapt") && source.getCounters(CounterType.P1P1) > 0) {
return false;
}
// TODO handle proper calculation of X values based on Cost
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
if (sa.hasParam("Adapt")) {
Game game = ai.getGame();
Combat combat = game.getCombat();
if (!source.canReceiveCounters(CounterType.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)) {
int nPump = 0;
if (type.equals("P1P1")) {
@@ -469,7 +460,7 @@ public class CountersPutAi extends SpellAbilityAi {
int left = amount;
for (Card c : list) {
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, i, i,
Lists.newArrayList())) {
Lists.<String>newArrayList())) {
sa.getTargets().add(c);
abTgt.addDividedAllocation(c, i);
left -= i;
@@ -506,7 +497,7 @@ public class CountersPutAi extends SpellAbilityAi {
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa)) {
for (Card c : list) {
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, amount, amount,
Lists.newArrayList())) {
Lists.<String>newArrayList())) {
choice = c;
break;
}
@@ -1058,39 +1049,4 @@ public class CountersPutAi extends SpellAbilityAi {
return false;
}
private boolean doCombatAdaptLogic(Card source, int amount, Combat combat) {
if (combat.isAttacking(source)) {
if (!combat.isBlocked(source)) {
return true;
} else {
for (Card blockedBy : combat.getBlockers(source)) {
if (blockedBy.getNetToughness() > source.getNetPower()
&& blockedBy.getNetToughness() <= source.getNetPower() + amount) {
return true;
}
}
int totBlkPower = Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
if (source.getNetToughness() <= totBlkPower
&& source.getNetToughness() + amount > totBlkPower) {
return true;
}
}
} else if (combat.isBlocking(source)) {
for (Card blocked : combat.getAttackersBlockedBy(source)) {
if (blocked.getNetToughness() > source.getNetPower()
&& blocked.getNetToughness() <= source.getNetPower() + amount) {
return true;
}
}
int totAtkPower = Aggregates.sum(combat.getAttackersBlockedBy(source), CardPredicates.Accessors.fnGetNetPower);
if (source.getNetToughness() <= totAtkPower
&& source.getNetToughness() + amount > totAtkPower) {
return true;
}
}
return false;
}
}

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
@@ -38,13 +39,8 @@ public class CountersPutAllAi extends SpellAbilityAi {
final boolean curse = sa.isCurse();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if ("OwnCreatsAndOtherPWs".equals(sa.getParam("AILogic"))) {
hList = CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), "Creature.YouCtrl,Planeswalker.YouCtrl+Other", source.getController(), source);
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);
}
hList = CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
if (abCost != null) {
// AI currently disabled for these costs
@@ -72,7 +68,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
}
if (tgt != null) {
Player pl = curse ? ai.getWeakestOpponent() : ai;
Player pl = curse ? ComputerUtil.getOpponentFor(ai) : ai;
sa.getTargets().add(pl);
hList = CardLists.filterControlledBy(hList, pl);
@@ -153,7 +149,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return player.getCreaturesInPlay().size() >= player.getWeakestOpponent().getCreaturesInPlay().size();
return player.getCreaturesInPlay().size() >= ComputerUtil.getOpponentFor(player).getCreaturesInPlay().size();
}
@Override

View File

@@ -6,7 +6,6 @@ import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
@@ -101,7 +100,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
}
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, list, false);
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
@@ -201,20 +200,14 @@ public class CountersRemoveAi extends SpellAbilityAi {
}
// do as P1P1 part
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasLessCounter(CounterType.P1P1, amount));
CardCollection aiUndyingList = CardLists.getKeyword(aiP1P1List, Keyword.UNDYING);
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
if (!aiUndyingList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiUndyingList));
return true;
aiP1P1List = aiUndyingList;
}
// remove P1P1 counters from opposing creatures
CardCollection oppP1P1List = CardLists.filter(list,
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
CardPredicates.hasCounter(CounterType.P1P1));
if (!oppP1P1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(oppP1P1List));
if (!aiP1P1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
return true;
}
@@ -361,30 +354,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
*/
@Override
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
GameEntity target = (GameEntity) params.get("Target");
CounterType type = (CounterType) params.get("CounterType");
if (target instanceof Card) {
Card targetCard = (Card) target;
if (targetCard.getController().isOpponentOf(player)) {
return !ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
} else {
if (targetCard.hasKeyword(Keyword.UNDYING) && type == 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;
}
}
// TODO Auto-generated method stub
return super.chooseNumber(player, sa, min, max, params);
}
@@ -400,49 +370,30 @@ public class CountersRemoveAi extends SpellAbilityAi {
return super.chooseCounterType(options, sa, params);
}
Player ai = sa.getActivatingPlayer();
GameEntity target = (GameEntity) params.get("Target");
Card target = (Card) params.get("Target");
if (target instanceof Card) {
Card targetCard = (Card) target;
if (targetCard.getController().isOpponentOf(ai)) {
// if its a Planeswalker try to remove Loyality first
if (targetCard.isPlaneswalker()) {
return CounterType.LOYALTY;
}
for (CounterType type : options) {
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;
}
if (target.getController().isOpponentOf(ai)) {
// if its a Planeswalker try to remove Loyality first
if (target.isPlaneswalker()) {
return CounterType.LOYALTY;
}
for (CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, target)) {
return type;
}
}
} else if (target instanceof Player) {
Player targetPlayer = (Player) target;
if (targetPlayer.isOpponentOf(ai)) {
for (CounterType type : options) {
if (!type.equals(CounterType.POISON)) {
return type;
}
}
} else {
for (CounterType type : options) {
if (type.equals(CounterType.POISON)) {
return type;
}
} else {
if (options.contains(CounterType.M1M1) && target.hasKeyword(Keyword.PERSIST)) {
return CounterType.M1M1;
} else if (options.contains(CounterType.P1P1) && target.hasKeyword(Keyword.UNDYING)) {
return CounterType.M1M1;
}
for (CounterType type : options) {
if (ComputerUtil.isNegativeCounter(type, target)) {
return type;
}
}
}
return super.chooseCounterType(options, sa, params);
}
}

View File

@@ -1,6 +1,7 @@
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
@@ -19,7 +20,7 @@ import forge.util.MyRandom;
public abstract class DamageAiBase extends SpellAbilityAi {
protected boolean avoidTargetP(final Player comp, final SpellAbility sa) {
Player enemy = comp.getWeakestOpponent();
Player enemy = ComputerUtil.getOpponentFor(comp);
// Logic for cards that damage owner, like Fireslinger
// Do not target a player if they aren't below 75% of our health.
// Unless Lifelink will cancel the damage to us
@@ -37,7 +38,9 @@ public abstract class DamageAiBase extends SpellAbilityAi {
}
if ("SelfDamage".equals(sa.getParam("AILogic"))) {
if (comp.getLife() * 0.75 < enemy.getLife()) {
return !lifelink;
if (!lifelink) {
return true;
}
}
}
return false;
@@ -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) {
int restDamage = d;
final Game game = comp.getGame();
Player enemy = comp.getWeakestOpponent();
Player enemy = ComputerUtil.getOpponentFor(comp);
boolean dmgByCardsInHand = false;
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&

View File

@@ -212,7 +212,7 @@ public class DamageAllAi extends SpellAbilityAi {
}
// Evaluate creatures getting killed
Player enemy = ai.getWeakestOpponent();
Player enemy = ComputerUtil.getOpponentFor(ai);
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -294,7 +294,7 @@ public class DamageAllAi extends SpellAbilityAi {
}
// Evaluate creatures getting killed
Player enemy = ai.getWeakestOpponent();
Player enemy = ComputerUtil.getOpponentFor(ai);
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();

View File

@@ -11,7 +11,6 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana;
import forge.game.cost.CostRemoveCounter;
import forge.game.keyword.Keyword;
@@ -76,11 +75,14 @@ public class DamageDealAi extends DamageAiBase {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
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
}
}
return this.damageTargetAI(ai, sa, dmg, true);
if (!this.damageTargetAI(ai, sa, dmg, true)) {
return false;
}
return true;
}
@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.
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
} 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
@@ -156,7 +158,7 @@ public class DamageDealAi extends DamageAiBase {
}
}
}
if (!ai.getCreaturesAttackedThisTurn().isEmpty()) {
if (ai.getAttackedWithCreatureThisTurn()) {
dmg = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
}
} else if ("WildHunt".equals(logic)) {
@@ -269,11 +271,11 @@ public class DamageDealAi extends DamageAiBase {
sourceName.equals("Crater's Claws")){
// If I can kill my target by paying less mana, do it
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) {
int actualPay = dmg;
int actualPay = 0;
final boolean noPrevention = sa.hasParam("NoPrevention");
for (final Card c : sa.getTargets().getTargetCards()) {
final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if (adjDamage < actualPay) {
if ((adjDamage > actualPay) && (adjDamage <= dmg)) {
actualPay = adjDamage;
}
}
@@ -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;
}
@@ -449,14 +434,14 @@ public class DamageDealAi extends DamageAiBase {
for (SpellAbility sa : pw.getSpellAbilities()) {
if (sa.hasParam("Ultimate") && sa.getPayCosts() != null) {
Integer loyaltyCost = 0;
int loyaltyCost = 0;
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
if (remLoyalty != null) {
// if remLoyalty is null, generally there's an AddCounter<0/LOYALTY> cost, like for Gideon Jura.
loyaltyCost = remLoyalty.convertAmount();
}
if (loyaltyCost != null && loyaltyCost != 0 && loyaltyCost - curLoyalty <= 1) {
if (loyaltyCost != 0 && loyaltyCost - curLoyalty <= 1) {
// Will ultimate soon
pwScore += 10000;
}
@@ -487,7 +472,9 @@ public class DamageDealAi extends DamageAiBase {
for (final Object o : objects) {
if (o instanceof Card) {
final Card c = (Card) o;
hPlay.remove(c);
if (hPlay.contains(c)) {
hPlay.remove(c);
}
}
}
hPlay = CardLists.getTargetableCards(hPlay, sa);
@@ -547,7 +534,7 @@ public class DamageDealAi extends DamageAiBase {
final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer");
final String logic = sa.getParamOrDefault("AILogic", "");
Player enemy = ai.getWeakestOpponent();
Player enemy = ComputerUtil.getOpponentFor(ai);
if ("PowerDmg".equals(logic)) {
// check if it is better to target the player instead, the original target is already set in PumpAi.pumpTgtAI()
@@ -648,9 +635,10 @@ public class DamageDealAi extends DamageAiBase {
if (c != null && !this.shouldTgtP(ai, sa, dmg, noPrevention, true)) {
tcs.add(c);
if (divided) {
int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
assignedDamage = Math.min(dmg, assignedDamage);
tgt.addDividedAllocation(c, assignedDamage);
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if (assignedDamage <= dmg) {
tgt.addDividedAllocation(c, assignedDamage);
}
dmg = dmg - assignedDamage;
if (dmg <= 0) {
break;
@@ -717,7 +705,8 @@ public class DamageDealAi extends DamageAiBase {
}
if (phase.is(PhaseType.MAIN2) && sa.isAbility()) {
if (sa.isPwAbility() || source.hasSVar("EndOfTurnLeavePlay"))
if (sa.getRestrictions().isPwAbility()
|| source.hasSVar("EndOfTurnLeavePlay"))
freePing = true;
}
}
@@ -871,7 +860,7 @@ public class DamageDealAi extends DamageAiBase {
// this is for Triggered targets that are mandatory
final boolean noPrevention = sa.hasParam("NoPrevention");
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)) {
if (tgt.canTgtPlaneswalker()) {
@@ -947,7 +936,9 @@ public class DamageDealAi extends DamageAiBase {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// If it's not mandatory check a few things
return mandatory || this.damageChooseNontargeted(ai, sa, dmg);
if (!mandatory && !this.damageChooseNontargeted(ai, sa, dmg)) {
return false;
}
} else {
if (!this.damageChoosingTargets(ai, sa, tgt, dmg, mandatory, true) && !mandatory) {
return false;
@@ -1089,7 +1080,7 @@ public class DamageDealAi extends DamageAiBase {
continue; // not a toughness debuff
}
}
if (StringUtils.isNumeric(dmgDef)) { // currently doesn't work for X and other dependent costs
if (StringUtils.isNumeric(dmgDef) && ab.canPlay()) { // currently doesn't work for X and other dependent costs
if (sa.usesTargeting() && ab.usesTargeting()) {
// Ensure that the chained spell can target at least the same things (or more) as the current one
TargetRestrictions tgtSa = sa.getTargetRestrictions();

View File

@@ -100,7 +100,7 @@ public class DamagePreventAi extends SpellAbilityAi {
tcs.add(ai);
chance = true;
}
final List<Card> threatenedTargets = new ArrayList<>();
final List<Card> threatenedTargets = new ArrayList<Card>();
// filter AIs battlefield by what I can target
List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard, sa);
targetables = CardLists.getTargetableCards(targetables, sa);

View File

@@ -4,6 +4,7 @@ import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
@@ -124,7 +125,7 @@ public class DebuffAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
CardCollection list = getCurseCreatures(ai, sa, kws == null ? Lists.newArrayList() : kws);
CardCollection list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
// several uses here:
@@ -176,7 +177,7 @@ public class DebuffAi extends SpellAbilityAi {
* @return a CardCollection.
*/
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
final Player opp = ai.getWeakestOpponent();
final Player opp = ComputerUtil.getOpponentFor(ai);
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
if (!list.isEmpty()) {
list = CardLists.filter(list, new Predicate<Card>() {
@@ -216,7 +217,7 @@ public class DebuffAi extends SpellAbilityAi {
list.remove(c);
}
final CardCollection pref = CardLists.filterControlledBy(list, ai.getWeakestOpponent());
final CardCollection pref = CardLists.filterControlledBy(list, ComputerUtil.getOpponentFor(ai));
final CardCollection forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getHostCard();
@@ -266,7 +267,7 @@ public class DebuffAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<>();
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<String>();
if (sa.getTargetRestrictions() == null) {
if (mandatory) {

View File

@@ -27,7 +27,7 @@ public class DelayedTriggerAi extends SpellAbilityAi {
trigsa.setActivatingPlayer(ai);
if (trigsa instanceof AbilitySub) {
return SpellApiToAi.Converter.get(trigsa.getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
return SpellApiToAi.Converter.get(((AbilitySub) trigsa).getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
} else {
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
}

View File

@@ -48,7 +48,7 @@ public class DestroyAi extends SpellAbilityAi {
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"))) {
@@ -63,7 +63,7 @@ public class DestroyAi extends SpellAbilityAi {
}
} else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) {
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;
}
}
@@ -132,7 +132,7 @@ public class DestroyAi extends SpellAbilityAi {
}
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, list, true);
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, true);
list = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
if (CardLists.getNotType(list, "Creature").isEmpty()) {
@@ -288,7 +288,7 @@ public class DestroyAi extends SpellAbilityAi {
} else if (sa.hasParam("Defined")) {
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
if ("WillSkipTurn".equals(logic) && (sa.getHostCard().getController().equals(ai)
|| ai.getCreaturesInPlay().size() < ai.getWeakestOpponent().getCreaturesInPlay().size()
|| ai.getCreaturesInPlay().size() < ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size()
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|| ai.getLife() <= 5)) {
// Basic ai logic for Lethal Vapors
@@ -296,8 +296,8 @@ public class DestroyAi extends SpellAbilityAi {
}
if (list.isEmpty()
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|| CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE).isEmpty()) {
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|| CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE).isEmpty()) {
return false;
}
}
@@ -342,7 +342,7 @@ public class DestroyAi extends SpellAbilityAi {
}
// Filter AI-specific targets if provided
preferred = ComputerUtil.filterAITgts(sa, ai, preferred, true);
preferred = ComputerUtil.filterAITgts(sa, ai, (CardCollection)preferred, true);
for (final Card c : preferred) {
list.remove(c);
@@ -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 {
return mandatory;
if (!mandatory) {
return false;
}
}
return true;
}
public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLand, String logic) {

View File

@@ -66,7 +66,7 @@ public class DestroyAllAi extends SpellAbilityAi {
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
final String logic = sa.getParamOrDefault("AILogic", "");
Player opponent = ai.getWeakestOpponent(); // TODO: how should this AI logic work for multiplayer and getOpponents()?
Player opponent = ComputerUtil.getOpponentFor(ai); // TODO: how should this AI logic work for multiplayer and getOpponents()?
final int CREATURE_EVAL_THRESHOLD = 200;
@@ -147,7 +147,7 @@ public class DestroyAllAi extends SpellAbilityAi {
block.assignBlockersForCombat(combat);
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
return true;
return true;
}
return false;
} // only lands involved
@@ -171,6 +171,7 @@ public class DestroyAllAi extends SpellAbilityAi {
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
return false;
}
return true;
}
}

View File

@@ -1,13 +1,9 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.*;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
@@ -24,7 +20,7 @@ public class DigAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
Player opp = ai.getWeakestOpponent();
Player opp = ComputerUtil.getOpponentFor(ai);
final Card host = sa.getHostCard();
Player libraryOwner = ai;
@@ -46,9 +42,7 @@ public class DigAi extends SpellAbilityAi {
if ("Never".equals(sa.getParam("AILogic"))) {
return false;
} else if ("AtOppEndOfTurn".equals(sa.getParam("AILogic"))) {
if (!(game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN))) {
return false;
}
return game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN);
}
// don't deck yourself
@@ -66,8 +60,7 @@ public class DigAi extends SpellAbilityAi {
}
final String num = sa.getParam("DigNum");
final boolean payXLogic = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayX");
if (num != null && (num.equals("X") && host.getSVar(num).equals("Count$xPaid")) || payXLogic) {
if (num != null && num.equals("X") && host.getSVar(num).equals("Count$xPaid")) {
// By default, set PayX here to maximum value.
if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) {
int manaToSave = 0;
@@ -106,7 +99,7 @@ public class DigAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getWeakestOpponent();
final Player opp = ComputerUtil.getOpponentFor(ai);
if (sa.usesTargeting()) {
sa.resetTargets();
if (mandatory && sa.canTarget(opp)) {
@@ -131,25 +124,6 @@ public class DigAi extends SpellAbilityAi {
@Override
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)) {
return ComputerUtilCard.getWorstPermanentAI(valid, false, true, false, false);
} else {

View File

@@ -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;
}
}

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
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);
Player libraryOwner = ai;
Player opp = ai.getWeakestOpponent();
Player opp = ComputerUtil.getOpponentFor(ai);
if ("DontMillSelf".equals(logic)) {
// A card that digs for specific things and puts everything revealed before it into graveyard

View File

@@ -56,7 +56,7 @@ public class DiscardAi extends SpellAbilityAi {
return SpecialCardAi.VolrathsShapeshifter.consider(ai, sa);
}
final boolean humanHasHand = ai.getWeakestOpponent().getCardsIn(ZoneType.Hand).size() > 0;
final boolean humanHasHand = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).size() > 0;
if (tgt != null) {
if (!discardTargetAI(ai, sa)) {
@@ -87,7 +87,7 @@ public class DiscardAi extends SpellAbilityAi {
if (sa.hasParam("NumCards")) {
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
// 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());
if (cardsToDiscard < 1) {
return false;
@@ -150,17 +150,14 @@ public class DiscardAi extends SpellAbilityAi {
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
for (Player opp : ai.getOpponents()) {
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
continue;
} else if (!opp.canDiscardBy(sa)) { // e.g. Tamiyo, Collector of Tales
continue;
}
if (tgt != null) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
return true;
}
Player opp = ComputerUtil.getOpponentFor(ai);
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
if (tgt != null) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
return true;
}
}
return false;
@@ -172,7 +169,7 @@ public class DiscardAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
Player opp = ai.getWeakestOpponent();
Player opp = ComputerUtil.getOpponentFor(ai);
if (!discardTargetAI(ai, sa)) {
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);
@@ -193,7 +190,7 @@ public class DiscardAi extends SpellAbilityAi {
}
if ("X".equals(sa.getParam("RevealNumber")) && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
// 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());
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
}

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -18,7 +19,7 @@ public class DrainManaAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Player opp = ai.getWeakestOpponent();
final Player opp = ComputerUtil.getOpponentFor(ai);
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
if (tgt == null) {
@@ -40,7 +41,7 @@ public class DrainManaAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getWeakestOpponent();
final Player opp = ComputerUtil.getOpponentFor(ai);
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
@@ -51,9 +52,12 @@ public class DrainManaAi extends SpellAbilityAi {
} else {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
return defined.contains(opp);
if (!defined.contains(opp)) {
return false;
}
}
return true;
} else {
sa.resetTargets();
sa.getTargets().add(opp);
@@ -78,7 +82,7 @@ public class DrainManaAi extends SpellAbilityAi {
}
} else {
sa.resetTargets();
sa.getTargets().add(ai.getWeakestOpponent());
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
}
return randomReturn;

View File

@@ -69,7 +69,7 @@ public class DrawAi extends SpellAbilityAi {
}
if (!canLoot(ai, sa)) {
return false;
return false;
}
return true;
}
@@ -107,7 +107,7 @@ public class DrawAi extends SpellAbilityAi {
}
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
return false;
}
@@ -211,11 +211,9 @@ public class DrawAi extends SpellAbilityAi {
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Card source = sa.getHostCard();
final boolean drawback = sa.getParent() != null;
final Game game = ai.getGame();
final String logic = sa.getParamOrDefault("AILogic", "");
final boolean considerPrimary = logic.equals("ConsiderPrimary");
final boolean drawback = (sa.getParent() != null) && !considerPrimary;
boolean assumeSafeX = false; // if true, the AI will assume that the X value has been set to a value that is safe to draw
int computerHandSize = ai.getCardsIn(ZoneType.Hand).size();
final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size();
@@ -243,12 +241,7 @@ public class DrawAi extends SpellAbilityAi {
numCards = Integer.parseInt(source.getSVar("PayX"));
} else {
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));
assumeSafeX = true;
}
xPaid = true;
}
@@ -409,7 +402,7 @@ public class DrawAi extends SpellAbilityAi {
if (computerHandSize + numCards > computerMaxHandSize && game.getPhaseHandler().isPlayerTurn(ai)) {
if (xPaid) {
numCards = computerMaxHandSize - computerHandSize;
if (sa.getHostCard().isInZone(ZoneType.Hand)) {
if (sa.getHostCard().getZone().is(ZoneType.Hand)) {
numCards++; // the card will be spent
}
source.setSVar("PayX", Integer.toString(numCards));
@@ -499,8 +492,7 @@ public class DrawAi extends SpellAbilityAi {
if ((computerHandSize + numCards > computerMaxHandSize)
&& game.getPhaseHandler().isPlayerTurn(ai)
&& !sa.isTrigger()
&& !assumeSafeX) {
&& !sa.isTrigger()) {
// Don't draw too many cards and then risk discarding cards at
// EOT
if (!drawback) {

View File

@@ -9,6 +9,7 @@ import com.google.common.collect.Iterables;
import forge.ai.*;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.combat.CombatUtil;
@@ -266,7 +267,7 @@ public class EffectAi extends SpellAbilityAi {
final SpellAbility saTop = game.getStack().peekAbility();
final Card host = saTop.getHostCard();
if (saTop.getActivatingPlayer() != ai // from opponent
&& host.canDamagePrevented(false) // no prevent damage
&& !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) // no prevent damage
&& host != null && (host.isInstant() || host.isSorcery())
&& !host.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")) { // valid target
final ApiType type = saTop.getApi();
@@ -282,7 +283,11 @@ public class EffectAi extends SpellAbilityAi {
return false;
}
final SpellAbility topStack = game.getStack().peekAbility();
return topStack.getActivatingPlayer().isOpponentOf(ai) && topStack.getApi() == ApiType.GainLife;
if (topStack.getActivatingPlayer().isOpponentOf(ai) && topStack.getApi() == ApiType.GainLife) {
return true;
} else {
return false;
}
} else if (logic.equals("Fight")) {
return FightAi.canFightAi(ai, sa, 0, 0);
} else if (logic.equals("Burn")) {
@@ -296,9 +301,11 @@ public class EffectAi extends SpellAbilityAi {
return false;
}
if (logic.contains(":")) {
String[] k = logic.split(":");
String k[] = logic.split(":");
Integer i = Integer.valueOf(k[1]);
return ai.getCreaturesInPlay().size() >= i;
if (ai.getCreaturesInPlay().size() < i) {
return false;
}
}
return true;
} else if (logic.equals("CastFromGraveThisTurn")) {

View File

@@ -108,10 +108,6 @@ public class FightAi extends SpellAbilityAi {
@Override
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);
}
@@ -196,36 +192,13 @@ public class FightAi extends SpellAbilityAi {
toughness += bonus;
}
if ("PowerDmg".equals(sa.getParam("AILogic"))) {
if ("2".equals(sa.getParam("TargetMax"))) {
// Band Together, uses up to two targets to deal damage to a single target
// TODO: Generalize this so that other TargetMax values can be properly accounted for
CardCollection aiCreaturesByPower = new CardCollection(aiCreatures);
CardLists.sortByPowerDesc(aiCreaturesByPower);
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;
if (FightAi.canKill(aiCreature, humanCreature, power)) {
sa.getTargets().add(aiCreature);
if (!isChandrasIgnition) {
tgtFight.resetTargets();
tgtFight.getTargets().add(humanCreature);
}
return true;
}
} else {
if (FightAi.shouldFight(aiCreature, humanCreature, power, toughness)) {
@@ -273,10 +246,10 @@ public class FightAi extends SpellAbilityAi {
if (!canKill(opponent, fighter, -pumpDefense)) { // can survive
return true;
} else {
if (MyRandom.getRandom().nextInt(20)<(opponent.getCMC() - fighter.getCMC())) { // trade
return true;
}
}
if (MyRandom.getRandom().nextInt(20)<(opponent.getCMC() - fighter.getCMC())) { // trade
return true;
}
}
}
return false;
}

View File

@@ -20,9 +20,9 @@ public class FlipACoinAi extends SpellAbilityAi {
if (AILogic.equals("Never")) {
return false;
} 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;
}
}
} else if (AILogic.equals("KillOrcs")) {
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
return false;

View File

@@ -107,7 +107,7 @@ public class FogAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
final Game game = aiPlayer.getGame();
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);
} else {
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -8,7 +9,7 @@ import forge.game.spellability.TargetRestrictions;
public class GameLossAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Player opp = ai.getWeakestOpponent();
final Player opp = ComputerUtil.getOpponentFor(ai);
if (opp.cantLose()) {
return false;
}
@@ -33,16 +34,15 @@ public class GameLossAi extends SpellAbilityAi {
// Phage the Untouchable
// (Final Fortune would need to attach it's delayed trigger to a
// specific turn, which can't be done yet)
Player opp = ai.getWeakestOpponent();
if (!mandatory && opp.cantLose()) {
if (!mandatory && ComputerUtil.getOpponentFor(ai).cantLose()) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
sa.getTargets().add(opp);
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
}
return true;

View File

@@ -11,7 +11,9 @@ public class GameWinAi extends SpellAbilityAi {
*/
@Override
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)
@@ -19,6 +21,7 @@ public class GameWinAi extends SpellAbilityAi {
// In general, don't return true.
// But this card wins the game, I can make an exception for that
return true;
}
@Override

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -19,7 +20,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final int myLife = aiPlayer.getLife();
Player opponent = aiPlayer.getWeakestOpponent();
Player opponent = ComputerUtil.getOpponentFor(aiPlayer);
final int hLife = opponent.getLife();
if (!aiPlayer.canGainLife()) {
@@ -75,7 +76,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getWeakestOpponent();
Player opp = ComputerUtil.getOpponentFor(ai);
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {

View File

@@ -149,7 +149,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getWeakestOpponent();
Player opp = ComputerUtil.getOpponentFor(ai);
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {

View File

@@ -106,9 +106,13 @@ public class LifeGainAi extends SpellAbilityAi {
return false;
}
return lifeCritical || activateForCost
|| (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|| sa.hasParam("PlayerTurn") || SpellAbilityAi.isSorcerySpeed(sa);
if (!lifeCritical && !activateForCost
&& (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) {
return false;
}
return true;
}
/*
@@ -300,7 +304,9 @@ public class LifeGainAi extends SpellAbilityAi {
hasTgt = true;
}
}
return hasTgt;
if (!hasTgt) {
return false;
}
}
return true;
}

View File

@@ -51,11 +51,6 @@ public class LifeLoseAi extends SpellAbilityAi {
if (tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
return false;
}
if (sa.usesTargeting()) {
return doTgt(ai, sa, false);
}
return true;
}
@@ -182,11 +177,15 @@ public class LifeLoseAi extends SpellAbilityAi {
}
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);
// For cards like Foul Imp, ETB you lose life
return mandatory || !tgtPlayers.contains(ai) || amount <= 0 || amount + 3 <= ai.getLife();
if (!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) {

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
@@ -19,7 +20,7 @@ public class LifeSetAi extends SpellAbilityAi {
// Ability_Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final int myLife = ai.getLife();
final Player opponent = ai.getWeakestOpponent();
final Player opponent = ComputerUtil.getOpponentFor(ai);
final int hlife = opponent.getLife();
final String amountStr = sa.getParam("LifeAmount");
@@ -106,7 +107,7 @@ public class LifeSetAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final int myLife = ai.getLife();
final Player opponent = ai.getWeakestOpponent();
final Player opponent = ComputerUtil.getOpponentFor(ai);
final int hlife = opponent.getLife();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);

View File

@@ -81,8 +81,11 @@ public class ManaEffectAi extends SpellAbilityAi {
return true; // handled elsewhere, does not meet the standard requirements
}
return sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
&& sa.getSubAbility() == null && ComputerUtil.playImmediately(ai, sa);
if (!(sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
&& sa.getSubAbility() == null && ComputerUtil.playImmediately(ai, sa))) {
return false;
}
return true;
// return super.checkApiLogic(ai, sa);
}

View File

@@ -1,17 +1,13 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardUtil;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
@@ -19,7 +15,6 @@ import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementLayer;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
@@ -89,79 +84,61 @@ public class ManifestAi extends SpellAbilityAi {
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;
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
final Game game = ai.getGame();
final Card host = sa.getHostCard();
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (sa.hasParam("Choices") || sa.hasParam("ChoiceZone")) {
ZoneType choiceZone = ZoneType.Hand;
if (sa.hasParam("ChoiceZone")) {
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
}
CardCollection choices = new CardCollection(game.getCardsIn(choiceZone));
if (sa.hasParam("Choices")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), ai, host);
}
if (choices.isEmpty()) {
return false;
}
} else {
// Library is empty, no Manifest
final CardCollectionView library = ai.getCardsIn(ZoneType.Library);
if (library.isEmpty())
// Library is empty, no Manifest
final CardCollectionView library = ai.getCardsIn(ZoneType.Library);
if (library.isEmpty())
return false;
// try not to mill himself with Manifest
if (library.size() < 5 && !ai.isCardInPlay("Laboratory Maniac")) {
return false;
}
// check to ensure that there are no replacement effects that prevent creatures ETBing from library
// (e.g. Grafdigger's Cage)
Card topCopy = CardUtil.getLKICopy(library.getFirst());
topCopy.setState(CardStateName.FaceDown, false);
topCopy.setManifested(true);
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;
// try not to mill himself with Manifest
if (library.size() < 5 && !ai.isCardInPlay("Laboratory Maniac")) {
// do not manifest a card with X in its cost
if (topCard.getManaCost().countX() > 0)
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;
}
}
// Probably should be a little more discerning on playing during OPPs turn
if (SpellAbilityAi.playReusable(ai, sa)) {
return true;
@@ -176,23 +153,4 @@ public class ManifestAi extends SpellAbilityAi {
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);
}
}

View File

@@ -31,13 +31,19 @@ public class MillAi extends SpellAbilityAi {
PhaseHandler ph = ai.getGame().getPhaseHandler();
if (aiLogic.equals("Main1")) {
return !ph.getPhase().isBefore(PhaseType.MAIN2) || sa.hasParam("ActivationPhases")
|| ComputerUtil.castSpellInMain1(ai, sa);
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
} 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")) {
// 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;
}
@@ -56,9 +62,11 @@ public class MillAi extends SpellAbilityAi {
}
}
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
// because they are also potentially useful for combat
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai);
if (!(ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai))) {
// creatures with a tap cost to mill (e.g. Doorkeeper) should be activated at the opponent's end step
// because they are also potentially useful for combat
return false;
}
}
return true;
}
@@ -92,7 +100,9 @@ public class MillAi extends SpellAbilityAi {
// Set PayX here to maximum value.
final int cardsToDiscard = getNumToDiscard(ai, sa);
source.setSVar("PayX", Integer.toString(cardsToDiscard));
return cardsToDiscard > 0;
if (cardsToDiscard <= 0) {
return false;
}
}
return true;
}

View File

@@ -95,7 +95,7 @@ public class MustBlockAi extends SpellAbilityAi {
boolean chance = false;
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()) {
return false;
}

View File

@@ -24,10 +24,10 @@ public class PeekAndRevealAi extends SpellAbilityAi {
return false;
}
if ("Main2".equals(sa.getParam("AILogic"))) {
if (aiPlayer.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
}
if (aiPlayer.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
}
// So far this only appears on Triggers, but will expand
// once things get converted from Dig + NoMove
return true;

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.game.card.*;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicates;
@@ -13,6 +12,10 @@ import forge.card.CardType.Supertype;
import forge.card.mana.ManaCost;
import forge.game.Game;
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.keyword.KeywordInterface;
import forge.game.mana.ManaCostBeingPaid;
@@ -38,7 +41,10 @@ public class PermanentAi extends SpellAbilityAi {
}
// 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")) {
// 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)
: ai.getCardsIn(ZoneType.Battlefield);
CardCollection ctrld = CardLists.filter(valid, CardPredicates.nameEquals(card.getName()));
CardCollection ctrld = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(card.getName()));
int numControlled = 0;
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
// 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).
@@ -256,7 +260,9 @@ public class PermanentAi extends SpellAbilityAi {
}
}
return !dontCast;
if (dontCast) {
return false;
}
}
return true;

View File

@@ -40,7 +40,9 @@ public class PermanentCreatureAi extends PermanentAi {
ComputerUtilCard.applyStaticContPT(game, copy, null);
return copy.getNetToughness() > 0;
if (copy.getNetToughness() <= 0) {
return false;
}
}
return true;
}
@@ -223,9 +225,13 @@ public class PermanentCreatureAi extends PermanentAi {
*/
final Card copy = CardUtil.getLKICopy(sa.getHostCard());
ComputerUtilCard.applyStaticContPT(game, copy, null);
// AiPlayDecision.WouldBecomeZeroToughnessCreature
return copy.getNetToughness() > 0 || copy.hasStartOfKeyword("etbCounter") || mana.countX() != 0
|| copy.hasETBTrigger(false) || copy.hasETBReplacement() || copy.hasSVar("NoZeroToughnessAI");
if (copy.getNetToughness() <= 0 && !copy.hasStartOfKeyword("etbCounter") && mana.countX() == 0
&& !copy.hasETBTrigger(false) && !copy.hasETBReplacement() && !copy.hasSVar("NoZeroToughnessAI")) {
// AiPlayDecision.WouldBecomeZeroToughnessCreature
return false;
}
return true;
}
}

View File

@@ -19,7 +19,10 @@ public class PermanentNoncreatureAi extends PermanentAi {
@Override
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")
targets = CardLists.filterControlledBy(targets, ai.getOpponents());
}
// AiPlayDecision.AnotherTime
return !targets.isEmpty();
if (targets.isEmpty()) {
// AiPlayDecision.AnotherTime
return false;
}
}
return true;
}

Some files were not shown because too many files have changed in this diff Show More