mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-11 08:16:23 +00:00
Normalize line endings
This commit is contained in:
254
.fbprefs
254
.fbprefs
@@ -1,127 +1,127 @@
|
||||
#FindBugs User Preferences
|
||||
#Wed Jul 27 18:41:56 EDT 2011
|
||||
detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true
|
||||
detectorBadAppletConstructor=BadAppletConstructor|false
|
||||
detectorBadResultSetAccess=BadResultSetAccess|true
|
||||
detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true
|
||||
detectorBadUseOfReturnValue=BadUseOfReturnValue|true
|
||||
detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true
|
||||
detectorBooleanReturnNull=BooleanReturnNull|true
|
||||
detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false
|
||||
detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true
|
||||
detectorCheckTypeQualifiers=CheckTypeQualifiers|true
|
||||
detectorCloneIdiom=CloneIdiom|true
|
||||
detectorComparatorIdiom=ComparatorIdiom|true
|
||||
detectorConfusedInheritance=ConfusedInheritance|true
|
||||
detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true
|
||||
detectorCrossSiteScripting=CrossSiteScripting|true
|
||||
detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true
|
||||
detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true
|
||||
detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true
|
||||
detectorDontUseEnum=DontUseEnum|true
|
||||
detectorDroppedException=DroppedException|true
|
||||
detectorDumbMethodInvocations=DumbMethodInvocations|true
|
||||
detectorDumbMethods=DumbMethods|true
|
||||
detectorDuplicateBranches=DuplicateBranches|true
|
||||
detectorEmptyZipFileEntry=EmptyZipFileEntry|true
|
||||
detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true
|
||||
detectorFinalizerNullsFields=FinalizerNullsFields|true
|
||||
detectorFindBadCast2=FindBadCast2|true
|
||||
detectorFindBadForLoop=FindBadForLoop|true
|
||||
detectorFindCircularDependencies=FindCircularDependencies|false
|
||||
detectorFindDeadLocalStores=FindDeadLocalStores|true
|
||||
detectorFindDoubleCheck=FindDoubleCheck|true
|
||||
detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true
|
||||
detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true
|
||||
detectorFindFinalizeInvocations=FindFinalizeInvocations|true
|
||||
detectorFindFloatEquality=FindFloatEquality|true
|
||||
detectorFindHEmismatch=FindHEmismatch|true
|
||||
detectorFindInconsistentSync2=FindInconsistentSync2|true
|
||||
detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true
|
||||
detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true
|
||||
detectorFindMaskedFields=FindMaskedFields|true
|
||||
detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true
|
||||
detectorFindNakedNotify=FindNakedNotify|true
|
||||
detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true
|
||||
detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true
|
||||
detectorFindNonShortCircuit=FindNonShortCircuit|true
|
||||
detectorFindNullDeref=FindNullDeref|true
|
||||
detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true
|
||||
detectorFindOpenStream=FindOpenStream|true
|
||||
detectorFindPuzzlers=FindPuzzlers|true
|
||||
detectorFindRefComparison=FindRefComparison|true
|
||||
detectorFindReturnRef=FindReturnRef|true
|
||||
detectorFindRunInvocations=FindRunInvocations|true
|
||||
detectorFindSelfComparison=FindSelfComparison|true
|
||||
detectorFindSelfComparison2=FindSelfComparison2|true
|
||||
detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true
|
||||
detectorFindSpinLoop=FindSpinLoop|true
|
||||
detectorFindSqlInjection=FindSqlInjection|true
|
||||
detectorFindTwoLockWait=FindTwoLockWait|true
|
||||
detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true
|
||||
detectorFindUnconditionalWait=FindUnconditionalWait|true
|
||||
detectorFindUninitializedGet=FindUninitializedGet|true
|
||||
detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true
|
||||
detectorFindUnreleasedLock=FindUnreleasedLock|true
|
||||
detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true
|
||||
detectorFindUnsyncGet=FindUnsyncGet|true
|
||||
detectorFindUselessControlFlow=FindUselessControlFlow|true
|
||||
detectorFormatStringChecker=FormatStringChecker|true
|
||||
detectorHugeSharedStringConstants=HugeSharedStringConstants|true
|
||||
detectorIDivResultCastToDouble=IDivResultCastToDouble|true
|
||||
detectorIncompatMask=IncompatMask|true
|
||||
detectorInconsistentAnnotations=InconsistentAnnotations|true
|
||||
detectorInefficientMemberAccess=InefficientMemberAccess|false
|
||||
detectorInefficientToArray=InefficientToArray|true
|
||||
detectorInfiniteLoop=InfiniteLoop|true
|
||||
detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true
|
||||
detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false
|
||||
detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true
|
||||
detectorInitializationChain=InitializationChain|true
|
||||
detectorInstantiateStaticClass=InstantiateStaticClass|true
|
||||
detectorInvalidJUnitTest=InvalidJUnitTest|true
|
||||
detectorIteratorIdioms=IteratorIdioms|true
|
||||
detectorLazyInit=LazyInit|true
|
||||
detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true
|
||||
detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true
|
||||
detectorMethodReturnCheck=MethodReturnCheck|true
|
||||
detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true
|
||||
detectorMutableLock=MutableLock|true
|
||||
detectorMutableStaticFields=MutableStaticFields|true
|
||||
detectorNaming=Naming|true
|
||||
detectorNumberConstructor=NumberConstructor|true
|
||||
detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true
|
||||
detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true
|
||||
detectorPublicSemaphores=PublicSemaphores|false
|
||||
detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true
|
||||
detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true
|
||||
detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true
|
||||
detectorRedundantInterfaces=RedundantInterfaces|true
|
||||
detectorRepeatedConditionals=RepeatedConditionals|true
|
||||
detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true
|
||||
detectorSerializableIdiom=SerializableIdiom|true
|
||||
detectorStartInConstructor=StartInConstructor|true
|
||||
detectorStaticCalendarDetector=StaticCalendarDetector|true
|
||||
detectorStringConcatenation=StringConcatenation|true
|
||||
detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true
|
||||
detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true
|
||||
detectorSwitchFallthrough=SwitchFallthrough|true
|
||||
detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true
|
||||
detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true
|
||||
detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true
|
||||
detectorURLProblems=URLProblems|true
|
||||
detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true
|
||||
detectorUnnecessaryMath=UnnecessaryMath|true
|
||||
detectorUnreadFields=UnreadFields|true
|
||||
detectorUseObjectEquals=UseObjectEquals|false
|
||||
detectorUselessSubclassMethod=UselessSubclassMethod|false
|
||||
detectorVarArgsProblems=VarArgsProblems|true
|
||||
detectorVolatileUsage=VolatileUsage|true
|
||||
detectorWaitInLoop=WaitInLoop|true
|
||||
detectorWrongMapIterator=WrongMapIterator|true
|
||||
detectorXMLFactoryBypass=XMLFactoryBypass|true
|
||||
detector_threshold=2
|
||||
effort=default
|
||||
filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false
|
||||
filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL|
|
||||
run_at_full_build=false
|
||||
#FindBugs User Preferences
|
||||
#Wed Jul 27 18:41:56 EDT 2011
|
||||
detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true
|
||||
detectorBadAppletConstructor=BadAppletConstructor|false
|
||||
detectorBadResultSetAccess=BadResultSetAccess|true
|
||||
detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true
|
||||
detectorBadUseOfReturnValue=BadUseOfReturnValue|true
|
||||
detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true
|
||||
detectorBooleanReturnNull=BooleanReturnNull|true
|
||||
detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false
|
||||
detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true
|
||||
detectorCheckTypeQualifiers=CheckTypeQualifiers|true
|
||||
detectorCloneIdiom=CloneIdiom|true
|
||||
detectorComparatorIdiom=ComparatorIdiom|true
|
||||
detectorConfusedInheritance=ConfusedInheritance|true
|
||||
detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true
|
||||
detectorCrossSiteScripting=CrossSiteScripting|true
|
||||
detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true
|
||||
detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true
|
||||
detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true
|
||||
detectorDontUseEnum=DontUseEnum|true
|
||||
detectorDroppedException=DroppedException|true
|
||||
detectorDumbMethodInvocations=DumbMethodInvocations|true
|
||||
detectorDumbMethods=DumbMethods|true
|
||||
detectorDuplicateBranches=DuplicateBranches|true
|
||||
detectorEmptyZipFileEntry=EmptyZipFileEntry|true
|
||||
detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true
|
||||
detectorFinalizerNullsFields=FinalizerNullsFields|true
|
||||
detectorFindBadCast2=FindBadCast2|true
|
||||
detectorFindBadForLoop=FindBadForLoop|true
|
||||
detectorFindCircularDependencies=FindCircularDependencies|false
|
||||
detectorFindDeadLocalStores=FindDeadLocalStores|true
|
||||
detectorFindDoubleCheck=FindDoubleCheck|true
|
||||
detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true
|
||||
detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true
|
||||
detectorFindFinalizeInvocations=FindFinalizeInvocations|true
|
||||
detectorFindFloatEquality=FindFloatEquality|true
|
||||
detectorFindHEmismatch=FindHEmismatch|true
|
||||
detectorFindInconsistentSync2=FindInconsistentSync2|true
|
||||
detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true
|
||||
detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true
|
||||
detectorFindMaskedFields=FindMaskedFields|true
|
||||
detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true
|
||||
detectorFindNakedNotify=FindNakedNotify|true
|
||||
detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true
|
||||
detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true
|
||||
detectorFindNonShortCircuit=FindNonShortCircuit|true
|
||||
detectorFindNullDeref=FindNullDeref|true
|
||||
detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true
|
||||
detectorFindOpenStream=FindOpenStream|true
|
||||
detectorFindPuzzlers=FindPuzzlers|true
|
||||
detectorFindRefComparison=FindRefComparison|true
|
||||
detectorFindReturnRef=FindReturnRef|true
|
||||
detectorFindRunInvocations=FindRunInvocations|true
|
||||
detectorFindSelfComparison=FindSelfComparison|true
|
||||
detectorFindSelfComparison2=FindSelfComparison2|true
|
||||
detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true
|
||||
detectorFindSpinLoop=FindSpinLoop|true
|
||||
detectorFindSqlInjection=FindSqlInjection|true
|
||||
detectorFindTwoLockWait=FindTwoLockWait|true
|
||||
detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true
|
||||
detectorFindUnconditionalWait=FindUnconditionalWait|true
|
||||
detectorFindUninitializedGet=FindUninitializedGet|true
|
||||
detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true
|
||||
detectorFindUnreleasedLock=FindUnreleasedLock|true
|
||||
detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true
|
||||
detectorFindUnsyncGet=FindUnsyncGet|true
|
||||
detectorFindUselessControlFlow=FindUselessControlFlow|true
|
||||
detectorFormatStringChecker=FormatStringChecker|true
|
||||
detectorHugeSharedStringConstants=HugeSharedStringConstants|true
|
||||
detectorIDivResultCastToDouble=IDivResultCastToDouble|true
|
||||
detectorIncompatMask=IncompatMask|true
|
||||
detectorInconsistentAnnotations=InconsistentAnnotations|true
|
||||
detectorInefficientMemberAccess=InefficientMemberAccess|false
|
||||
detectorInefficientToArray=InefficientToArray|true
|
||||
detectorInfiniteLoop=InfiniteLoop|true
|
||||
detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true
|
||||
detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false
|
||||
detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true
|
||||
detectorInitializationChain=InitializationChain|true
|
||||
detectorInstantiateStaticClass=InstantiateStaticClass|true
|
||||
detectorInvalidJUnitTest=InvalidJUnitTest|true
|
||||
detectorIteratorIdioms=IteratorIdioms|true
|
||||
detectorLazyInit=LazyInit|true
|
||||
detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true
|
||||
detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true
|
||||
detectorMethodReturnCheck=MethodReturnCheck|true
|
||||
detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true
|
||||
detectorMutableLock=MutableLock|true
|
||||
detectorMutableStaticFields=MutableStaticFields|true
|
||||
detectorNaming=Naming|true
|
||||
detectorNumberConstructor=NumberConstructor|true
|
||||
detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true
|
||||
detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true
|
||||
detectorPublicSemaphores=PublicSemaphores|false
|
||||
detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true
|
||||
detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true
|
||||
detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true
|
||||
detectorRedundantInterfaces=RedundantInterfaces|true
|
||||
detectorRepeatedConditionals=RepeatedConditionals|true
|
||||
detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true
|
||||
detectorSerializableIdiom=SerializableIdiom|true
|
||||
detectorStartInConstructor=StartInConstructor|true
|
||||
detectorStaticCalendarDetector=StaticCalendarDetector|true
|
||||
detectorStringConcatenation=StringConcatenation|true
|
||||
detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true
|
||||
detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true
|
||||
detectorSwitchFallthrough=SwitchFallthrough|true
|
||||
detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true
|
||||
detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true
|
||||
detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true
|
||||
detectorURLProblems=URLProblems|true
|
||||
detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true
|
||||
detectorUnnecessaryMath=UnnecessaryMath|true
|
||||
detectorUnreadFields=UnreadFields|true
|
||||
detectorUseObjectEquals=UseObjectEquals|false
|
||||
detectorUselessSubclassMethod=UselessSubclassMethod|false
|
||||
detectorVarArgsProblems=VarArgsProblems|true
|
||||
detectorVolatileUsage=VolatileUsage|true
|
||||
detectorWaitInLoop=WaitInLoop|true
|
||||
detectorWrongMapIterator=WrongMapIterator|true
|
||||
detectorXMLFactoryBypass=XMLFactoryBypass|true
|
||||
detector_threshold=2
|
||||
effort=default
|
||||
filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false
|
||||
filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL|
|
||||
run_at_full_build=false
|
||||
|
||||
22393
.gitattributes
vendored
22393
.gitattributes
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,284 +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
|
||||
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
@@ -1,9 +1,9 @@
|
||||
<?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.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"/>
|
||||
<?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.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>
|
||||
@@ -1,4 +1,4 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=ISO-8859-1
|
||||
encoding//src/test/java=ISO-8859-1
|
||||
encoding/<project>=UTF-8
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=ISO-8859-1
|
||||
encoding//src/test/java=ISO-8859-1
|
||||
encoding/<project>=UTF-8
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,21 @@
|
||||
package forge.ai;
|
||||
|
||||
public enum AiPlayDecision {
|
||||
WillPlay,
|
||||
CantPlaySa,
|
||||
CantPlayAi,
|
||||
CantAfford,
|
||||
CantAffordX,
|
||||
WaitForMain2,
|
||||
AnotherTime,
|
||||
MissingNeededCards,
|
||||
NeedsToPlayCriteriaNotMet,
|
||||
TargetingFailed,
|
||||
CostNotAcceptable,
|
||||
WouldDestroyLegend,
|
||||
WouldDestroyOtherPlaneswalker,
|
||||
WouldBecomeZeroToughnessCreature,
|
||||
WouldDestroyWorldEnchantment,
|
||||
BadEtbEffects,
|
||||
CurseEffects;
|
||||
package forge.ai;
|
||||
|
||||
public enum AiPlayDecision {
|
||||
WillPlay,
|
||||
CantPlaySa,
|
||||
CantPlayAi,
|
||||
CantAfford,
|
||||
CantAffordX,
|
||||
WaitForMain2,
|
||||
AnotherTime,
|
||||
MissingNeededCards,
|
||||
NeedsToPlayCriteriaNotMet,
|
||||
TargetingFailed,
|
||||
CostNotAcceptable,
|
||||
WouldDestroyLegend,
|
||||
WouldDestroyOtherPlaneswalker,
|
||||
WouldBecomeZeroToughnessCreature,
|
||||
WouldDestroyWorldEnchantment,
|
||||
BadEtbEffects,
|
||||
CurseEffects;
|
||||
}
|
||||
@@ -1,122 +1,122 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2013 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai;
|
||||
|
||||
/**
|
||||
* AI personality profile settings identifiers, and their default values.
|
||||
* When this class is instantiated, these enum values are used
|
||||
* in a map that is populated with the current AI profile settings
|
||||
* from the text file.
|
||||
*/
|
||||
public enum AiProps { /** */
|
||||
DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN ("1"), /** */
|
||||
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE ("3"), /** */
|
||||
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
|
||||
MULLIGAN_THRESHOLD ("5"), /** */
|
||||
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
|
||||
HOLD_LAND_DROP_FOR_MAIN2_IF_UNUSED ("0"), /** */
|
||||
HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS ("true"), /** */
|
||||
CHEAT_WITH_MANA_ON_SHUFFLE ("false"),
|
||||
MOVE_EQUIPMENT_TO_BETTER_CREATURES ("from_useless_only"),
|
||||
MOVE_EQUIPMENT_CREATURE_EVAL_THRESHOLD ("60"),
|
||||
PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS ("true"),
|
||||
PREDICT_SPELLS_FOR_MAIN2 ("true"), /** */
|
||||
RESERVE_MANA_FOR_MAIN2_CHANCE ("0"), /** */
|
||||
PLAY_AGGRO ("false"),
|
||||
CHANCE_TO_ATTACK_INTO_TRADE ("40"), /** */
|
||||
RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE ("true"), /** */
|
||||
ATTACK_INTO_TRADE_WHEN_TAPPED_OUT ("false"), /** */
|
||||
CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA ("0"), /** */
|
||||
TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK ("true"), /** */
|
||||
TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK ("false"), /** */
|
||||
CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK ("30"), /** */
|
||||
ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK ("true"), /** */
|
||||
RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS ("false"), /** */
|
||||
MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE ("1"), /** */
|
||||
ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT ("true"), /** */
|
||||
MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE_WITH_REPL ("1"), /** */
|
||||
MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK ("30"), /** */
|
||||
MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK ("70"), /** */
|
||||
CHANCE_DECREASE_TO_TRADE_VS_EMBALM ("30"), /** */
|
||||
CHANCE_TO_TRADE_TO_SAVE_PLANESWALKER ("70"), /** */
|
||||
CHANCE_TO_TRADE_DOWN_TO_SAVE_PLANESWALKER ("0"), /** */
|
||||
THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER ("135"), /** */
|
||||
THRESHOLD_NONTOKEN_CHUMP_TO_SAVE_PLANESWALKER ("110"), /** */
|
||||
CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL ("true"), /** */
|
||||
MIN_SPELL_CMC_TO_COUNTER ("0"), /** */
|
||||
CHANCE_TO_COUNTER_CMC_1 ("50"), /** */
|
||||
CHANCE_TO_COUNTER_CMC_2 ("75"), /** */
|
||||
CHANCE_TO_COUNTER_CMC_3 ("100"), /** */
|
||||
ALWAYS_COUNTER_OTHER_COUNTERSPELLS ("true"), /** */
|
||||
ALWAYS_COUNTER_DAMAGE_SPELLS ("true"), /** */
|
||||
ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS ("true"), /** */
|
||||
ALWAYS_COUNTER_REMOVAL_SPELLS ("true"), /** */
|
||||
ALWAYS_COUNTER_PUMP_SPELLS ("true"), /** */
|
||||
ALWAYS_COUNTER_AURAS ("true"), /** */
|
||||
ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS (""), /** */
|
||||
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */
|
||||
ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */
|
||||
PRIORITY_REDUCTION_FOR_STORM_SPELLS ("0"), /** */
|
||||
USE_BERSERK_AGGRESSIVELY ("false"), /** */
|
||||
MIN_COUNT_FOR_STORM_SPELLS ("0"), /** */
|
||||
STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE ("1"), /** */
|
||||
STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK ("3"), /** */
|
||||
STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK ("6"), /** */
|
||||
STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING ("3"), /** */
|
||||
STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP ("false"),
|
||||
TOKEN_GENERATION_ABILITY_CHANCE ("80"), /** */
|
||||
TOKEN_GENERATION_ALWAYS_IF_FROM_PLANESWALKER ("true"), /** */
|
||||
TOKEN_GENERATION_ALWAYS_IF_OPP_ATTACKS ("true"), /** */
|
||||
SCRY_NUM_LANDS_TO_STILL_NEED_MORE ("4"), /** */
|
||||
SCRY_NUM_LANDS_TO_NOT_NEED_MORE ("7"), /** */
|
||||
SCRY_NUM_CREATURES_TO_NOT_NEED_SUBPAR_ONES ("4"), /** */
|
||||
SCRY_EVALTHR_CREATCOUNT_TO_SCRY_AWAY_LOWCMC ("3"), /** */
|
||||
SCRY_EVALTHR_TO_SCRY_AWAY_LOWCMC_CREATURE ("160"), /** */
|
||||
SCRY_EVALTHR_CMC_THRESHOLD ("3"), /** */
|
||||
SCRY_IMMEDIATELY_UNCASTABLE_TO_BOTTOM ("false"), /** */
|
||||
SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF ("1"), /** */
|
||||
COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION ("true"), /** */
|
||||
COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION ("true"), /** */
|
||||
CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT ("true"), /** */
|
||||
CONSERVATIVE_ENERGY_PAYMENT_ONLY_DEFENSIVELY ("true"), /** */
|
||||
BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF ("200"), /** */
|
||||
BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF ("200"), /** */
|
||||
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF ("3"), /** */
|
||||
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF ("3"), /** */
|
||||
INTUITION_ALTERNATIVE_LOGIC ("false"), /** */
|
||||
EXPLORE_MAX_CMC_DIFF_TO_PUT_IN_GRAVEYARD ("2"),
|
||||
EXPLORE_NUM_LANDS_TO_STILL_NEED_MORE ("2"); /** */
|
||||
// Experimental features, must be removed after extensive testing and, ideally, defaulting
|
||||
// <-- There are no experimental options here -->
|
||||
|
||||
private final String strDefaultVal;
|
||||
|
||||
/** @param s0   {@link java.lang.String} */
|
||||
AiProps(final String s0) {
|
||||
this.strDefaultVal = s0;
|
||||
}
|
||||
|
||||
/** @return {@link java.lang.String} */
|
||||
public String getDefault() {
|
||||
return strDefaultVal;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2013 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai;
|
||||
|
||||
/**
|
||||
* AI personality profile settings identifiers, and their default values.
|
||||
* When this class is instantiated, these enum values are used
|
||||
* in a map that is populated with the current AI profile settings
|
||||
* from the text file.
|
||||
*/
|
||||
public enum AiProps { /** */
|
||||
DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN ("1"), /** */
|
||||
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE ("3"), /** */
|
||||
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
|
||||
MULLIGAN_THRESHOLD ("5"), /** */
|
||||
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
|
||||
HOLD_LAND_DROP_FOR_MAIN2_IF_UNUSED ("0"), /** */
|
||||
HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS ("true"), /** */
|
||||
CHEAT_WITH_MANA_ON_SHUFFLE ("false"),
|
||||
MOVE_EQUIPMENT_TO_BETTER_CREATURES ("from_useless_only"),
|
||||
MOVE_EQUIPMENT_CREATURE_EVAL_THRESHOLD ("60"),
|
||||
PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS ("true"),
|
||||
PREDICT_SPELLS_FOR_MAIN2 ("true"), /** */
|
||||
RESERVE_MANA_FOR_MAIN2_CHANCE ("0"), /** */
|
||||
PLAY_AGGRO ("false"),
|
||||
CHANCE_TO_ATTACK_INTO_TRADE ("40"), /** */
|
||||
RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE ("true"), /** */
|
||||
ATTACK_INTO_TRADE_WHEN_TAPPED_OUT ("false"), /** */
|
||||
CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA ("0"), /** */
|
||||
TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK ("true"), /** */
|
||||
TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK ("false"), /** */
|
||||
CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK ("30"), /** */
|
||||
ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK ("true"), /** */
|
||||
RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS ("false"), /** */
|
||||
MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE ("1"), /** */
|
||||
ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT ("true"), /** */
|
||||
MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE_WITH_REPL ("1"), /** */
|
||||
MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK ("30"), /** */
|
||||
MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK ("70"), /** */
|
||||
CHANCE_DECREASE_TO_TRADE_VS_EMBALM ("30"), /** */
|
||||
CHANCE_TO_TRADE_TO_SAVE_PLANESWALKER ("70"), /** */
|
||||
CHANCE_TO_TRADE_DOWN_TO_SAVE_PLANESWALKER ("0"), /** */
|
||||
THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER ("135"), /** */
|
||||
THRESHOLD_NONTOKEN_CHUMP_TO_SAVE_PLANESWALKER ("110"), /** */
|
||||
CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL ("true"), /** */
|
||||
MIN_SPELL_CMC_TO_COUNTER ("0"), /** */
|
||||
CHANCE_TO_COUNTER_CMC_1 ("50"), /** */
|
||||
CHANCE_TO_COUNTER_CMC_2 ("75"), /** */
|
||||
CHANCE_TO_COUNTER_CMC_3 ("100"), /** */
|
||||
ALWAYS_COUNTER_OTHER_COUNTERSPELLS ("true"), /** */
|
||||
ALWAYS_COUNTER_DAMAGE_SPELLS ("true"), /** */
|
||||
ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS ("true"), /** */
|
||||
ALWAYS_COUNTER_REMOVAL_SPELLS ("true"), /** */
|
||||
ALWAYS_COUNTER_PUMP_SPELLS ("true"), /** */
|
||||
ALWAYS_COUNTER_AURAS ("true"), /** */
|
||||
ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS (""), /** */
|
||||
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */
|
||||
ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */
|
||||
PRIORITY_REDUCTION_FOR_STORM_SPELLS ("0"), /** */
|
||||
USE_BERSERK_AGGRESSIVELY ("false"), /** */
|
||||
MIN_COUNT_FOR_STORM_SPELLS ("0"), /** */
|
||||
STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE ("1"), /** */
|
||||
STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK ("3"), /** */
|
||||
STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK ("6"), /** */
|
||||
STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING ("3"), /** */
|
||||
STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP ("false"),
|
||||
TOKEN_GENERATION_ABILITY_CHANCE ("80"), /** */
|
||||
TOKEN_GENERATION_ALWAYS_IF_FROM_PLANESWALKER ("true"), /** */
|
||||
TOKEN_GENERATION_ALWAYS_IF_OPP_ATTACKS ("true"), /** */
|
||||
SCRY_NUM_LANDS_TO_STILL_NEED_MORE ("4"), /** */
|
||||
SCRY_NUM_LANDS_TO_NOT_NEED_MORE ("7"), /** */
|
||||
SCRY_NUM_CREATURES_TO_NOT_NEED_SUBPAR_ONES ("4"), /** */
|
||||
SCRY_EVALTHR_CREATCOUNT_TO_SCRY_AWAY_LOWCMC ("3"), /** */
|
||||
SCRY_EVALTHR_TO_SCRY_AWAY_LOWCMC_CREATURE ("160"), /** */
|
||||
SCRY_EVALTHR_CMC_THRESHOLD ("3"), /** */
|
||||
SCRY_IMMEDIATELY_UNCASTABLE_TO_BOTTOM ("false"), /** */
|
||||
SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF ("1"), /** */
|
||||
COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION ("true"), /** */
|
||||
COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION ("true"), /** */
|
||||
CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT ("true"), /** */
|
||||
CONSERVATIVE_ENERGY_PAYMENT_ONLY_DEFENSIVELY ("true"), /** */
|
||||
BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF ("200"), /** */
|
||||
BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF ("200"), /** */
|
||||
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF ("3"), /** */
|
||||
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF ("3"), /** */
|
||||
INTUITION_ALTERNATIVE_LOGIC ("false"), /** */
|
||||
EXPLORE_MAX_CMC_DIFF_TO_PUT_IN_GRAVEYARD ("2"),
|
||||
EXPLORE_NUM_LANDS_TO_STILL_NEED_MORE ("2"); /** */
|
||||
// Experimental features, must be removed after extensive testing and, ideally, defaulting
|
||||
// <-- There are no experimental options here -->
|
||||
|
||||
private final String strDefaultVal;
|
||||
|
||||
/** @param s0   {@link java.lang.String} */
|
||||
AiProps(final String s0) {
|
||||
this.strDefaultVal = s0;
|
||||
}
|
||||
|
||||
/** @return {@link java.lang.String} */
|
||||
public String getDefault() {
|
||||
return strDefaultVal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,174 +1,174 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class ComputerUtilAbility {
|
||||
public static CardCollection getAvailableLandsToPlay(final Game game, final Player player) {
|
||||
if (!game.getStack().isEmpty() || !game.getPhaseHandler().getPhase().isMain()) {
|
||||
return null;
|
||||
}
|
||||
final CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
||||
hand.addAll(player.getCardsIn(ZoneType.Exile));
|
||||
CardCollection landList = CardLists.filter(hand, Presets.LANDS);
|
||||
|
||||
//filter out cards that can't be played
|
||||
landList = CardLists.filter(landList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (!c.getSVar("NeedsToPlay").isEmpty()) {
|
||||
final String needsToPlay = c.getSVar("NeedsToPlay");
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay.split(","), c.getController(), c, null);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return player.canPlayLand(c);
|
||||
}
|
||||
});
|
||||
|
||||
final CardCollection landsNotInHand = new CardCollection(player.getCardsIn(ZoneType.Graveyard));
|
||||
landsNotInHand.addAll(game.getCardsIn(ZoneType.Exile));
|
||||
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||
landsNotInHand.add(player.getCardsIn(ZoneType.Library).get(0));
|
||||
}
|
||||
for (final Card crd : landsNotInHand) {
|
||||
if (!(crd.isLand() || (crd.isFaceDown() && crd.getState(CardStateName.Original).getType().isLand()))) {
|
||||
continue;
|
||||
}
|
||||
if (!crd.mayPlay(player).isEmpty()) {
|
||||
landList.add(crd);
|
||||
}
|
||||
}
|
||||
if (landList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return landList;
|
||||
}
|
||||
|
||||
public static CardCollection getAvailableCards(final Game game, final Player player) {
|
||||
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
||||
|
||||
all.addAll(player.getCardsIn(ZoneType.Graveyard));
|
||||
all.addAll(player.getCardsIn(ZoneType.Command));
|
||||
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||
all.add(player.getCardsIn(ZoneType.Library).get(0));
|
||||
}
|
||||
for(Player p : game.getPlayers()) {
|
||||
all.addAll(p.getCardsIn(ZoneType.Exile));
|
||||
all.addAll(p.getCardsIn(ZoneType.Battlefield));
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
public static List<SpellAbility> getSpellAbilities(final CardCollectionView l, final Player player) {
|
||||
final List<SpellAbility> spellAbilities = Lists.newArrayList();
|
||||
for (final Card c : l) {
|
||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||
spellAbilities.add(sa);
|
||||
}
|
||||
if (c.isFaceDown() && c.isInZone(ZoneType.Exile) && !c.mayPlay(player).isEmpty()) {
|
||||
for (final SpellAbility sa : c.getState(CardStateName.Original).getSpellAbilities()) {
|
||||
spellAbilities.add(sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
return spellAbilities;
|
||||
}
|
||||
|
||||
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
|
||||
final List<SpellAbility> newAbilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : originList) {
|
||||
sa.setActivatingPlayer(player);
|
||||
//add alternative costs as additional spell abilities
|
||||
newAbilities.add(sa);
|
||||
newAbilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
||||
}
|
||||
|
||||
final List<SpellAbility> result = Lists.newArrayList();
|
||||
for (SpellAbility sa : newAbilities) {
|
||||
sa.setActivatingPlayer(player);
|
||||
result.addAll(GameActionUtil.getOptionalCosts(sa));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static SpellAbility getTopSpellAbilityOnStack(Game game, SpellAbility sa) {
|
||||
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
|
||||
|
||||
if (!it.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SpellAbility tgtSA = it.next().getSpellAbility(true);
|
||||
// Grab the topmost spellability that isn't this SA and use that for comparisons
|
||||
if (sa.equals(tgtSA) && game.getStack().size() > 1) {
|
||||
if (!it.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
tgtSA = it.next().getSpellAbility(true);
|
||||
}
|
||||
return tgtSA;
|
||||
}
|
||||
|
||||
public static Card getAbilitySource(SpellAbility sa) {
|
||||
return sa.getOriginalHost() != null ? sa.getOriginalHost() : sa.getHostCard();
|
||||
}
|
||||
|
||||
public static String getAbilitySourceName(SpellAbility sa) {
|
||||
final Card c = getAbilitySource(sa);
|
||||
return c != null ? c.getName() : "";
|
||||
}
|
||||
|
||||
public static CardCollection getCardsTargetedWithApi(Player ai, CardCollection cardList, SpellAbility sa, ApiType api) {
|
||||
// Returns a collection of cards which have already been targeted with the given API either in the parent ability,
|
||||
// in the sub ability, or by something on stack. If "sa" is specified, the parent and sub abilities of this SA will
|
||||
// be checked for targets. If "sa" is null, only the stack instances will be checked.
|
||||
CardCollection targeted = new CardCollection();
|
||||
if (sa != null) {
|
||||
SpellAbility saSub = sa.getRootAbility();
|
||||
while (saSub != null) {
|
||||
if (saSub.getApi() == api && saSub.getTargets() != null) {
|
||||
for (Card c : cardList) {
|
||||
if (saSub.getTargets().getTargetCards().contains(c)) {
|
||||
// Was already targeted with this API in a parent or sub SA
|
||||
targeted.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
saSub = saSub.getSubAbility();
|
||||
}
|
||||
}
|
||||
for (SpellAbilityStackInstance si : ai.getGame().getStack()) {
|
||||
SpellAbility ab = si.getSpellAbility(false);
|
||||
if (ab != null && ab.getApi() == api && si.getTargetChoices() != null) {
|
||||
for (Card c : cardList) {
|
||||
// TODO: somehow ensure that the detected SA won't be countered
|
||||
if (si.getTargetChoices().getTargetCards().contains(c)) {
|
||||
// Was already targeted by a spell ability instance on stack
|
||||
targeted.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targeted;
|
||||
}
|
||||
}
|
||||
package forge.ai;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class ComputerUtilAbility {
|
||||
public static CardCollection getAvailableLandsToPlay(final Game game, final Player player) {
|
||||
if (!game.getStack().isEmpty() || !game.getPhaseHandler().getPhase().isMain()) {
|
||||
return null;
|
||||
}
|
||||
final CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
||||
hand.addAll(player.getCardsIn(ZoneType.Exile));
|
||||
CardCollection landList = CardLists.filter(hand, Presets.LANDS);
|
||||
|
||||
//filter out cards that can't be played
|
||||
landList = CardLists.filter(landList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (!c.getSVar("NeedsToPlay").isEmpty()) {
|
||||
final String needsToPlay = c.getSVar("NeedsToPlay");
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay.split(","), c.getController(), c, null);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return player.canPlayLand(c);
|
||||
}
|
||||
});
|
||||
|
||||
final CardCollection landsNotInHand = new CardCollection(player.getCardsIn(ZoneType.Graveyard));
|
||||
landsNotInHand.addAll(game.getCardsIn(ZoneType.Exile));
|
||||
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||
landsNotInHand.add(player.getCardsIn(ZoneType.Library).get(0));
|
||||
}
|
||||
for (final Card crd : landsNotInHand) {
|
||||
if (!(crd.isLand() || (crd.isFaceDown() && crd.getState(CardStateName.Original).getType().isLand()))) {
|
||||
continue;
|
||||
}
|
||||
if (!crd.mayPlay(player).isEmpty()) {
|
||||
landList.add(crd);
|
||||
}
|
||||
}
|
||||
if (landList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return landList;
|
||||
}
|
||||
|
||||
public static CardCollection getAvailableCards(final Game game, final Player player) {
|
||||
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
||||
|
||||
all.addAll(player.getCardsIn(ZoneType.Graveyard));
|
||||
all.addAll(player.getCardsIn(ZoneType.Command));
|
||||
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||
all.add(player.getCardsIn(ZoneType.Library).get(0));
|
||||
}
|
||||
for(Player p : game.getPlayers()) {
|
||||
all.addAll(p.getCardsIn(ZoneType.Exile));
|
||||
all.addAll(p.getCardsIn(ZoneType.Battlefield));
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
public static List<SpellAbility> getSpellAbilities(final CardCollectionView l, final Player player) {
|
||||
final List<SpellAbility> spellAbilities = Lists.newArrayList();
|
||||
for (final Card c : l) {
|
||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||
spellAbilities.add(sa);
|
||||
}
|
||||
if (c.isFaceDown() && c.isInZone(ZoneType.Exile) && !c.mayPlay(player).isEmpty()) {
|
||||
for (final SpellAbility sa : c.getState(CardStateName.Original).getSpellAbilities()) {
|
||||
spellAbilities.add(sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
return spellAbilities;
|
||||
}
|
||||
|
||||
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
|
||||
final List<SpellAbility> newAbilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : originList) {
|
||||
sa.setActivatingPlayer(player);
|
||||
//add alternative costs as additional spell abilities
|
||||
newAbilities.add(sa);
|
||||
newAbilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
||||
}
|
||||
|
||||
final List<SpellAbility> result = Lists.newArrayList();
|
||||
for (SpellAbility sa : newAbilities) {
|
||||
sa.setActivatingPlayer(player);
|
||||
result.addAll(GameActionUtil.getOptionalCosts(sa));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static SpellAbility getTopSpellAbilityOnStack(Game game, SpellAbility sa) {
|
||||
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
|
||||
|
||||
if (!it.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SpellAbility tgtSA = it.next().getSpellAbility(true);
|
||||
// Grab the topmost spellability that isn't this SA and use that for comparisons
|
||||
if (sa.equals(tgtSA) && game.getStack().size() > 1) {
|
||||
if (!it.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
tgtSA = it.next().getSpellAbility(true);
|
||||
}
|
||||
return tgtSA;
|
||||
}
|
||||
|
||||
public static Card getAbilitySource(SpellAbility sa) {
|
||||
return sa.getOriginalHost() != null ? sa.getOriginalHost() : sa.getHostCard();
|
||||
}
|
||||
|
||||
public static String getAbilitySourceName(SpellAbility sa) {
|
||||
final Card c = getAbilitySource(sa);
|
||||
return c != null ? c.getName() : "";
|
||||
}
|
||||
|
||||
public static CardCollection getCardsTargetedWithApi(Player ai, CardCollection cardList, SpellAbility sa, ApiType api) {
|
||||
// Returns a collection of cards which have already been targeted with the given API either in the parent ability,
|
||||
// in the sub ability, or by something on stack. If "sa" is specified, the parent and sub abilities of this SA will
|
||||
// be checked for targets. If "sa" is null, only the stack instances will be checked.
|
||||
CardCollection targeted = new CardCollection();
|
||||
if (sa != null) {
|
||||
SpellAbility saSub = sa.getRootAbility();
|
||||
while (saSub != null) {
|
||||
if (saSub.getApi() == api && saSub.getTargets() != null) {
|
||||
for (Card c : cardList) {
|
||||
if (saSub.getTargets().getTargetCards().contains(c)) {
|
||||
// Was already targeted with this API in a parent or sub SA
|
||||
targeted.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
saSub = saSub.getSubAbility();
|
||||
}
|
||||
}
|
||||
for (SpellAbilityStackInstance si : ai.getGame().getStack()) {
|
||||
SpellAbility ab = si.getSpellAbility(false);
|
||||
if (ab != null && ab.getApi() == api && si.getTargetChoices() != null) {
|
||||
for (Card c : cardList) {
|
||||
// TODO: somehow ensure that the detected SA won't be countered
|
||||
if (si.getTargetChoices().getTargetCards().contains(c)) {
|
||||
// Was already targeted by a spell ability instance on stack
|
||||
targeted.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targeted;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,273 +1,273 @@
|
||||
package forge.ai;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.CostPayEnergy;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
protected int getEffectivePower(final Card c) {
|
||||
return c.getNetCombatDamage();
|
||||
}
|
||||
protected int getEffectiveToughness(final Card c) {
|
||||
return c.getNetToughness();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer apply(Card c) {
|
||||
return evaluateCreature(c);
|
||||
}
|
||||
|
||||
public int evaluateCreature(final Card c) {
|
||||
return evaluateCreature(c, true, true);
|
||||
}
|
||||
|
||||
public int evaluateCreature(final Card c, final boolean considerPT, final boolean considerCMC) {
|
||||
int value = 80;
|
||||
if (!c.isToken()) {
|
||||
value += addValue(20, "non-token"); // tokens should be worth less than actual cards
|
||||
}
|
||||
int power = getEffectivePower(c);
|
||||
final int toughness = getEffectiveToughness(c);
|
||||
for (KeywordInterface kw : c.getKeywords()) {
|
||||
String keyword = kw.getOriginal();
|
||||
if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
|
||||
|| keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
|
||||
|| keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
||||
|| keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
||||
power = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (considerPT) {
|
||||
value += addValue(power * 15, "power");
|
||||
value += addValue(toughness * 10, "toughness: " + toughness);
|
||||
}
|
||||
if (considerCMC) {
|
||||
value += addValue(c.getCMC() * 5, "cmc");
|
||||
}
|
||||
|
||||
// Evasion keywords
|
||||
if (c.hasKeyword("Flying")) {
|
||||
value += addValue(power * 10, "flying");
|
||||
}
|
||||
if (c.hasKeyword("Horsemanship")) {
|
||||
value += addValue(power * 10, "horses");
|
||||
}
|
||||
if (c.hasKeyword("Unblockable")) {
|
||||
value += addValue(power * 10, "unblockable");
|
||||
} else {
|
||||
if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
||||
value += addValue(power * 6, "thorns");
|
||||
}
|
||||
if (c.hasKeyword("Fear")) {
|
||||
value += addValue(power * 6, "fear");
|
||||
}
|
||||
if (c.hasKeyword("Intimidate")) {
|
||||
value += addValue(power * 6, "intimidate");
|
||||
}
|
||||
if (c.hasStartOfKeyword("Menace")) {
|
||||
value += addValue(power * 4, "menace");
|
||||
}
|
||||
if (c.hasStartOfKeyword("CantBeBlockedBy")) {
|
||||
value += addValue(power * 3, "block-restrict");
|
||||
}
|
||||
}
|
||||
|
||||
// Other good keywords
|
||||
if (power > 0) {
|
||||
if (c.hasKeyword("Double Strike")) {
|
||||
value += addValue(10 + (power * 15), "ds");
|
||||
} else if (c.hasKeyword("First Strike")) {
|
||||
value += addValue(10 + (power * 5), "fs");
|
||||
}
|
||||
if (c.hasKeyword("Deathtouch")) {
|
||||
value += addValue(25, "dt");
|
||||
}
|
||||
if (c.hasKeyword("Lifelink")) {
|
||||
value += addValue(power * 10, "lifelink");
|
||||
}
|
||||
if (power > 1 && c.hasKeyword("Trample")) {
|
||||
value += addValue((power - 1) * 5, "trample");
|
||||
}
|
||||
if (c.hasKeyword("Vigilance")) {
|
||||
value += addValue((power * 5) + (toughness * 5), "vigilance");
|
||||
}
|
||||
if (c.hasKeyword("Wither")) {
|
||||
value += addValue(power * 10, "Wither");
|
||||
}
|
||||
if (c.hasKeyword("Infect")) {
|
||||
value += addValue(power * 15, "infect");
|
||||
}
|
||||
value += addValue(c.getKeywordMagnitude("Rampage"), "rampage");
|
||||
value += addValue(c.getKeywordMagnitude("Afflict") * 5, "afflict");
|
||||
}
|
||||
|
||||
value += addValue(c.getKeywordMagnitude("Bushido") * 16, "bushido");
|
||||
value += addValue(c.getAmountOfKeyword("Flanking") * 15, "flanking");
|
||||
value += addValue(c.getAmountOfKeyword("Exalted") * 15, "exalted");
|
||||
value += addValue(c.getKeywordMagnitude("Annihilator") * 50, "eldrazi");
|
||||
value += addValue(c.getKeywordMagnitude("Absorb") * 11, "absorb");
|
||||
|
||||
// Keywords that may produce temporary or permanent buffs over time
|
||||
if (c.hasKeyword("Prowess")) {
|
||||
value += addValue(5, "prowess");
|
||||
}
|
||||
if (c.hasKeyword("Outlast")) {
|
||||
value += addValue(10, "outlast");
|
||||
}
|
||||
|
||||
// Defensive Keywords
|
||||
if (c.hasKeyword("Reach") && !c.hasKeyword("Flying")) {
|
||||
value += addValue(5, "reach");
|
||||
}
|
||||
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
|
||||
value += addValue(3, "shadow-block");
|
||||
}
|
||||
|
||||
// Protection
|
||||
if (c.hasKeyword("Indestructible")) {
|
||||
value += addValue(70, "darksteel");
|
||||
}
|
||||
if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
|
||||
value += addValue(60, "cho-manno");
|
||||
} else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
|
||||
value += addValue(50, "fogbank");
|
||||
}
|
||||
if (c.hasKeyword("Hexproof")) {
|
||||
value += addValue(35, "hexproof");
|
||||
} else if (c.hasKeyword("Shroud")) {
|
||||
value += addValue(30, "shroud");
|
||||
}
|
||||
if (c.hasStartOfKeyword("Protection")) {
|
||||
value += addValue(20, "protection");
|
||||
}
|
||||
if (c.hasStartOfKeyword("PreventAllDamageBy")) {
|
||||
value += addValue(10, "prevent-dmg");
|
||||
}
|
||||
|
||||
// Bad keywords
|
||||
if (c.hasKeyword("Defender") || c.hasKeyword("CARDNAME can't attack.")) {
|
||||
value -= subValue((power * 9) + 40, "defender");
|
||||
} else if (c.getSVar("SacrificeEndCombat").equals("True")) {
|
||||
value -= subValue(40, "sac-end");
|
||||
}
|
||||
if (c.hasKeyword("CARDNAME can't block.")) {
|
||||
value -= subValue(10, "cant-block");
|
||||
} else if (c.hasKeyword("CARDNAME attacks each turn if able.")
|
||||
|| c.hasKeyword("CARDNAME attacks each combat if able.")) {
|
||||
value -= subValue(10, "must-attack");
|
||||
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
||||
value -= subValue(10, "must-attack-player");
|
||||
} else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
|
||||
value -= subValue(toughness * 5, "reverse-reach");
|
||||
}
|
||||
|
||||
if (c.hasSVar("DestroyWhenDamaged")) {
|
||||
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
|
||||
}
|
||||
|
||||
if (c.hasKeyword("CARDNAME can't attack or block.")) {
|
||||
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
|
||||
}
|
||||
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 {
|
||||
value -= subValue(50, "doesnt-untap");
|
||||
}
|
||||
}
|
||||
if (c.hasSVar("EndOfTurnLeavePlay")) {
|
||||
value -= subValue(50, "eot-leaves");
|
||||
} else if (c.hasStartOfKeyword("Cumulative upkeep")) {
|
||||
value -= subValue(30, "cupkeep");
|
||||
} else if (c.hasStartOfKeyword("UpkeepCost")) {
|
||||
value -= subValue(20, "sac-unless");
|
||||
} else if (c.hasStartOfKeyword("Echo") && c.cameUnderControlSinceLastUpkeep()) {
|
||||
value -= subValue(10, "echo-unpaid");
|
||||
}
|
||||
|
||||
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
|
||||
value -= subValue(20, "upkeep-dmg");
|
||||
}
|
||||
if (c.hasStartOfKeyword("Fading")) {
|
||||
value -= subValue(20, "fading");
|
||||
}
|
||||
if (c.hasStartOfKeyword("Vanishing")) {
|
||||
value -= subValue(20, "vanishing");
|
||||
}
|
||||
if (c.getSVar("Targeting").equals("Dies")) {
|
||||
value -= subValue(25, "dies");
|
||||
}
|
||||
|
||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||
if (sa.isAbility()) {
|
||||
value += addValue(evaluateSpellAbility(sa), "sa: " + sa);
|
||||
}
|
||||
}
|
||||
if (!c.getManaAbilities().isEmpty()) {
|
||||
value += addValue(10, "manadork");
|
||||
}
|
||||
|
||||
if (c.isUntapped()) {
|
||||
value += addValue(1, "untapped");
|
||||
}
|
||||
|
||||
// paired creatures are more valuable because they grant a bonus to the other creature
|
||||
if (c.isPaired()) {
|
||||
value += addValue(14, "paired");
|
||||
}
|
||||
|
||||
if (!c.getEncodedCards().isEmpty()) {
|
||||
value += addValue(24, "encoded");
|
||||
}
|
||||
|
||||
// card-specific evaluation modifier
|
||||
if (c.hasSVar("AIEvaluationModifier")) {
|
||||
int mod = AbilityUtils.calculateAmount(c, c.getSVar("AIEvaluationModifier"), null);
|
||||
value += mod;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private int evaluateSpellAbility(SpellAbility sa) {
|
||||
// Pump abilities
|
||||
if (sa.getApi() == ApiType.Pump) {
|
||||
// Pump abilities that grant +X/+X to the card
|
||||
if ("+X".equals(sa.getParam("NumAtt"))
|
||||
&& "+X".equals(sa.getParam("NumDef"))
|
||||
&& !sa.usesTargeting()
|
||||
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||
// Electrostatic Pummeler, can be expanded for similar cards
|
||||
int initPower = getEffectivePower(sa.getHostCard());
|
||||
int pumpedPower = initPower;
|
||||
int energy = sa.getHostCard().getController().getCounters(CounterType.ENERGY);
|
||||
if (energy > 0) {
|
||||
int numActivations = energy / 3;
|
||||
for (int i = 0; i < numActivations; i++) {
|
||||
pumpedPower *= 2;
|
||||
}
|
||||
return (pumpedPower - initPower) * 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default value
|
||||
return 10;
|
||||
}
|
||||
|
||||
protected int addValue(int value, String text) {
|
||||
return value;
|
||||
}
|
||||
protected int subValue(int value, String text) {
|
||||
return -addValue(-value, text);
|
||||
}
|
||||
}
|
||||
package forge.ai;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.CostPayEnergy;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
protected int getEffectivePower(final Card c) {
|
||||
return c.getNetCombatDamage();
|
||||
}
|
||||
protected int getEffectiveToughness(final Card c) {
|
||||
return c.getNetToughness();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer apply(Card c) {
|
||||
return evaluateCreature(c);
|
||||
}
|
||||
|
||||
public int evaluateCreature(final Card c) {
|
||||
return evaluateCreature(c, true, true);
|
||||
}
|
||||
|
||||
public int evaluateCreature(final Card c, final boolean considerPT, final boolean considerCMC) {
|
||||
int value = 80;
|
||||
if (!c.isToken()) {
|
||||
value += addValue(20, "non-token"); // tokens should be worth less than actual cards
|
||||
}
|
||||
int power = getEffectivePower(c);
|
||||
final int toughness = getEffectiveToughness(c);
|
||||
for (KeywordInterface kw : c.getKeywords()) {
|
||||
String keyword = kw.getOriginal();
|
||||
if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
|
||||
|| keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
|
||||
|| keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
||||
|| keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
||||
power = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (considerPT) {
|
||||
value += addValue(power * 15, "power");
|
||||
value += addValue(toughness * 10, "toughness: " + toughness);
|
||||
}
|
||||
if (considerCMC) {
|
||||
value += addValue(c.getCMC() * 5, "cmc");
|
||||
}
|
||||
|
||||
// Evasion keywords
|
||||
if (c.hasKeyword("Flying")) {
|
||||
value += addValue(power * 10, "flying");
|
||||
}
|
||||
if (c.hasKeyword("Horsemanship")) {
|
||||
value += addValue(power * 10, "horses");
|
||||
}
|
||||
if (c.hasKeyword("Unblockable")) {
|
||||
value += addValue(power * 10, "unblockable");
|
||||
} else {
|
||||
if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
||||
value += addValue(power * 6, "thorns");
|
||||
}
|
||||
if (c.hasKeyword("Fear")) {
|
||||
value += addValue(power * 6, "fear");
|
||||
}
|
||||
if (c.hasKeyword("Intimidate")) {
|
||||
value += addValue(power * 6, "intimidate");
|
||||
}
|
||||
if (c.hasStartOfKeyword("Menace")) {
|
||||
value += addValue(power * 4, "menace");
|
||||
}
|
||||
if (c.hasStartOfKeyword("CantBeBlockedBy")) {
|
||||
value += addValue(power * 3, "block-restrict");
|
||||
}
|
||||
}
|
||||
|
||||
// Other good keywords
|
||||
if (power > 0) {
|
||||
if (c.hasKeyword("Double Strike")) {
|
||||
value += addValue(10 + (power * 15), "ds");
|
||||
} else if (c.hasKeyword("First Strike")) {
|
||||
value += addValue(10 + (power * 5), "fs");
|
||||
}
|
||||
if (c.hasKeyword("Deathtouch")) {
|
||||
value += addValue(25, "dt");
|
||||
}
|
||||
if (c.hasKeyword("Lifelink")) {
|
||||
value += addValue(power * 10, "lifelink");
|
||||
}
|
||||
if (power > 1 && c.hasKeyword("Trample")) {
|
||||
value += addValue((power - 1) * 5, "trample");
|
||||
}
|
||||
if (c.hasKeyword("Vigilance")) {
|
||||
value += addValue((power * 5) + (toughness * 5), "vigilance");
|
||||
}
|
||||
if (c.hasKeyword("Wither")) {
|
||||
value += addValue(power * 10, "Wither");
|
||||
}
|
||||
if (c.hasKeyword("Infect")) {
|
||||
value += addValue(power * 15, "infect");
|
||||
}
|
||||
value += addValue(c.getKeywordMagnitude("Rampage"), "rampage");
|
||||
value += addValue(c.getKeywordMagnitude("Afflict") * 5, "afflict");
|
||||
}
|
||||
|
||||
value += addValue(c.getKeywordMagnitude("Bushido") * 16, "bushido");
|
||||
value += addValue(c.getAmountOfKeyword("Flanking") * 15, "flanking");
|
||||
value += addValue(c.getAmountOfKeyword("Exalted") * 15, "exalted");
|
||||
value += addValue(c.getKeywordMagnitude("Annihilator") * 50, "eldrazi");
|
||||
value += addValue(c.getKeywordMagnitude("Absorb") * 11, "absorb");
|
||||
|
||||
// Keywords that may produce temporary or permanent buffs over time
|
||||
if (c.hasKeyword("Prowess")) {
|
||||
value += addValue(5, "prowess");
|
||||
}
|
||||
if (c.hasKeyword("Outlast")) {
|
||||
value += addValue(10, "outlast");
|
||||
}
|
||||
|
||||
// Defensive Keywords
|
||||
if (c.hasKeyword("Reach") && !c.hasKeyword("Flying")) {
|
||||
value += addValue(5, "reach");
|
||||
}
|
||||
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
|
||||
value += addValue(3, "shadow-block");
|
||||
}
|
||||
|
||||
// Protection
|
||||
if (c.hasKeyword("Indestructible")) {
|
||||
value += addValue(70, "darksteel");
|
||||
}
|
||||
if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
|
||||
value += addValue(60, "cho-manno");
|
||||
} else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
|
||||
value += addValue(50, "fogbank");
|
||||
}
|
||||
if (c.hasKeyword("Hexproof")) {
|
||||
value += addValue(35, "hexproof");
|
||||
} else if (c.hasKeyword("Shroud")) {
|
||||
value += addValue(30, "shroud");
|
||||
}
|
||||
if (c.hasStartOfKeyword("Protection")) {
|
||||
value += addValue(20, "protection");
|
||||
}
|
||||
if (c.hasStartOfKeyword("PreventAllDamageBy")) {
|
||||
value += addValue(10, "prevent-dmg");
|
||||
}
|
||||
|
||||
// Bad keywords
|
||||
if (c.hasKeyword("Defender") || c.hasKeyword("CARDNAME can't attack.")) {
|
||||
value -= subValue((power * 9) + 40, "defender");
|
||||
} else if (c.getSVar("SacrificeEndCombat").equals("True")) {
|
||||
value -= subValue(40, "sac-end");
|
||||
}
|
||||
if (c.hasKeyword("CARDNAME can't block.")) {
|
||||
value -= subValue(10, "cant-block");
|
||||
} else if (c.hasKeyword("CARDNAME attacks each turn if able.")
|
||||
|| c.hasKeyword("CARDNAME attacks each combat if able.")) {
|
||||
value -= subValue(10, "must-attack");
|
||||
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
||||
value -= subValue(10, "must-attack-player");
|
||||
} else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
|
||||
value -= subValue(toughness * 5, "reverse-reach");
|
||||
}
|
||||
|
||||
if (c.hasSVar("DestroyWhenDamaged")) {
|
||||
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
|
||||
}
|
||||
|
||||
if (c.hasKeyword("CARDNAME can't attack or block.")) {
|
||||
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
|
||||
}
|
||||
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 {
|
||||
value -= subValue(50, "doesnt-untap");
|
||||
}
|
||||
}
|
||||
if (c.hasSVar("EndOfTurnLeavePlay")) {
|
||||
value -= subValue(50, "eot-leaves");
|
||||
} else if (c.hasStartOfKeyword("Cumulative upkeep")) {
|
||||
value -= subValue(30, "cupkeep");
|
||||
} else if (c.hasStartOfKeyword("UpkeepCost")) {
|
||||
value -= subValue(20, "sac-unless");
|
||||
} else if (c.hasStartOfKeyword("Echo") && c.cameUnderControlSinceLastUpkeep()) {
|
||||
value -= subValue(10, "echo-unpaid");
|
||||
}
|
||||
|
||||
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
|
||||
value -= subValue(20, "upkeep-dmg");
|
||||
}
|
||||
if (c.hasStartOfKeyword("Fading")) {
|
||||
value -= subValue(20, "fading");
|
||||
}
|
||||
if (c.hasStartOfKeyword("Vanishing")) {
|
||||
value -= subValue(20, "vanishing");
|
||||
}
|
||||
if (c.getSVar("Targeting").equals("Dies")) {
|
||||
value -= subValue(25, "dies");
|
||||
}
|
||||
|
||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||
if (sa.isAbility()) {
|
||||
value += addValue(evaluateSpellAbility(sa), "sa: " + sa);
|
||||
}
|
||||
}
|
||||
if (!c.getManaAbilities().isEmpty()) {
|
||||
value += addValue(10, "manadork");
|
||||
}
|
||||
|
||||
if (c.isUntapped()) {
|
||||
value += addValue(1, "untapped");
|
||||
}
|
||||
|
||||
// paired creatures are more valuable because they grant a bonus to the other creature
|
||||
if (c.isPaired()) {
|
||||
value += addValue(14, "paired");
|
||||
}
|
||||
|
||||
if (!c.getEncodedCards().isEmpty()) {
|
||||
value += addValue(24, "encoded");
|
||||
}
|
||||
|
||||
// card-specific evaluation modifier
|
||||
if (c.hasSVar("AIEvaluationModifier")) {
|
||||
int mod = AbilityUtils.calculateAmount(c, c.getSVar("AIEvaluationModifier"), null);
|
||||
value += mod;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private int evaluateSpellAbility(SpellAbility sa) {
|
||||
// Pump abilities
|
||||
if (sa.getApi() == ApiType.Pump) {
|
||||
// Pump abilities that grant +X/+X to the card
|
||||
if ("+X".equals(sa.getParam("NumAtt"))
|
||||
&& "+X".equals(sa.getParam("NumDef"))
|
||||
&& !sa.usesTargeting()
|
||||
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||
// Electrostatic Pummeler, can be expanded for similar cards
|
||||
int initPower = getEffectivePower(sa.getHostCard());
|
||||
int pumpedPower = initPower;
|
||||
int energy = sa.getHostCard().getController().getCounters(CounterType.ENERGY);
|
||||
if (energy > 0) {
|
||||
int numActivations = energy / 3;
|
||||
for (int i = 0; i < numActivations; i++) {
|
||||
pumpedPower *= 2;
|
||||
}
|
||||
return (pumpedPower - initPower) * 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default value
|
||||
return 10;
|
||||
}
|
||||
|
||||
protected int addValue(int value, String text) {
|
||||
return value;
|
||||
}
|
||||
protected int subValue(int value, String text) {
|
||||
return -addValue(-value, text);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,71 +1,71 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import forge.LobbyPlayer;
|
||||
import forge.game.Game;
|
||||
import forge.game.player.IGameEntitiesFactory;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController;
|
||||
|
||||
public class LobbyPlayerAi extends LobbyPlayer implements IGameEntitiesFactory {
|
||||
|
||||
private String aiProfile = "";
|
||||
private boolean rotateProfileEachGame;
|
||||
private boolean allowCheatShuffle;
|
||||
private boolean useSimulation;
|
||||
|
||||
public LobbyPlayerAi(String name, Set<AIOption> options) {
|
||||
super(name);
|
||||
if (options != null && options.contains(AIOption.USE_SIMULATION)) {
|
||||
this.useSimulation = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAllowCheatShuffle() {
|
||||
return allowCheatShuffle;
|
||||
}
|
||||
|
||||
public void setAllowCheatShuffle(boolean allowCheatShuffle) {
|
||||
this.allowCheatShuffle = allowCheatShuffle;
|
||||
}
|
||||
|
||||
public void setAiProfile(String profileName) {
|
||||
aiProfile = profileName;
|
||||
}
|
||||
|
||||
public String getAiProfile() {
|
||||
return aiProfile;
|
||||
}
|
||||
|
||||
public void setRotateProfileEachGame(boolean rotateProfileEachGame) {
|
||||
this.rotateProfileEachGame = rotateProfileEachGame;
|
||||
}
|
||||
|
||||
private PlayerControllerAi createControllerFor(Player ai) {
|
||||
PlayerControllerAi result = new PlayerControllerAi(ai.getGame(), ai, this);
|
||||
result.setUseSimulation(useSimulation);
|
||||
result.allowCheatShuffle(allowCheatShuffle);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerController createMindSlaveController(Player master, Player slave) {
|
||||
return createControllerFor(slave);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player createIngamePlayer(Game game, final int id) {
|
||||
Player ai = new Player(getName(), game, id);
|
||||
ai.setFirstController(createControllerFor(ai));
|
||||
|
||||
if (rotateProfileEachGame) {
|
||||
setAiProfile(AiProfileUtil.getRandomProfile());
|
||||
/*System.out.println(String.format("AI profile %s was chosen for the lobby player %s.", getAiProfile(), getName()));*/
|
||||
}
|
||||
return ai;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hear(LobbyPlayer player, String message) { /* Local AI is deaf. */ }
|
||||
package forge.ai;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import forge.LobbyPlayer;
|
||||
import forge.game.Game;
|
||||
import forge.game.player.IGameEntitiesFactory;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController;
|
||||
|
||||
public class LobbyPlayerAi extends LobbyPlayer implements IGameEntitiesFactory {
|
||||
|
||||
private String aiProfile = "";
|
||||
private boolean rotateProfileEachGame;
|
||||
private boolean allowCheatShuffle;
|
||||
private boolean useSimulation;
|
||||
|
||||
public LobbyPlayerAi(String name, Set<AIOption> options) {
|
||||
super(name);
|
||||
if (options != null && options.contains(AIOption.USE_SIMULATION)) {
|
||||
this.useSimulation = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAllowCheatShuffle() {
|
||||
return allowCheatShuffle;
|
||||
}
|
||||
|
||||
public void setAllowCheatShuffle(boolean allowCheatShuffle) {
|
||||
this.allowCheatShuffle = allowCheatShuffle;
|
||||
}
|
||||
|
||||
public void setAiProfile(String profileName) {
|
||||
aiProfile = profileName;
|
||||
}
|
||||
|
||||
public String getAiProfile() {
|
||||
return aiProfile;
|
||||
}
|
||||
|
||||
public void setRotateProfileEachGame(boolean rotateProfileEachGame) {
|
||||
this.rotateProfileEachGame = rotateProfileEachGame;
|
||||
}
|
||||
|
||||
private PlayerControllerAi createControllerFor(Player ai) {
|
||||
PlayerControllerAi result = new PlayerControllerAi(ai.getGame(), ai, this);
|
||||
result.setUseSimulation(useSimulation);
|
||||
result.allowCheatShuffle(allowCheatShuffle);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerController createMindSlaveController(Player master, Player slave) {
|
||||
return createControllerFor(slave);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player createIngamePlayer(Game game, final int id) {
|
||||
Player ai = new Player(getName(), game, id);
|
||||
ai.setFirstController(createControllerFor(ai));
|
||||
|
||||
if (rotateProfileEachGame) {
|
||||
setAiProfile(AiProfileUtil.getRandomProfile());
|
||||
/*System.out.println(String.format("AI profile %s was chosen for the lobby player %s.", getAiProfile(), getName()));*/
|
||||
}
|
||||
return ai;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hear(LobbyPlayer player, String message) { /* Local AI is deaf. */ }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,365 +1,365 @@
|
||||
package forge.ai;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.card.ICardFace;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostParser;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerController.BinaryChoiceType;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityCondition;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Base class for API-specific AI logic
|
||||
* <p>
|
||||
* The three main methods are canPlayAI(), chkAIDrawback and doTriggerAINoCost.
|
||||
*/
|
||||
public abstract class SpellAbilityAi {
|
||||
|
||||
public final boolean canPlayAIWithSubs(final Player aiPlayer, final SpellAbility sa) {
|
||||
if (!canPlayAI(aiPlayer, sa)) {
|
||||
return false;
|
||||
}
|
||||
final AbilitySub subAb = sa.getSubAbility();
|
||||
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the AI decision to play a "main" SpellAbility
|
||||
*/
|
||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (sa.getRestrictions() != null && !sa.getRestrictions().canPlay(source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return canPlayWithoutRestrict(ai, sa);
|
||||
}
|
||||
|
||||
protected boolean canPlayWithoutRestrict(final Player ai, final SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
if (!checkConditions(ai, sa, sa.getConditions())) {
|
||||
SpellAbility sub = sa.getSubAbility();
|
||||
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("AILogic")) {
|
||||
final String logic = sa.getParam("AILogic");
|
||||
if (!checkAiLogic(ai, sa, logic)) {
|
||||
return false;
|
||||
}
|
||||
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler(), logic)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
|
||||
return false;
|
||||
}
|
||||
return checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
|
||||
// copy it to disable some checks that the AI need to check extra
|
||||
con = (SpellAbilityCondition) con.copy();
|
||||
|
||||
// if manaspent, check if AI can pay the colored mana as cost
|
||||
if (!con.getManaSpent().isEmpty()) {
|
||||
// need to use ManaCostBeingPaid check, can't use Cost#canPay
|
||||
ManaCostBeingPaid paid = new ManaCostBeingPaid(new ManaCost(new ManaCostParser(con.getManaSpent())));
|
||||
if (ComputerUtilMana.canPayManaCost(paid, sa, ai)) {
|
||||
con.setManaSpent("");
|
||||
}
|
||||
}
|
||||
|
||||
return con.areMet(sa);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility with the specified AiLogic
|
||||
*/
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
if (aiLogic.equals("CheckCondition")) {
|
||||
SpellAbility saCopy = sa.copy();
|
||||
saCopy.setActivatingPlayer(ai);
|
||||
return saCopy.getConditions().areMet(saCopy);
|
||||
}
|
||||
|
||||
return !("Never".equals(aiLogic));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI is willing to pay for additional costs
|
||||
* <p>
|
||||
* Evaluated costs are: life, discard, sacrifice and counter-removal
|
||||
*/
|
||||
protected boolean willPayCosts(final Player ai, final SpellAbility sa, final Cost cost, final Card source) {
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility based on its phase restrictions
|
||||
*/
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph,
|
||||
final String logic) {
|
||||
return checkPhaseRestrictions(ai, sa, ph);
|
||||
}
|
||||
/**
|
||||
* The rest of the logic not covered by the canPlayAI template is defined here
|
||||
*/
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false; // prevent infinite loop
|
||||
}
|
||||
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
||||
}
|
||||
|
||||
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// a mandatory SpellAbility with targeting but without candidates,
|
||||
// does not need to go any deeper
|
||||
if (sa.usesTargeting() && mandatory && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
|
||||
}
|
||||
|
||||
public final boolean doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory)
|
||||
{
|
||||
if (!doTriggerAINoCost(aiPlayer, sa, mandatory)) {
|
||||
return false;
|
||||
}
|
||||
final AbilitySub subAb = sa.getSubAbility();
|
||||
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb) || mandatory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the AI decision to play a triggered SpellAbility
|
||||
*/
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
if (canPlayWithoutRestrict(aiPlayer, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// not mandatory, short way out
|
||||
if (!mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// invalid target might prevent it
|
||||
if (sa.usesTargeting()) {
|
||||
// make list of players it does try to target
|
||||
List<Player> players = Lists.newArrayList();
|
||||
players.addAll(aiPlayer.getOpponents());
|
||||
players.addAll(aiPlayer.getAllies());
|
||||
players.add(aiPlayer);
|
||||
|
||||
// try to target opponent, then ally, then itself
|
||||
for (final Player p : players) {
|
||||
if (p.canBeTargetedBy(sa) && sa.canTarget(p)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(p);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the AI decision to play a sub-SpellAbility
|
||||
*/
|
||||
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||
// sub-SpellAbility might use targets too
|
||||
if (sa.usesTargeting()) {
|
||||
// no Candidates, no adding to Stack
|
||||
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||
return false;
|
||||
}
|
||||
// but if it does, it should override this function
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chkAIDrawback is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isSorcerySpeed.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected static boolean isSorcerySpeed(final SpellAbility sa) {
|
||||
return (sa.isSpell() && sa.getHostCard().isSorcery())
|
||||
|| (sa.isAbility() && sa.getRestrictions().isSorcerySpeed())
|
||||
|| (sa.getRestrictions().isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed."));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* playReusable.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected static boolean playReusable(final Player ai, final SpellAbility sa) {
|
||||
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
||||
|
||||
// TODO probably also consider if winter orb or similar are out
|
||||
|
||||
if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
|
||||
return true; // This is only true for Drawbacks and triggers
|
||||
}
|
||||
|
||||
if (!sa.getPayCosts().isReusuableResource()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sa.getRestrictions().isPwAbility() && phase.is(PhaseType.MAIN2)) {
|
||||
return true;
|
||||
}
|
||||
if (sa.isSpell() && !sa.isBuyBackAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this method.
|
||||
* @param aiPlayer
|
||||
* @param ab
|
||||
* @return
|
||||
*/
|
||||
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
|
||||
final AbilitySub subAb = ab.getSubAbility();
|
||||
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
|
||||
}
|
||||
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
|
||||
boolean hasPlayer = false;
|
||||
boolean hasCard = false;
|
||||
boolean hasPlaneswalker = false;
|
||||
|
||||
for (T ent : options) {
|
||||
if (ent instanceof Player) {
|
||||
hasPlayer = true;
|
||||
} else if (ent instanceof Card) {
|
||||
hasCard = true;
|
||||
if (((Card)ent).isPlaneswalker()) {
|
||||
hasPlaneswalker = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPlayer && hasPlaneswalker) {
|
||||
return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options);
|
||||
} else if (hasCard) {
|
||||
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
|
||||
} else if (hasPlayer) {
|
||||
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleSpellAbility is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return spells.get(0);
|
||||
}
|
||||
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleCard is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayer is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayerOrPlaneswalker is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseCardName is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
|
||||
final ICardFace face = Iterables.getFirst(faces, null);
|
||||
return face == null ? "" : face.getName();
|
||||
}
|
||||
|
||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||
return max;
|
||||
}
|
||||
|
||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
|
||||
return MyRandom.getRandom().nextBoolean();
|
||||
}
|
||||
}
|
||||
package forge.ai;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.card.ICardFace;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostParser;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerController.BinaryChoiceType;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityCondition;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Base class for API-specific AI logic
|
||||
* <p>
|
||||
* The three main methods are canPlayAI(), chkAIDrawback and doTriggerAINoCost.
|
||||
*/
|
||||
public abstract class SpellAbilityAi {
|
||||
|
||||
public final boolean canPlayAIWithSubs(final Player aiPlayer, final SpellAbility sa) {
|
||||
if (!canPlayAI(aiPlayer, sa)) {
|
||||
return false;
|
||||
}
|
||||
final AbilitySub subAb = sa.getSubAbility();
|
||||
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the AI decision to play a "main" SpellAbility
|
||||
*/
|
||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (sa.getRestrictions() != null && !sa.getRestrictions().canPlay(source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return canPlayWithoutRestrict(ai, sa);
|
||||
}
|
||||
|
||||
protected boolean canPlayWithoutRestrict(final Player ai, final SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
if (!checkConditions(ai, sa, sa.getConditions())) {
|
||||
SpellAbility sub = sa.getSubAbility();
|
||||
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("AILogic")) {
|
||||
final String logic = sa.getParam("AILogic");
|
||||
if (!checkAiLogic(ai, sa, logic)) {
|
||||
return false;
|
||||
}
|
||||
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler(), logic)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
|
||||
return false;
|
||||
}
|
||||
return checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
|
||||
// copy it to disable some checks that the AI need to check extra
|
||||
con = (SpellAbilityCondition) con.copy();
|
||||
|
||||
// if manaspent, check if AI can pay the colored mana as cost
|
||||
if (!con.getManaSpent().isEmpty()) {
|
||||
// need to use ManaCostBeingPaid check, can't use Cost#canPay
|
||||
ManaCostBeingPaid paid = new ManaCostBeingPaid(new ManaCost(new ManaCostParser(con.getManaSpent())));
|
||||
if (ComputerUtilMana.canPayManaCost(paid, sa, ai)) {
|
||||
con.setManaSpent("");
|
||||
}
|
||||
}
|
||||
|
||||
return con.areMet(sa);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility with the specified AiLogic
|
||||
*/
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
if (aiLogic.equals("CheckCondition")) {
|
||||
SpellAbility saCopy = sa.copy();
|
||||
saCopy.setActivatingPlayer(ai);
|
||||
return saCopy.getConditions().areMet(saCopy);
|
||||
}
|
||||
|
||||
return !("Never".equals(aiLogic));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI is willing to pay for additional costs
|
||||
* <p>
|
||||
* Evaluated costs are: life, discard, sacrifice and counter-removal
|
||||
*/
|
||||
protected boolean willPayCosts(final Player ai, final SpellAbility sa, final Cost cost, final Card source) {
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility based on its phase restrictions
|
||||
*/
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph,
|
||||
final String logic) {
|
||||
return checkPhaseRestrictions(ai, sa, ph);
|
||||
}
|
||||
/**
|
||||
* The rest of the logic not covered by the canPlayAI template is defined here
|
||||
*/
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false; // prevent infinite loop
|
||||
}
|
||||
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
||||
}
|
||||
|
||||
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// a mandatory SpellAbility with targeting but without candidates,
|
||||
// does not need to go any deeper
|
||||
if (sa.usesTargeting() && mandatory && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
|
||||
}
|
||||
|
||||
public final boolean doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory)
|
||||
{
|
||||
if (!doTriggerAINoCost(aiPlayer, sa, mandatory)) {
|
||||
return false;
|
||||
}
|
||||
final AbilitySub subAb = sa.getSubAbility();
|
||||
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb) || mandatory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the AI decision to play a triggered SpellAbility
|
||||
*/
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
if (canPlayWithoutRestrict(aiPlayer, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// not mandatory, short way out
|
||||
if (!mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// invalid target might prevent it
|
||||
if (sa.usesTargeting()) {
|
||||
// make list of players it does try to target
|
||||
List<Player> players = Lists.newArrayList();
|
||||
players.addAll(aiPlayer.getOpponents());
|
||||
players.addAll(aiPlayer.getAllies());
|
||||
players.add(aiPlayer);
|
||||
|
||||
// try to target opponent, then ally, then itself
|
||||
for (final Player p : players) {
|
||||
if (p.canBeTargetedBy(sa) && sa.canTarget(p)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(p);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the AI decision to play a sub-SpellAbility
|
||||
*/
|
||||
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||
// sub-SpellAbility might use targets too
|
||||
if (sa.usesTargeting()) {
|
||||
// no Candidates, no adding to Stack
|
||||
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||
return false;
|
||||
}
|
||||
// but if it does, it should override this function
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chkAIDrawback is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isSorcerySpeed.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected static boolean isSorcerySpeed(final SpellAbility sa) {
|
||||
return (sa.isSpell() && sa.getHostCard().isSorcery())
|
||||
|| (sa.isAbility() && sa.getRestrictions().isSorcerySpeed())
|
||||
|| (sa.getRestrictions().isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed."));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* playReusable.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected static boolean playReusable(final Player ai, final SpellAbility sa) {
|
||||
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
||||
|
||||
// TODO probably also consider if winter orb or similar are out
|
||||
|
||||
if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
|
||||
return true; // This is only true for Drawbacks and triggers
|
||||
}
|
||||
|
||||
if (!sa.getPayCosts().isReusuableResource()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sa.getRestrictions().isPwAbility() && phase.is(PhaseType.MAIN2)) {
|
||||
return true;
|
||||
}
|
||||
if (sa.isSpell() && !sa.isBuyBackAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this method.
|
||||
* @param aiPlayer
|
||||
* @param ab
|
||||
* @return
|
||||
*/
|
||||
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
|
||||
final AbilitySub subAb = ab.getSubAbility();
|
||||
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
|
||||
}
|
||||
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
|
||||
boolean hasPlayer = false;
|
||||
boolean hasCard = false;
|
||||
boolean hasPlaneswalker = false;
|
||||
|
||||
for (T ent : options) {
|
||||
if (ent instanceof Player) {
|
||||
hasPlayer = true;
|
||||
} else if (ent instanceof Card) {
|
||||
hasCard = true;
|
||||
if (((Card)ent).isPlaneswalker()) {
|
||||
hasPlaneswalker = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPlayer && hasPlaneswalker) {
|
||||
return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options);
|
||||
} else if (hasCard) {
|
||||
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
|
||||
} else if (hasPlayer) {
|
||||
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleSpellAbility is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return spells.get(0);
|
||||
}
|
||||
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleCard is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayer is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayerOrPlaneswalker is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseCardName is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
|
||||
final ICardFace face = Iterables.getFirst(faces, null);
|
||||
return face == null ? "" : face.getName();
|
||||
}
|
||||
|
||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||
return max;
|
||||
}
|
||||
|
||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
|
||||
return MyRandom.getRandom().nextBoolean();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,174 +1,174 @@
|
||||
package forge.ai;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.ai.ability.*;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.util.ReflectionUtil;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public enum SpellApiToAi {
|
||||
Converter;
|
||||
|
||||
private final Map<ApiType, SpellAbilityAi> apiToInstance = Maps.newEnumMap(ApiType.class);
|
||||
|
||||
// Do the extra copy to make an actual EnumMap (faster)
|
||||
private final Map<ApiType, Class<? extends SpellAbilityAi>> apiToClass = Maps.newEnumMap(ImmutableMap
|
||||
.<ApiType, Class<? extends SpellAbilityAi>>builder()
|
||||
.put(ApiType.Abandon, AlwaysPlayAi.class)
|
||||
.put(ApiType.ActivateAbility, ActivateAbilityAi.class)
|
||||
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
|
||||
.put(ApiType.AddPhase, AddPhaseAi.class)
|
||||
.put(ApiType.AddTurn, AddTurnAi.class)
|
||||
.put(ApiType.Animate, AnimateAi.class)
|
||||
.put(ApiType.AnimateAll, AnimateAllAi.class)
|
||||
.put(ApiType.Attach, AttachAi.class)
|
||||
.put(ApiType.Balance, BalanceAi.class)
|
||||
.put(ApiType.BecomeMonarch, AlwaysPlayAi.class)
|
||||
.put(ApiType.BecomesBlocked, BecomesBlockedAi.class)
|
||||
.put(ApiType.BidLife, BidLifeAi.class)
|
||||
.put(ApiType.Bond, BondAi.class)
|
||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||
.put(ApiType.ChangeCombatants, CannotPlayAi.class)
|
||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||
.put(ApiType.ChangeZone, ChangeZoneAi.class)
|
||||
.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class)
|
||||
.put(ApiType.Charm, CharmAi.class)
|
||||
.put(ApiType.ChooseCard, ChooseCardAi.class)
|
||||
.put(ApiType.ChooseColor, ChooseColorAi.class)
|
||||
.put(ApiType.ChooseDirection, ChooseDirectionAi.class)
|
||||
.put(ApiType.ChooseNumber, ChooseNumberAi.class)
|
||||
.put(ApiType.ChoosePlayer, ChoosePlayerAi.class)
|
||||
.put(ApiType.ChooseSource, ChooseSourceAi.class)
|
||||
.put(ApiType.ChooseType, ChooseTypeAi.class)
|
||||
.put(ApiType.Clash, ClashAi.class)
|
||||
.put(ApiType.Cleanup, AlwaysPlayAi.class)
|
||||
.put(ApiType.Clone, CloneAi.class)
|
||||
.put(ApiType.CopyPermanent, CopyPermanentAi.class)
|
||||
.put(ApiType.CopySpellAbility, CopySpellAbilityAi.class)
|
||||
.put(ApiType.ControlPlayer, CannotPlayAi.class)
|
||||
.put(ApiType.ControlSpell, CannotPlayAi.class)
|
||||
.put(ApiType.Counter, CounterAi.class)
|
||||
.put(ApiType.DamageAll, DamageAllAi.class)
|
||||
.put(ApiType.DealDamage, DamageDealAi.class)
|
||||
.put(ApiType.Debuff, DebuffAi.class)
|
||||
.put(ApiType.DeclareCombatants, CannotPlayAi.class)
|
||||
.put(ApiType.DelayedTrigger, DelayedTriggerAi.class)
|
||||
.put(ApiType.Destroy, DestroyAi.class)
|
||||
.put(ApiType.DestroyAll, DestroyAllAi.class)
|
||||
.put(ApiType.Dig, DigAi.class)
|
||||
.put(ApiType.DigUntil, DigUntilAi.class)
|
||||
.put(ApiType.Discard, DiscardAi.class)
|
||||
.put(ApiType.DrainMana, DrainManaAi.class)
|
||||
.put(ApiType.Draw, DrawAi.class)
|
||||
.put(ApiType.EachDamage, DamageEachAi.class)
|
||||
.put(ApiType.Effect, EffectAi.class)
|
||||
.put(ApiType.Encode, EncodeAi.class)
|
||||
.put(ApiType.EndTurn, EndTurnAi.class)
|
||||
.put(ApiType.ExchangeLife, LifeExchangeAi.class)
|
||||
.put(ApiType.ExchangeControl, ControlExchangeAi.class)
|
||||
.put(ApiType.ExchangeControlVariant, CannotPlayAi.class)
|
||||
.put(ApiType.ExchangePower, PowerExchangeAi.class)
|
||||
.put(ApiType.ExchangeZone, ZoneExchangeAi.class)
|
||||
.put(ApiType.Explore, ExploreAi.class)
|
||||
.put(ApiType.Fight, FightAi.class)
|
||||
.put(ApiType.FlipACoin, FlipACoinAi.class)
|
||||
.put(ApiType.Fog, FogAi.class)
|
||||
.put(ApiType.GainControl, ControlGainAi.class)
|
||||
.put(ApiType.GainLife, LifeGainAi.class)
|
||||
.put(ApiType.GainOwnership, CannotPlayAi.class)
|
||||
.put(ApiType.GameDrawn, CannotPlayAi.class)
|
||||
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
|
||||
.put(ApiType.Goad, GoadAi.class)
|
||||
.put(ApiType.Haunt, HauntAi.class)
|
||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||
.put(ApiType.LosesGame, GameLossAi.class)
|
||||
.put(ApiType.Mana, ManaEffectAi.class)
|
||||
.put(ApiType.ManaReflected, CannotPlayAi.class)
|
||||
.put(ApiType.Manifest, ManifestAi.class)
|
||||
.put(ApiType.Meld, MeldAi.class)
|
||||
.put(ApiType.Mill, MillAi.class)
|
||||
.put(ApiType.MoveCounter, CountersMoveAi.class)
|
||||
.put(ApiType.MultiplePiles, CannotPlayAi.class)
|
||||
.put(ApiType.MultiplyCounter, CountersMultiplyAi.class)
|
||||
.put(ApiType.MustAttack, MustAttackAi.class)
|
||||
.put(ApiType.MustBlock, MustBlockAi.class)
|
||||
.put(ApiType.NameCard, ChooseCardNameAi.class)
|
||||
.put(ApiType.NoteCounters, AlwaysPlayAi.class)
|
||||
.put(ApiType.PeekAndReveal, PeekAndRevealAi.class)
|
||||
.put(ApiType.PermanentCreature, PermanentCreatureAi.class)
|
||||
.put(ApiType.PermanentNoncreature, PermanentNoncreatureAi.class)
|
||||
.put(ApiType.Phases, PhasesAi.class)
|
||||
.put(ApiType.Planeswalk, AlwaysPlayAi.class)
|
||||
.put(ApiType.Play, PlayAi.class)
|
||||
.put(ApiType.PlayLandVariant, CannotPlayAi.class)
|
||||
.put(ApiType.Poison, PoisonAi.class)
|
||||
.put(ApiType.PreventDamage, DamagePreventAi.class)
|
||||
.put(ApiType.PreventDamageAll, DamagePreventAllAi.class)
|
||||
.put(ApiType.Proliferate, CountersProliferateAi.class)
|
||||
.put(ApiType.Protection, ProtectAi.class)
|
||||
.put(ApiType.ProtectionAll, ProtectAllAi.class)
|
||||
.put(ApiType.Pump, PumpAi.class)
|
||||
.put(ApiType.PumpAll, PumpAllAi.class)
|
||||
.put(ApiType.PutCounter, CountersPutAi.class)
|
||||
.put(ApiType.PutCounterAll, CountersPutAllAi.class)
|
||||
.put(ApiType.RearrangeTopOfLibrary, RearrangeTopOfLibraryAi.class)
|
||||
.put(ApiType.Regenerate, RegenerateAi.class)
|
||||
.put(ApiType.RegenerateAll, RegenerateAllAi.class)
|
||||
.put(ApiType.RemoveCounter, CountersRemoveAi.class)
|
||||
.put(ApiType.RemoveCounterAll, CannotPlayAi.class)
|
||||
.put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class)
|
||||
.put(ApiType.ReorderZone, AlwaysPlayAi.class)
|
||||
.put(ApiType.Repeat, RepeatAi.class)
|
||||
.put(ApiType.RepeatEach, RepeatEachAi.class)
|
||||
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
|
||||
.put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class)
|
||||
.put(ApiType.RestartGame, RestartGameAi.class)
|
||||
.put(ApiType.Reveal, RevealAi.class)
|
||||
.put(ApiType.RevealHand, RevealHandAi.class)
|
||||
.put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class)
|
||||
.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class)
|
||||
.put(ApiType.RunSVarAbility, AlwaysPlayAi.class)
|
||||
.put(ApiType.Sacrifice, SacrificeAi.class)
|
||||
.put(ApiType.SacrificeAll, SacrificeAllAi.class)
|
||||
.put(ApiType.Scry, ScryAi.class)
|
||||
.put(ApiType.SetInMotion, AlwaysPlayAi.class)
|
||||
.put(ApiType.SetLife, LifeSetAi.class)
|
||||
.put(ApiType.SetState, SetStateAi.class)
|
||||
.put(ApiType.Shuffle, ShuffleAi.class)
|
||||
.put(ApiType.SkipTurn, SkipTurnAi.class)
|
||||
.put(ApiType.StoreMap, StoreMapAi.class)
|
||||
.put(ApiType.StoreSVar, StoreSVarAi.class)
|
||||
.put(ApiType.Tap, TapAi.class)
|
||||
.put(ApiType.TapAll, TapAllAi.class)
|
||||
.put(ApiType.TapOrUntap, TapOrUntapAi.class)
|
||||
.put(ApiType.TapOrUntapAll, TapOrUntapAllAi.class)
|
||||
.put(ApiType.Token, TokenAi.class)
|
||||
.put(ApiType.TwoPiles, TwoPilesAi.class)
|
||||
.put(ApiType.Unattach, CannotPlayAi.class)
|
||||
.put(ApiType.UnattachAll, UnattachAllAi.class)
|
||||
.put(ApiType.Untap, UntapAi.class)
|
||||
.put(ApiType.UntapAll, UntapAllAi.class)
|
||||
.put(ApiType.Vote, VoteAi.class)
|
||||
.put(ApiType.WinsGame, GameWinAi.class)
|
||||
|
||||
.put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class)
|
||||
.put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class)
|
||||
.put(ApiType.InternalIgnoreEffect, CannotPlayAi.class)
|
||||
.build());
|
||||
|
||||
public SpellAbilityAi get(final ApiType api) {
|
||||
SpellAbilityAi result = apiToInstance.get(api);
|
||||
if (null == result) {
|
||||
Class<? extends SpellAbilityAi> clz = apiToClass.get(api);
|
||||
if (null == clz) {
|
||||
System.err.println("No AI assigned for API: " + api);
|
||||
clz = CannotPlayAi.class;
|
||||
}
|
||||
result = ReflectionUtil.makeDefaultInstanceOf(clz);
|
||||
apiToInstance.put(api, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
package forge.ai;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.ai.ability.*;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.util.ReflectionUtil;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public enum SpellApiToAi {
|
||||
Converter;
|
||||
|
||||
private final Map<ApiType, SpellAbilityAi> apiToInstance = Maps.newEnumMap(ApiType.class);
|
||||
|
||||
// Do the extra copy to make an actual EnumMap (faster)
|
||||
private final Map<ApiType, Class<? extends SpellAbilityAi>> apiToClass = Maps.newEnumMap(ImmutableMap
|
||||
.<ApiType, Class<? extends SpellAbilityAi>>builder()
|
||||
.put(ApiType.Abandon, AlwaysPlayAi.class)
|
||||
.put(ApiType.ActivateAbility, ActivateAbilityAi.class)
|
||||
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
|
||||
.put(ApiType.AddPhase, AddPhaseAi.class)
|
||||
.put(ApiType.AddTurn, AddTurnAi.class)
|
||||
.put(ApiType.Animate, AnimateAi.class)
|
||||
.put(ApiType.AnimateAll, AnimateAllAi.class)
|
||||
.put(ApiType.Attach, AttachAi.class)
|
||||
.put(ApiType.Balance, BalanceAi.class)
|
||||
.put(ApiType.BecomeMonarch, AlwaysPlayAi.class)
|
||||
.put(ApiType.BecomesBlocked, BecomesBlockedAi.class)
|
||||
.put(ApiType.BidLife, BidLifeAi.class)
|
||||
.put(ApiType.Bond, BondAi.class)
|
||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||
.put(ApiType.ChangeCombatants, CannotPlayAi.class)
|
||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||
.put(ApiType.ChangeZone, ChangeZoneAi.class)
|
||||
.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class)
|
||||
.put(ApiType.Charm, CharmAi.class)
|
||||
.put(ApiType.ChooseCard, ChooseCardAi.class)
|
||||
.put(ApiType.ChooseColor, ChooseColorAi.class)
|
||||
.put(ApiType.ChooseDirection, ChooseDirectionAi.class)
|
||||
.put(ApiType.ChooseNumber, ChooseNumberAi.class)
|
||||
.put(ApiType.ChoosePlayer, ChoosePlayerAi.class)
|
||||
.put(ApiType.ChooseSource, ChooseSourceAi.class)
|
||||
.put(ApiType.ChooseType, ChooseTypeAi.class)
|
||||
.put(ApiType.Clash, ClashAi.class)
|
||||
.put(ApiType.Cleanup, AlwaysPlayAi.class)
|
||||
.put(ApiType.Clone, CloneAi.class)
|
||||
.put(ApiType.CopyPermanent, CopyPermanentAi.class)
|
||||
.put(ApiType.CopySpellAbility, CopySpellAbilityAi.class)
|
||||
.put(ApiType.ControlPlayer, CannotPlayAi.class)
|
||||
.put(ApiType.ControlSpell, CannotPlayAi.class)
|
||||
.put(ApiType.Counter, CounterAi.class)
|
||||
.put(ApiType.DamageAll, DamageAllAi.class)
|
||||
.put(ApiType.DealDamage, DamageDealAi.class)
|
||||
.put(ApiType.Debuff, DebuffAi.class)
|
||||
.put(ApiType.DeclareCombatants, CannotPlayAi.class)
|
||||
.put(ApiType.DelayedTrigger, DelayedTriggerAi.class)
|
||||
.put(ApiType.Destroy, DestroyAi.class)
|
||||
.put(ApiType.DestroyAll, DestroyAllAi.class)
|
||||
.put(ApiType.Dig, DigAi.class)
|
||||
.put(ApiType.DigUntil, DigUntilAi.class)
|
||||
.put(ApiType.Discard, DiscardAi.class)
|
||||
.put(ApiType.DrainMana, DrainManaAi.class)
|
||||
.put(ApiType.Draw, DrawAi.class)
|
||||
.put(ApiType.EachDamage, DamageEachAi.class)
|
||||
.put(ApiType.Effect, EffectAi.class)
|
||||
.put(ApiType.Encode, EncodeAi.class)
|
||||
.put(ApiType.EndTurn, EndTurnAi.class)
|
||||
.put(ApiType.ExchangeLife, LifeExchangeAi.class)
|
||||
.put(ApiType.ExchangeControl, ControlExchangeAi.class)
|
||||
.put(ApiType.ExchangeControlVariant, CannotPlayAi.class)
|
||||
.put(ApiType.ExchangePower, PowerExchangeAi.class)
|
||||
.put(ApiType.ExchangeZone, ZoneExchangeAi.class)
|
||||
.put(ApiType.Explore, ExploreAi.class)
|
||||
.put(ApiType.Fight, FightAi.class)
|
||||
.put(ApiType.FlipACoin, FlipACoinAi.class)
|
||||
.put(ApiType.Fog, FogAi.class)
|
||||
.put(ApiType.GainControl, ControlGainAi.class)
|
||||
.put(ApiType.GainLife, LifeGainAi.class)
|
||||
.put(ApiType.GainOwnership, CannotPlayAi.class)
|
||||
.put(ApiType.GameDrawn, CannotPlayAi.class)
|
||||
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
|
||||
.put(ApiType.Goad, GoadAi.class)
|
||||
.put(ApiType.Haunt, HauntAi.class)
|
||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||
.put(ApiType.LosesGame, GameLossAi.class)
|
||||
.put(ApiType.Mana, ManaEffectAi.class)
|
||||
.put(ApiType.ManaReflected, CannotPlayAi.class)
|
||||
.put(ApiType.Manifest, ManifestAi.class)
|
||||
.put(ApiType.Meld, MeldAi.class)
|
||||
.put(ApiType.Mill, MillAi.class)
|
||||
.put(ApiType.MoveCounter, CountersMoveAi.class)
|
||||
.put(ApiType.MultiplePiles, CannotPlayAi.class)
|
||||
.put(ApiType.MultiplyCounter, CountersMultiplyAi.class)
|
||||
.put(ApiType.MustAttack, MustAttackAi.class)
|
||||
.put(ApiType.MustBlock, MustBlockAi.class)
|
||||
.put(ApiType.NameCard, ChooseCardNameAi.class)
|
||||
.put(ApiType.NoteCounters, AlwaysPlayAi.class)
|
||||
.put(ApiType.PeekAndReveal, PeekAndRevealAi.class)
|
||||
.put(ApiType.PermanentCreature, PermanentCreatureAi.class)
|
||||
.put(ApiType.PermanentNoncreature, PermanentNoncreatureAi.class)
|
||||
.put(ApiType.Phases, PhasesAi.class)
|
||||
.put(ApiType.Planeswalk, AlwaysPlayAi.class)
|
||||
.put(ApiType.Play, PlayAi.class)
|
||||
.put(ApiType.PlayLandVariant, CannotPlayAi.class)
|
||||
.put(ApiType.Poison, PoisonAi.class)
|
||||
.put(ApiType.PreventDamage, DamagePreventAi.class)
|
||||
.put(ApiType.PreventDamageAll, DamagePreventAllAi.class)
|
||||
.put(ApiType.Proliferate, CountersProliferateAi.class)
|
||||
.put(ApiType.Protection, ProtectAi.class)
|
||||
.put(ApiType.ProtectionAll, ProtectAllAi.class)
|
||||
.put(ApiType.Pump, PumpAi.class)
|
||||
.put(ApiType.PumpAll, PumpAllAi.class)
|
||||
.put(ApiType.PutCounter, CountersPutAi.class)
|
||||
.put(ApiType.PutCounterAll, CountersPutAllAi.class)
|
||||
.put(ApiType.RearrangeTopOfLibrary, RearrangeTopOfLibraryAi.class)
|
||||
.put(ApiType.Regenerate, RegenerateAi.class)
|
||||
.put(ApiType.RegenerateAll, RegenerateAllAi.class)
|
||||
.put(ApiType.RemoveCounter, CountersRemoveAi.class)
|
||||
.put(ApiType.RemoveCounterAll, CannotPlayAi.class)
|
||||
.put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class)
|
||||
.put(ApiType.ReorderZone, AlwaysPlayAi.class)
|
||||
.put(ApiType.Repeat, RepeatAi.class)
|
||||
.put(ApiType.RepeatEach, RepeatEachAi.class)
|
||||
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
|
||||
.put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class)
|
||||
.put(ApiType.RestartGame, RestartGameAi.class)
|
||||
.put(ApiType.Reveal, RevealAi.class)
|
||||
.put(ApiType.RevealHand, RevealHandAi.class)
|
||||
.put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class)
|
||||
.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class)
|
||||
.put(ApiType.RunSVarAbility, AlwaysPlayAi.class)
|
||||
.put(ApiType.Sacrifice, SacrificeAi.class)
|
||||
.put(ApiType.SacrificeAll, SacrificeAllAi.class)
|
||||
.put(ApiType.Scry, ScryAi.class)
|
||||
.put(ApiType.SetInMotion, AlwaysPlayAi.class)
|
||||
.put(ApiType.SetLife, LifeSetAi.class)
|
||||
.put(ApiType.SetState, SetStateAi.class)
|
||||
.put(ApiType.Shuffle, ShuffleAi.class)
|
||||
.put(ApiType.SkipTurn, SkipTurnAi.class)
|
||||
.put(ApiType.StoreMap, StoreMapAi.class)
|
||||
.put(ApiType.StoreSVar, StoreSVarAi.class)
|
||||
.put(ApiType.Tap, TapAi.class)
|
||||
.put(ApiType.TapAll, TapAllAi.class)
|
||||
.put(ApiType.TapOrUntap, TapOrUntapAi.class)
|
||||
.put(ApiType.TapOrUntapAll, TapOrUntapAllAi.class)
|
||||
.put(ApiType.Token, TokenAi.class)
|
||||
.put(ApiType.TwoPiles, TwoPilesAi.class)
|
||||
.put(ApiType.Unattach, CannotPlayAi.class)
|
||||
.put(ApiType.UnattachAll, UnattachAllAi.class)
|
||||
.put(ApiType.Untap, UntapAi.class)
|
||||
.put(ApiType.UntapAll, UntapAllAi.class)
|
||||
.put(ApiType.Vote, VoteAi.class)
|
||||
.put(ApiType.WinsGame, GameWinAi.class)
|
||||
|
||||
.put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class)
|
||||
.put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class)
|
||||
.put(ApiType.InternalIgnoreEffect, CannotPlayAi.class)
|
||||
.build());
|
||||
|
||||
public SpellAbilityAi get(final ApiType api) {
|
||||
SpellAbilityAi result = apiToInstance.get(api);
|
||||
if (null == result) {
|
||||
Class<? extends SpellAbilityAi> clz = apiToClass.get(api);
|
||||
if (null == clz) {
|
||||
System.err.println("No AI assigned for API: " + api);
|
||||
clz = CannotPlayAi.class;
|
||||
}
|
||||
result = ReflectionUtil.makeDefaultInstanceOf(clz);
|
||||
apiToInstance.put(api, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,101 +1,101 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// AI cannot use this properly until he can use SAs during Humans turn
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tgt == null) {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!defined.contains(opp)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (null == tgt) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
} else {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!defined.contains(opp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
// AI cannot use this properly until he can use SAs during Humans turn
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
boolean randomReturn = true;
|
||||
|
||||
if (tgt == null) {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (defined.contains(ai)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
return spells.get(0);
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// AI cannot use this properly until he can use SAs during Humans turn
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tgt == null) {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!defined.contains(opp)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (null == tgt) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
} else {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!defined.contains(opp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
// AI cannot use this properly until he can use SAs during Humans turn
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
boolean randomReturn = true;
|
||||
|
||||
if (tgt == null) {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (defined.contains(ai)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
return spells.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class AddPhaseAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class AddPhaseAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class AlwaysPlayAi extends SpellAbilityAi {
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class AlwaysPlayAi extends SpellAbilityAi {
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,19 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class AnimateAllAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
} // end animateAllCanPlayAI()
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // end class AbilityFactoryAnimate
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class AnimateAllAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
} // end animateAllCanPlayAI()
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // end class AbilityFactoryAnimate
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,48 +1,48 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class BalanceAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
String logic = sa.getParam("AILogic");
|
||||
|
||||
int diff = 0;
|
||||
// TODO Add support for multiplayer logic
|
||||
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
||||
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
if ("BalanceCreaturesAndLands".equals(logic)) {
|
||||
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
|
||||
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
|
||||
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
|
||||
diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
|
||||
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
|
||||
}
|
||||
else if ("BalancePermanents".equals(logic)) {
|
||||
// Don't cast if you have to sacrifice permanents
|
||||
diff += humPerms.size() - compPerms.size();
|
||||
}
|
||||
|
||||
if (diff < 0) {
|
||||
// Don't sacrifice permanents even if opponent has a ton of cards in hand
|
||||
return false;
|
||||
}
|
||||
|
||||
final CardCollectionView humHand = opp.getCardsIn(ZoneType.Hand);
|
||||
final CardCollectionView compHand = aiPlayer.getCardsIn(ZoneType.Hand);
|
||||
diff += 0.5 * (humHand.size() - compHand.size());
|
||||
|
||||
// Larger differential == more chance to actually cast this spell
|
||||
return diff > 2 && MyRandom.getRandom().nextInt(100) < diff*10;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class BalanceAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
String logic = sa.getParam("AILogic");
|
||||
|
||||
int diff = 0;
|
||||
// TODO Add support for multiplayer logic
|
||||
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
||||
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
if ("BalanceCreaturesAndLands".equals(logic)) {
|
||||
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
|
||||
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
|
||||
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
|
||||
diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
|
||||
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
|
||||
}
|
||||
else if ("BalancePermanents".equals(logic)) {
|
||||
// Don't cast if you have to sacrifice permanents
|
||||
diff += humPerms.size() - compPerms.size();
|
||||
}
|
||||
|
||||
if (diff < 0) {
|
||||
// Don't sacrifice permanents even if opponent has a ton of cards in hand
|
||||
return false;
|
||||
}
|
||||
|
||||
final CardCollectionView humHand = opp.getCardsIn(ZoneType.Hand);
|
||||
final CardCollectionView compHand = aiPlayer.getCardsIn(ZoneType.Hand);
|
||||
diff += 0.5 * (humHand.size() - compHand.size());
|
||||
|
||||
// Larger differential == more chance to actually cast this spell
|
||||
return diff > 2 && MyRandom.getRandom().nextInt(100) < diff*10;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class BecomesBlockedAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Game game = aiPlayer.getGame();
|
||||
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), aiPlayer.getOpponents());
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
list = CardLists.getNotKeyword(list, "Trample");
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
Card choice = null;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
return false;
|
||||
}
|
||||
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
// TODO - implement AI
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
boolean chance;
|
||||
|
||||
// TODO - implement AI
|
||||
chance = false;
|
||||
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class BecomesBlockedAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Game game = aiPlayer.getGame();
|
||||
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), aiPlayer.getOpponents());
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
list = CardLists.getNotKeyword(list, "Trample");
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
Card choice = null;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
return false;
|
||||
}
|
||||
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
// TODO - implement AI
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
boolean chance;
|
||||
|
||||
// TODO - implement AI
|
||||
chance = false;
|
||||
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class BidLifeAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = source.getGame();
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canTgtCreature()) {
|
||||
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;
|
||||
}
|
||||
Card c = ComputerUtilCard.getBestCreatureAI(list);
|
||||
if (sa.canTarget(c)) {
|
||||
sa.getTargets().add(c);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (tgt.getZone().contains(ZoneType.Stack)) {
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final SpellAbility topSA = game.getStack().peekAbility();
|
||||
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || aiPlayer.equals(topSA.getActivatingPlayer())) {
|
||||
return false;
|
||||
}
|
||||
if (sa.canTargetSpellAbility(topSA)) {
|
||||
sa.getTargets().add(topSA);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
return chance;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class BidLifeAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = source.getGame();
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canTgtCreature()) {
|
||||
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;
|
||||
}
|
||||
Card c = ComputerUtilCard.getBestCreatureAI(list);
|
||||
if (sa.canTarget(c)) {
|
||||
sa.getTargets().add(c);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (tgt.getZone().contains(ZoneType.Stack)) {
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final SpellAbility topSA = game.getStack().peekAbility();
|
||||
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || aiPlayer.equals(topSA.getActivatingPlayer())) {
|
||||
return false;
|
||||
}
|
||||
if (sa.canTargetSpellAbility(topSA)) {
|
||||
sa.getTargets().add(topSA);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
return chance;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactoryBond class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
|
||||
*/
|
||||
public final class BondAi extends SpellAbilityAi {
|
||||
/**
|
||||
* <p>
|
||||
* bondCanPlayAI.
|
||||
* </p>
|
||||
* @param aiPlayer
|
||||
* a {@link forge.game.player.Player} object.
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return true;
|
||||
} // end bondCanPlayAI()
|
||||
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
return ComputerUtilCard.getBestCreatureAI(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactoryBond class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
|
||||
*/
|
||||
public final class BondAi extends SpellAbilityAi {
|
||||
/**
|
||||
* <p>
|
||||
* bondCanPlayAI.
|
||||
* </p>
|
||||
* @param aiPlayer
|
||||
* a {@link forge.game.player.Player} object.
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return true;
|
||||
} // end bondCanPlayAI()
|
||||
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
return ComputerUtilCard.getBestCreatureAI(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* copySpellTriggerAI.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
// This might be called from CopySpellAbilityEffect - to hide warning (for having no overload) use this simple overload
|
||||
return spells.get(0);
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* copySpellTriggerAI.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
// This might be called from CopySpellAbilityEffect - to hide warning (for having no overload) use this simple overload
|
||||
return spells.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class CannotPlayAi extends SpellAbilityAi {
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class CannotPlayAi extends SpellAbilityAi {
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +1,93 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ChangeTargetsAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
final Game game = sa.getHostCard().getGame();
|
||||
final SpellAbility topSa = game.getStack().isEmpty() ? null
|
||||
: ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
||||
|
||||
if ("Self".equals(sa.getParam("DefinedMagnet"))) {
|
||||
return doSpellMagnet(sa, topSa, ai);
|
||||
}
|
||||
|
||||
// The AI can't otherwise play this ability, but should at least not
|
||||
// miss mandatory activations (e.g. triggers).
|
||||
return sa.isMandatory();
|
||||
}
|
||||
|
||||
private boolean doSpellMagnet(SpellAbility sa, SpellAbility topSa, Player aiPlayer) {
|
||||
// For cards like Spellskite that retarget spells to itself
|
||||
if (topSa == null) {
|
||||
// nothing on stack, so nothing to target
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.getTargets().getNumTargeted() != 0) {
|
||||
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!topSa.usesTargeting() || topSa.getTargets().getTargetCards().contains(sa.getHostCard())) {
|
||||
// if this does not target at all or already targets host, no need to redirect it again
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Card tgt : topSa.getTargets().getTargetCards()) {
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
|
||||
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
|
||||
// no need to retarget again to another one
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (topSa.getHostCard() != null && !topSa.getHostCard().getController().isOpponentOf(aiPlayer)) {
|
||||
// make sure not to redirect our own abilities
|
||||
return false;
|
||||
}
|
||||
if (!topSa.canTarget(sa.getHostCard())) {
|
||||
// don't try targeting it if we can't legally target the host card with it in the first place
|
||||
return false;
|
||||
}
|
||||
if (!sa.canTarget(topSa)) {
|
||||
// don't try retargeting a spell that the current card can't legally retarget (e.g. Muck Drubb + Lightning Bolt to the face)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) {
|
||||
ManaCost manaCost = sa.getPayCosts().getCostMana().getMana();
|
||||
int payDamage = manaCost.getPhyrexianCount() * 2;
|
||||
// e.g. Spellskite or a creature receiving its ability that requires Phyrexian mana P/U
|
||||
int potentialDmg = ComputerUtil.predictDamageFromSpell(topSa, aiPlayer);
|
||||
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
||||
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
||||
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
||||
&& topSa.getTargets().getTargets().contains(aiPlayer)) {
|
||||
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(topSa);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ChangeTargetsAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
final Game game = sa.getHostCard().getGame();
|
||||
final SpellAbility topSa = game.getStack().isEmpty() ? null
|
||||
: ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
||||
|
||||
if ("Self".equals(sa.getParam("DefinedMagnet"))) {
|
||||
return doSpellMagnet(sa, topSa, ai);
|
||||
}
|
||||
|
||||
// The AI can't otherwise play this ability, but should at least not
|
||||
// miss mandatory activations (e.g. triggers).
|
||||
return sa.isMandatory();
|
||||
}
|
||||
|
||||
private boolean doSpellMagnet(SpellAbility sa, SpellAbility topSa, Player aiPlayer) {
|
||||
// For cards like Spellskite that retarget spells to itself
|
||||
if (topSa == null) {
|
||||
// nothing on stack, so nothing to target
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.getTargets().getNumTargeted() != 0) {
|
||||
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!topSa.usesTargeting() || topSa.getTargets().getTargetCards().contains(sa.getHostCard())) {
|
||||
// if this does not target at all or already targets host, no need to redirect it again
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Card tgt : topSa.getTargets().getTargetCards()) {
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
|
||||
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
|
||||
// no need to retarget again to another one
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (topSa.getHostCard() != null && !topSa.getHostCard().getController().isOpponentOf(aiPlayer)) {
|
||||
// make sure not to redirect our own abilities
|
||||
return false;
|
||||
}
|
||||
if (!topSa.canTarget(sa.getHostCard())) {
|
||||
// don't try targeting it if we can't legally target the host card with it in the first place
|
||||
return false;
|
||||
}
|
||||
if (!sa.canTarget(topSa)) {
|
||||
// don't try retargeting a spell that the current card can't legally retarget (e.g. Muck Drubb + Lightning Bolt to the face)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) {
|
||||
ManaCost manaCost = sa.getPayCosts().getCostMana().getMana();
|
||||
int payDamage = manaCost.getPhyrexianCount() * 2;
|
||||
// e.g. Spellskite or a creature receiving its ability that requires Phyrexian mana P/U
|
||||
int potentialDmg = ComputerUtil.predictDamageFromSpell(topSa, aiPlayer);
|
||||
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
||||
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
||||
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
||||
&& topSa.getTargets().getTargets().contains(aiPlayer)) {
|
||||
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(topSa);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,466 +1,466 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Random;
|
||||
|
||||
public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// Change Zone All, can be any type moving from one zone to another
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final Game game = ai.getGame();
|
||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
boolean aiLogicAllowsDiscard = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("DiscardAll");
|
||||
|
||||
if (!aiLogicAllowsDiscard) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Random r = MyRandom.getRandom();
|
||||
// prevent run-away activations - first time will always return true
|
||||
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
// TODO targeting with ChangeZoneAll
|
||||
// really two types of targeting.
|
||||
// Target Player has all their types change zones
|
||||
// or target permanent and do something relative to that permanent
|
||||
// ex. "Return all Auras attached to target"
|
||||
// ex. "Return all blocking/blocked by target creature"
|
||||
|
||||
CardCollectionView oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
|
||||
CardCollectionView computerType = ai.getCardsIn(origin);
|
||||
|
||||
// Ugin check need to be done before filterListByType because of ChosenX
|
||||
// Ugin AI: always try to sweep before considering +1
|
||||
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
||||
return SpecialCardAi.UginTheSpiritDragon.considerPWAbilityPriority(ai, sa, origin, oppType, computerType);
|
||||
}
|
||||
|
||||
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
|
||||
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
||||
|
||||
if ("LivingDeath".equals(sa.getParam("AILogic"))) {
|
||||
// Living Death AI
|
||||
return SpecialCardAi.LivingDeath.consider(ai, sa);
|
||||
} else if ("Timetwister".equals(sa.getParam("AILogic"))) {
|
||||
// Timetwister AI
|
||||
return SpecialCardAi.Timetwister.consider(ai, sa);
|
||||
} else if ("RetDiscardedThisTurn".equals(sa.getParam("AILogic"))) {
|
||||
// e.g. Shadow of the Grave
|
||||
return ai.getNumDiscardedThisTurn() > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN);
|
||||
} else if ("ExileGraveyards".equals(sa.getParam("AILogic"))) {
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
CardCollectionView cardsGY = opp.getCardsIn(ZoneType.Graveyard);
|
||||
CardCollection creats = CardLists.filter(cardsGY, CardPredicates.Presets.CREATURES);
|
||||
|
||||
if (opp.hasDelirium() || opp.hasThreshold() || creats.size() >= 5) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if ("ManifestCreatsFromGraveyard".equals(sa.getParam("AILogic"))) {
|
||||
PlayerCollection players = new PlayerCollection();
|
||||
players.addAll(ai.getOpponents());
|
||||
players.add(ai);
|
||||
int maxSize = 1;
|
||||
for (Player player : players) {
|
||||
Player bestTgt = null;
|
||||
if (player.canBeTargetedBy(sa)) {
|
||||
CardCollectionView cardsGY = CardLists.filter(player.getCardsIn(ZoneType.Graveyard),
|
||||
CardPredicates.Presets.CREATURES);
|
||||
if (cardsGY.size() > maxSize) {
|
||||
maxSize = cardsGY.size();
|
||||
bestTgt = player;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestTgt != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(bestTgt);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO improve restrictions on when the AI would want to use this
|
||||
// spBounceAll has some AI we can compare to.
|
||||
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
|
||||
if (!sa.usesTargeting()) {
|
||||
// TODO: improve logic for non-targeted SAs of this type (most are currently RemAIDeck, e.g. Memory Jar)
|
||||
return true;
|
||||
} else {
|
||||
// search targetable Opponents
|
||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), PlayerPredicates.isTargetableBy(sa));
|
||||
|
||||
// get the one with the most handsize
|
||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList), PlayerPredicates.compareByZoneSize(origin));
|
||||
|
||||
// set the target
|
||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(oppTarget);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Battlefield)) {
|
||||
// this statement is assuming the AI is trying to use this spell
|
||||
// offensively
|
||||
// if the AI is using it defensively, then something else needs to
|
||||
// occur
|
||||
// if only creatures are affected evaluate both lists and pass only
|
||||
// if human creatures are more valuable
|
||||
if (sa.usesTargeting()) {
|
||||
// search targetable Opponents
|
||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
||||
PlayerPredicates.isTargetableBy(sa));
|
||||
|
||||
// get the one with the most in graveyard
|
||||
// zone is visible so evaluate which would be hurt the most
|
||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
||||
PlayerPredicates.compareByZoneSize(origin));
|
||||
|
||||
// set the target
|
||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(oppTarget);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
computerType = new CardCollection();
|
||||
}
|
||||
|
||||
int creatureEvalThreshold = 200; // value difference (in evaluateCreatureList units)
|
||||
int nonCreatureEvalThreshold = 3; // CMC difference
|
||||
if (ai.getController().isAI()) {
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
if (destination == ZoneType.Hand) {
|
||||
creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF);
|
||||
nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF);
|
||||
} else {
|
||||
creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF);
|
||||
nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF);
|
||||
}
|
||||
}
|
||||
|
||||
// mass zone change for creatures: if in dire danger, do it; otherwise, only do it if the opponent's
|
||||
// creatures are better in value
|
||||
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
|
||||
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
||||
if (game.getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
|
||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
|
||||
// Life is in serious danger, return all creatures from the battlefield to wherever
|
||||
// so they don't deal lethal damage
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ((ComputerUtilCard.evaluateCreatureList(computerType) + creatureEvalThreshold) >= ComputerUtilCard
|
||||
.evaluateCreatureList(oppType)) {
|
||||
return false;
|
||||
}
|
||||
} // mass zone change for non-creatures: evaluate both lists by CMC and pass only if human
|
||||
// permanents are more valuable
|
||||
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + nonCreatureEvalThreshold) >= ComputerUtilCard
|
||||
.evaluatePermanentList(oppType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't cast during main1?
|
||||
if (game.getPhaseHandler().is(PhaseType.MAIN1, ai)) {
|
||||
return false;
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Graveyard)) {
|
||||
if (sa.usesTargeting()) {
|
||||
// search targetable Opponents
|
||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
||||
PlayerPredicates.isTargetableBy(sa));
|
||||
|
||||
if (Iterables.isEmpty(oppList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the one with the most in graveyard
|
||||
// zone is visible so evaluate which would be hurt the most
|
||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
||||
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
||||
|
||||
// set the target
|
||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(oppTarget);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Exile)) {
|
||||
String logic = sa.getParam("AILogic");
|
||||
|
||||
if (logic != null && logic.startsWith("DiscardAllAndRetExiled")) {
|
||||
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
|
||||
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||
|
||||
// minimum card advantage unless the hand will be fully reloaded
|
||||
int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0;
|
||||
|
||||
if (numExiledWithSrc > curHandSize) {
|
||||
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
|
||||
// Try to gain some card advantage if the card will die anyway
|
||||
// TODO: ideally, should evaluate the hand value and not discard good hands to it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Stack)) {
|
||||
// time stop can do something like this:
|
||||
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
|
||||
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
|
||||
// otherwise, this situation doesn't exist
|
||||
return false;
|
||||
}
|
||||
|
||||
if (destination.equals(ZoneType.Battlefield)) {
|
||||
if (sa.getParam("GainControl") != null) {
|
||||
// Check if the cards are valuable enough
|
||||
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
|
||||
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
||||
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
|
||||
.evaluateCreatureList(oppType)) < 400) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human
|
||||
// permanents are less valuable
|
||||
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
||||
.evaluatePermanentList(oppType)) < 6) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// don't activate if human gets more back than AI does
|
||||
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
|
||||
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(computerType) <= (ComputerUtilCard
|
||||
.evaluateCreatureList(oppType) + 100)) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human
|
||||
// permanents are less valuable
|
||||
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= (ComputerUtilCard
|
||||
.evaluatePermanentList(oppType) + 2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (((r.nextFloat() < .8) || sa.isTrigger()) && chance);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* changeZoneAllPlayDrawbackAI.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param aiPlayer
|
||||
* a {@link forge.game.player.Player} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
// if putting cards from hand to library and parent is drawing cards
|
||||
// make sure this will actually do something:
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String hostName = source.getName();
|
||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||
|
||||
if (hostName.equals("Dawnbreak Reclaimer")) {
|
||||
final CardCollectionView cards = AbilityUtils.filterListByType(ai.getGame().getCardsIn(origin), sa.getParam("ChangeType"), sa);
|
||||
|
||||
// AI gets nothing
|
||||
final CardCollection aiCards = CardLists.filterControlledBy(cards, ai);
|
||||
if (aiCards.isEmpty())
|
||||
return false;
|
||||
|
||||
// Human gets nothing
|
||||
final CardCollection humanCards = CardLists.filterControlledBy(cards, ai.getOpponents());
|
||||
if (humanCards.isEmpty())
|
||||
return true;
|
||||
|
||||
// if AI creature is better than Human Creature
|
||||
if (ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard
|
||||
.evaluateCreatureList(humanCards)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, final SpellAbility sa, boolean mandatory) {
|
||||
// Change Zone All, can be any type moving from one zone to another
|
||||
|
||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Profaner of the Dead")) {
|
||||
// TODO: this is a stub to prevent the AI from crashing the game when, for instance, playing the opponent's
|
||||
// Profaner from exile without paying its mana cost. Otherwise the card is marked RemAIDeck and there is no
|
||||
// specific AI to support playing it in a smarter way. Feel free to expand.
|
||||
return !CardLists.filter(ai.getOpponents().getCardsIn(origin), CardPredicates.Presets.CREATURES).isEmpty();
|
||||
}
|
||||
|
||||
CardCollectionView humanType = CardLists.filterControlledBy(ai.getGame().getCardsIn(origin), ai.getOpponents());
|
||||
humanType = AbilityUtils.filterListByType(humanType, sa.getParam("ChangeType"), sa);
|
||||
|
||||
CardCollectionView computerType = ai.getCardsIn(origin);
|
||||
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
||||
|
||||
// TODO improve restrictions on when the AI would want to use this
|
||||
// spBounceAll has some AI we can compare to.
|
||||
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
|
||||
if (sa.usesTargeting()) {
|
||||
// search targetable Opponents
|
||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
||||
PlayerPredicates.isTargetableBy(sa));
|
||||
|
||||
// get the one with the most handsize
|
||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
||||
PlayerPredicates.compareByZoneSize(origin));
|
||||
|
||||
// set the target
|
||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(oppTarget);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Battlefield)) {
|
||||
// if mandatory, no need to evaluate
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
// this statement is assuming the AI is trying to use this spell offensively
|
||||
// if the AI is using it defensively, then something else needs to occur
|
||||
// if only creatures are affected evaluate both lists and pass only
|
||||
// if human creatures are more valuable
|
||||
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(computerType) >= ComputerUtilCard
|
||||
.evaluateCreatureList(humanType)) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human
|
||||
// permanents are more valuable
|
||||
else if (ComputerUtilCard.evaluatePermanentList(computerType) >= ComputerUtilCard
|
||||
.evaluatePermanentList(humanType)) {
|
||||
return false;
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Graveyard)) {
|
||||
if (sa.usesTargeting()) {
|
||||
// search targetable Opponents
|
||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
||||
PlayerPredicates.isTargetableBy(sa));
|
||||
|
||||
// get the one with the most in graveyard
|
||||
// zone is visible so evaluate which would be hurt the most
|
||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
||||
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
||||
|
||||
// set the target
|
||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(oppTarget);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Exile)) {
|
||||
|
||||
} else if (origin.equals(ZoneType.Stack)) {
|
||||
// time stop can do something like this:
|
||||
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
|
||||
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
|
||||
// otherwise, this situation doesn't exist
|
||||
return false;
|
||||
}
|
||||
|
||||
if (destination.equals(ZoneType.Battlefield)) {
|
||||
// if mandatory, no need to evaluate
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
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)) {
|
||||
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 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())) {
|
||||
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 if (ComputerUtilCard.evaluatePermanentList(computerType) <= ComputerUtilCard
|
||||
.evaluatePermanentList(humanType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Random;
|
||||
|
||||
public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// Change Zone All, can be any type moving from one zone to another
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final Game game = ai.getGame();
|
||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
boolean aiLogicAllowsDiscard = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("DiscardAll");
|
||||
|
||||
if (!aiLogicAllowsDiscard) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Random r = MyRandom.getRandom();
|
||||
// prevent run-away activations - first time will always return true
|
||||
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
// TODO targeting with ChangeZoneAll
|
||||
// really two types of targeting.
|
||||
// Target Player has all their types change zones
|
||||
// or target permanent and do something relative to that permanent
|
||||
// ex. "Return all Auras attached to target"
|
||||
// ex. "Return all blocking/blocked by target creature"
|
||||
|
||||
CardCollectionView oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
|
||||
CardCollectionView computerType = ai.getCardsIn(origin);
|
||||
|
||||
// Ugin check need to be done before filterListByType because of ChosenX
|
||||
// Ugin AI: always try to sweep before considering +1
|
||||
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
||||
return SpecialCardAi.UginTheSpiritDragon.considerPWAbilityPriority(ai, sa, origin, oppType, computerType);
|
||||
}
|
||||
|
||||
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
|
||||
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
||||
|
||||
if ("LivingDeath".equals(sa.getParam("AILogic"))) {
|
||||
// Living Death AI
|
||||
return SpecialCardAi.LivingDeath.consider(ai, sa);
|
||||
} else if ("Timetwister".equals(sa.getParam("AILogic"))) {
|
||||
// Timetwister AI
|
||||
return SpecialCardAi.Timetwister.consider(ai, sa);
|
||||
} else if ("RetDiscardedThisTurn".equals(sa.getParam("AILogic"))) {
|
||||
// e.g. Shadow of the Grave
|
||||
return ai.getNumDiscardedThisTurn() > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN);
|
||||
} else if ("ExileGraveyards".equals(sa.getParam("AILogic"))) {
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
CardCollectionView cardsGY = opp.getCardsIn(ZoneType.Graveyard);
|
||||
CardCollection creats = CardLists.filter(cardsGY, CardPredicates.Presets.CREATURES);
|
||||
|
||||
if (opp.hasDelirium() || opp.hasThreshold() || creats.size() >= 5) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if ("ManifestCreatsFromGraveyard".equals(sa.getParam("AILogic"))) {
|
||||
PlayerCollection players = new PlayerCollection();
|
||||
players.addAll(ai.getOpponents());
|
||||
players.add(ai);
|
||||
int maxSize = 1;
|
||||
for (Player player : players) {
|
||||
Player bestTgt = null;
|
||||
if (player.canBeTargetedBy(sa)) {
|
||||
CardCollectionView cardsGY = CardLists.filter(player.getCardsIn(ZoneType.Graveyard),
|
||||
CardPredicates.Presets.CREATURES);
|
||||
if (cardsGY.size() > maxSize) {
|
||||
maxSize = cardsGY.size();
|
||||
bestTgt = player;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestTgt != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(bestTgt);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO improve restrictions on when the AI would want to use this
|
||||
// spBounceAll has some AI we can compare to.
|
||||
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
|
||||
if (!sa.usesTargeting()) {
|
||||
// TODO: improve logic for non-targeted SAs of this type (most are currently RemAIDeck, e.g. Memory Jar)
|
||||
return true;
|
||||
} else {
|
||||
// search targetable Opponents
|
||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), PlayerPredicates.isTargetableBy(sa));
|
||||
|
||||
// get the one with the most handsize
|
||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList), PlayerPredicates.compareByZoneSize(origin));
|
||||
|
||||
// set the target
|
||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(oppTarget);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Battlefield)) {
|
||||
// this statement is assuming the AI is trying to use this spell
|
||||
// offensively
|
||||
// if the AI is using it defensively, then something else needs to
|
||||
// occur
|
||||
// if only creatures are affected evaluate both lists and pass only
|
||||
// if human creatures are more valuable
|
||||
if (sa.usesTargeting()) {
|
||||
// search targetable Opponents
|
||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
||||
PlayerPredicates.isTargetableBy(sa));
|
||||
|
||||
// get the one with the most in graveyard
|
||||
// zone is visible so evaluate which would be hurt the most
|
||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
||||
PlayerPredicates.compareByZoneSize(origin));
|
||||
|
||||
// set the target
|
||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(oppTarget);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
computerType = new CardCollection();
|
||||
}
|
||||
|
||||
int creatureEvalThreshold = 200; // value difference (in evaluateCreatureList units)
|
||||
int nonCreatureEvalThreshold = 3; // CMC difference
|
||||
if (ai.getController().isAI()) {
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
if (destination == ZoneType.Hand) {
|
||||
creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF);
|
||||
nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF);
|
||||
} else {
|
||||
creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF);
|
||||
nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF);
|
||||
}
|
||||
}
|
||||
|
||||
// mass zone change for creatures: if in dire danger, do it; otherwise, only do it if the opponent's
|
||||
// creatures are better in value
|
||||
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
|
||||
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
||||
if (game.getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
|
||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
|
||||
// Life is in serious danger, return all creatures from the battlefield to wherever
|
||||
// so they don't deal lethal damage
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ((ComputerUtilCard.evaluateCreatureList(computerType) + creatureEvalThreshold) >= ComputerUtilCard
|
||||
.evaluateCreatureList(oppType)) {
|
||||
return false;
|
||||
}
|
||||
} // mass zone change for non-creatures: evaluate both lists by CMC and pass only if human
|
||||
// permanents are more valuable
|
||||
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + nonCreatureEvalThreshold) >= ComputerUtilCard
|
||||
.evaluatePermanentList(oppType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't cast during main1?
|
||||
if (game.getPhaseHandler().is(PhaseType.MAIN1, ai)) {
|
||||
return false;
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Graveyard)) {
|
||||
if (sa.usesTargeting()) {
|
||||
// search targetable Opponents
|
||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
||||
PlayerPredicates.isTargetableBy(sa));
|
||||
|
||||
if (Iterables.isEmpty(oppList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the one with the most in graveyard
|
||||
// zone is visible so evaluate which would be hurt the most
|
||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
||||
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
||||
|
||||
// set the target
|
||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(oppTarget);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Exile)) {
|
||||
String logic = sa.getParam("AILogic");
|
||||
|
||||
if (logic != null && logic.startsWith("DiscardAllAndRetExiled")) {
|
||||
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
|
||||
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||
|
||||
// minimum card advantage unless the hand will be fully reloaded
|
||||
int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0;
|
||||
|
||||
if (numExiledWithSrc > curHandSize) {
|
||||
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
|
||||
// Try to gain some card advantage if the card will die anyway
|
||||
// TODO: ideally, should evaluate the hand value and not discard good hands to it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Stack)) {
|
||||
// time stop can do something like this:
|
||||
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
|
||||
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
|
||||
// otherwise, this situation doesn't exist
|
||||
return false;
|
||||
}
|
||||
|
||||
if (destination.equals(ZoneType.Battlefield)) {
|
||||
if (sa.getParam("GainControl") != null) {
|
||||
// Check if the cards are valuable enough
|
||||
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
|
||||
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
||||
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
|
||||
.evaluateCreatureList(oppType)) < 400) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human
|
||||
// permanents are less valuable
|
||||
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
||||
.evaluatePermanentList(oppType)) < 6) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// don't activate if human gets more back than AI does
|
||||
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
|
||||
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(computerType) <= (ComputerUtilCard
|
||||
.evaluateCreatureList(oppType) + 100)) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human
|
||||
// permanents are less valuable
|
||||
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= (ComputerUtilCard
|
||||
.evaluatePermanentList(oppType) + 2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (((r.nextFloat() < .8) || sa.isTrigger()) && chance);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* changeZoneAllPlayDrawbackAI.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param aiPlayer
|
||||
* a {@link forge.game.player.Player} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
// if putting cards from hand to library and parent is drawing cards
|
||||
// make sure this will actually do something:
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String hostName = source.getName();
|
||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||
|
||||
if (hostName.equals("Dawnbreak Reclaimer")) {
|
||||
final CardCollectionView cards = AbilityUtils.filterListByType(ai.getGame().getCardsIn(origin), sa.getParam("ChangeType"), sa);
|
||||
|
||||
// AI gets nothing
|
||||
final CardCollection aiCards = CardLists.filterControlledBy(cards, ai);
|
||||
if (aiCards.isEmpty())
|
||||
return false;
|
||||
|
||||
// Human gets nothing
|
||||
final CardCollection humanCards = CardLists.filterControlledBy(cards, ai.getOpponents());
|
||||
if (humanCards.isEmpty())
|
||||
return true;
|
||||
|
||||
// if AI creature is better than Human Creature
|
||||
if (ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard
|
||||
.evaluateCreatureList(humanCards)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, final SpellAbility sa, boolean mandatory) {
|
||||
// Change Zone All, can be any type moving from one zone to another
|
||||
|
||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Profaner of the Dead")) {
|
||||
// TODO: this is a stub to prevent the AI from crashing the game when, for instance, playing the opponent's
|
||||
// Profaner from exile without paying its mana cost. Otherwise the card is marked RemAIDeck and there is no
|
||||
// specific AI to support playing it in a smarter way. Feel free to expand.
|
||||
return !CardLists.filter(ai.getOpponents().getCardsIn(origin), CardPredicates.Presets.CREATURES).isEmpty();
|
||||
}
|
||||
|
||||
CardCollectionView humanType = CardLists.filterControlledBy(ai.getGame().getCardsIn(origin), ai.getOpponents());
|
||||
humanType = AbilityUtils.filterListByType(humanType, sa.getParam("ChangeType"), sa);
|
||||
|
||||
CardCollectionView computerType = ai.getCardsIn(origin);
|
||||
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
||||
|
||||
// TODO improve restrictions on when the AI would want to use this
|
||||
// spBounceAll has some AI we can compare to.
|
||||
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
|
||||
if (sa.usesTargeting()) {
|
||||
// search targetable Opponents
|
||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
||||
PlayerPredicates.isTargetableBy(sa));
|
||||
|
||||
// get the one with the most handsize
|
||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
||||
PlayerPredicates.compareByZoneSize(origin));
|
||||
|
||||
// set the target
|
||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(oppTarget);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Battlefield)) {
|
||||
// if mandatory, no need to evaluate
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
// this statement is assuming the AI is trying to use this spell offensively
|
||||
// if the AI is using it defensively, then something else needs to occur
|
||||
// if only creatures are affected evaluate both lists and pass only
|
||||
// if human creatures are more valuable
|
||||
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(computerType) >= ComputerUtilCard
|
||||
.evaluateCreatureList(humanType)) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human
|
||||
// permanents are more valuable
|
||||
else if (ComputerUtilCard.evaluatePermanentList(computerType) >= ComputerUtilCard
|
||||
.evaluatePermanentList(humanType)) {
|
||||
return false;
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Graveyard)) {
|
||||
if (sa.usesTargeting()) {
|
||||
// search targetable Opponents
|
||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
||||
PlayerPredicates.isTargetableBy(sa));
|
||||
|
||||
// get the one with the most in graveyard
|
||||
// zone is visible so evaluate which would be hurt the most
|
||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
||||
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
||||
|
||||
// set the target
|
||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(oppTarget);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Exile)) {
|
||||
|
||||
} else if (origin.equals(ZoneType.Stack)) {
|
||||
// time stop can do something like this:
|
||||
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
|
||||
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
|
||||
// otherwise, this situation doesn't exist
|
||||
return false;
|
||||
}
|
||||
|
||||
if (destination.equals(ZoneType.Battlefield)) {
|
||||
// if mandatory, no need to evaluate
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
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)) {
|
||||
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 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())) {
|
||||
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 if (ComputerUtilCard.evaluatePermanentList(computerType) <= ComputerUtilCard
|
||||
.evaluatePermanentList(humanType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,241 +1,241 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class CharmAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
// sa is Entwined, no need for extra logic
|
||||
if (sa.isEntwine()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Random r = MyRandom.getRandom();
|
||||
|
||||
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
|
||||
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
|
||||
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
|
||||
|
||||
// Reset the chosen list otherwise it will be locked in forever by earlier calls
|
||||
sa.setChosenList(null);
|
||||
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
||||
List<AbilitySub> chosenList;
|
||||
|
||||
if (!ai.equals(sa.getActivatingPlayer())) {
|
||||
// This branch is for "An Opponent chooses" Charm spells from Alliances
|
||||
// Current just choose the first available spell, which seem generally less disastrous for the AI.
|
||||
//return choices.subList(0, 1);
|
||||
chosenList = choices.subList(1, choices.size());
|
||||
} else if ("Triskaidekaphobia".equals(ComputerUtilAbility.getAbilitySourceName(sa))) {
|
||||
chosenList = chooseTriskaidekaphobia(choices, ai);
|
||||
} else {
|
||||
/*
|
||||
* The generic chooseOptionsAi uses canPlayAi() to determine good choices
|
||||
* which means most "bonus" effects like life-gain and random pumps will
|
||||
* usually not be chosen. This is designed to force the AI to only select
|
||||
* the best choice(s) since it does not actually know if it can pay for
|
||||
* "bonus" choices (eg. Entwine/Escalate).
|
||||
* chooseMultipleOptionsAi() uses "AILogic$Good" tags to manually identify
|
||||
* bonus choice(s) for the AI otherwise it might be too hard to ever fulfil
|
||||
* minimum choice requirements with canPlayAi() alone.
|
||||
*/
|
||||
chosenList = min > 1 ? chooseMultipleOptionsAi(choices, ai, min)
|
||||
: chooseOptionsAi(choices, ai, timingRight, num, min, sa.hasParam("CanRepeatModes"));
|
||||
}
|
||||
|
||||
if (chosenList.isEmpty()) {
|
||||
if (timingRight) {
|
||||
// Set minimum choices for triggers where chooseMultipleOptionsAi() returns null
|
||||
chosenList = chooseOptionsAi(choices, ai, true, num, min, sa.hasParam("CanRepeatModes"));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sa.setChosenList(chosenList);
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
return r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
}
|
||||
|
||||
private List<AbilitySub> chooseOptionsAi(List<AbilitySub> choices, final Player ai, boolean isTrigger, int num,
|
||||
int min, boolean allowRepeat) {
|
||||
List<AbilitySub> chosenList = Lists.newArrayList();
|
||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||
// First pass using standard canPlayAi() for good choices
|
||||
for (AbilitySub sub : choices) {
|
||||
sub.setActivatingPlayer(ai);
|
||||
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
||||
chosenList.add(sub);
|
||||
if (chosenList.size() == num) {
|
||||
return chosenList; // maximum choices reached
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isTrigger && chosenList.size() < min) {
|
||||
// Second pass using doTrigger(false) to fulfil minimum choice
|
||||
choices.removeAll(chosenList);
|
||||
for (AbilitySub sub : choices) {
|
||||
sub.setActivatingPlayer(ai);
|
||||
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||
if (aic.doTrigger(sub, false)) {
|
||||
chosenList.add(sub);
|
||||
if (chosenList.size() == min) {
|
||||
return chosenList;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Third pass using doTrigger(true) to force fill minimum choices
|
||||
if (chosenList.size() < min) {
|
||||
choices.removeAll(chosenList);
|
||||
for (AbilitySub sub : choices) {
|
||||
sub.setActivatingPlayer(ai);
|
||||
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||
if (aic.doTrigger(sub, true)) {
|
||||
chosenList.add(sub);
|
||||
if (chosenList.size() == min) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chosenList.size() < min) {
|
||||
chosenList.clear(); // not enough choices
|
||||
}
|
||||
return chosenList;
|
||||
}
|
||||
|
||||
private List<AbilitySub> chooseTriskaidekaphobia(List<AbilitySub> choices, final Player ai) {
|
||||
List<AbilitySub> chosenList = Lists.newArrayList();
|
||||
if (choices == null || choices.isEmpty()) { return chosenList; }
|
||||
|
||||
AbilitySub gain = choices.get(0);
|
||||
AbilitySub lose = choices.get(1);
|
||||
FCollection<Player> opponents = ai.getOpponents();
|
||||
|
||||
boolean oppTainted = false;
|
||||
boolean allyTainted = ai.isCardInPlay("Tainted Remedy");
|
||||
final int aiLife = ai.getLife();
|
||||
|
||||
//Check if Opponent controls Tainted Remedy
|
||||
for (Player p : opponents) {
|
||||
if (p.isCardInPlay("Tainted Remedy")) {
|
||||
oppTainted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if ai or ally of ai does control Tainted Remedy, prefer gain life instead of lose
|
||||
if (!allyTainted) {
|
||||
for (Player p : ai.getAllies()) {
|
||||
if (p.isCardInPlay("Tainted Remedy")) {
|
||||
allyTainted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ai.canLoseLife() || ai.cantLose()) {
|
||||
// ai cant lose life, or cant lose the game, don't think about others
|
||||
chosenList.add(allyTainted ? gain : lose);
|
||||
} else if (oppTainted || ai.getGame().isCardInPlay("Rain of Gore")) {
|
||||
// Rain of Gore does negate lifegain, so don't benefit the others
|
||||
// same for if a oppoent does control Tainted Remedy
|
||||
// but if ai cant gain life, the effects are negated
|
||||
chosenList.add(ai.canGainLife() ? lose : gain);
|
||||
} else if (ai.getGame().isCardInPlay("Sulfuric Vortex")) {
|
||||
// no life gain, but extra life loss.
|
||||
if (aiLife >= 17)
|
||||
chosenList.add(lose);
|
||||
// try to prevent to get to 13 with extra lose
|
||||
else if (aiLife < 13 || ((aiLife - 13) % 2) == 1) {
|
||||
chosenList.add(gain);
|
||||
} else {
|
||||
chosenList.add(lose);
|
||||
}
|
||||
} else if (ai.canGainLife() && aiLife <= 5) {
|
||||
// critical Life try to gain more
|
||||
chosenList.add(gain);
|
||||
} else if(!ai.canGainLife() && aiLife == 14 ) {
|
||||
// ai cant gain life, but try to avoid falling to 13
|
||||
// but if a oppoent does control Tainted Remedy its irrelevant
|
||||
chosenList.add(oppTainted ? lose : gain);
|
||||
} else if (allyTainted) {
|
||||
// Tainted Remedy negation logic, try gain instead of lose
|
||||
// because negation does turn it into lose for opponents
|
||||
boolean oppCritical = false;
|
||||
// an oppoent is Critical = 14, and can't gain life, try to lose life instead
|
||||
// but only if ai doesn't kill itself with that.
|
||||
if (aiLife != 14) {
|
||||
for (Player p : opponents) {
|
||||
if (p.getLife() == 14 && !p.canGainLife() && p.canLoseLife()) {
|
||||
oppCritical = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
chosenList.add(aiLife == 12 || oppCritical ? lose : gain);
|
||||
} else {
|
||||
// normal logic, try to gain life if its critical
|
||||
boolean oppCritical = false;
|
||||
// an oppoent is Critical = 12, and can gain life, try to gain life instead
|
||||
// but only if ai doesn't kill itself with that.
|
||||
if (aiLife != 12) {
|
||||
for (Player p : opponents) {
|
||||
if (p.getLife() == 12 && p.canGainLife()) {
|
||||
oppCritical = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
chosenList.add(aiLife == 14 || aiLife <= 10 || oppCritical ? gain : lose);
|
||||
}
|
||||
return chosenList;
|
||||
}
|
||||
|
||||
// Choice selection for charms that require multiple choices (eg. Cryptic Command, DTK commands)
|
||||
private List<AbilitySub> chooseMultipleOptionsAi(List<AbilitySub> choices, final Player ai, int min) {
|
||||
AbilitySub goodChoice = null;
|
||||
List<AbilitySub> chosenList = Lists.newArrayList();
|
||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||
for (AbilitySub sub : choices) {
|
||||
sub.setActivatingPlayer(ai);
|
||||
// Assign generic good choice to fill up choices if necessary
|
||||
if ("Good".equals(sub.getParam("AILogic")) && aic.doTrigger(sub, false)) {
|
||||
goodChoice = sub;
|
||||
} else {
|
||||
// Standard canPlayAi()
|
||||
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
||||
chosenList.add(sub);
|
||||
if (chosenList.size() == min) {
|
||||
break; // enough choices
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add generic good choice if one more choice is needed
|
||||
if (chosenList.size() == min - 1 && goodChoice != null) {
|
||||
chosenList.add(0, goodChoice); // hack to make Dromoka's Command fight targets work
|
||||
}
|
||||
if (chosenList.size() != min) {
|
||||
chosenList.clear();
|
||||
}
|
||||
return chosenList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents) {
|
||||
return Aggregates.random(opponents);
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class CharmAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
// sa is Entwined, no need for extra logic
|
||||
if (sa.isEntwine()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Random r = MyRandom.getRandom();
|
||||
|
||||
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
|
||||
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
|
||||
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
|
||||
|
||||
// Reset the chosen list otherwise it will be locked in forever by earlier calls
|
||||
sa.setChosenList(null);
|
||||
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
||||
List<AbilitySub> chosenList;
|
||||
|
||||
if (!ai.equals(sa.getActivatingPlayer())) {
|
||||
// This branch is for "An Opponent chooses" Charm spells from Alliances
|
||||
// Current just choose the first available spell, which seem generally less disastrous for the AI.
|
||||
//return choices.subList(0, 1);
|
||||
chosenList = choices.subList(1, choices.size());
|
||||
} else if ("Triskaidekaphobia".equals(ComputerUtilAbility.getAbilitySourceName(sa))) {
|
||||
chosenList = chooseTriskaidekaphobia(choices, ai);
|
||||
} else {
|
||||
/*
|
||||
* The generic chooseOptionsAi uses canPlayAi() to determine good choices
|
||||
* which means most "bonus" effects like life-gain and random pumps will
|
||||
* usually not be chosen. This is designed to force the AI to only select
|
||||
* the best choice(s) since it does not actually know if it can pay for
|
||||
* "bonus" choices (eg. Entwine/Escalate).
|
||||
* chooseMultipleOptionsAi() uses "AILogic$Good" tags to manually identify
|
||||
* bonus choice(s) for the AI otherwise it might be too hard to ever fulfil
|
||||
* minimum choice requirements with canPlayAi() alone.
|
||||
*/
|
||||
chosenList = min > 1 ? chooseMultipleOptionsAi(choices, ai, min)
|
||||
: chooseOptionsAi(choices, ai, timingRight, num, min, sa.hasParam("CanRepeatModes"));
|
||||
}
|
||||
|
||||
if (chosenList.isEmpty()) {
|
||||
if (timingRight) {
|
||||
// Set minimum choices for triggers where chooseMultipleOptionsAi() returns null
|
||||
chosenList = chooseOptionsAi(choices, ai, true, num, min, sa.hasParam("CanRepeatModes"));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sa.setChosenList(chosenList);
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
return r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
}
|
||||
|
||||
private List<AbilitySub> chooseOptionsAi(List<AbilitySub> choices, final Player ai, boolean isTrigger, int num,
|
||||
int min, boolean allowRepeat) {
|
||||
List<AbilitySub> chosenList = Lists.newArrayList();
|
||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||
// First pass using standard canPlayAi() for good choices
|
||||
for (AbilitySub sub : choices) {
|
||||
sub.setActivatingPlayer(ai);
|
||||
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
||||
chosenList.add(sub);
|
||||
if (chosenList.size() == num) {
|
||||
return chosenList; // maximum choices reached
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isTrigger && chosenList.size() < min) {
|
||||
// Second pass using doTrigger(false) to fulfil minimum choice
|
||||
choices.removeAll(chosenList);
|
||||
for (AbilitySub sub : choices) {
|
||||
sub.setActivatingPlayer(ai);
|
||||
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||
if (aic.doTrigger(sub, false)) {
|
||||
chosenList.add(sub);
|
||||
if (chosenList.size() == min) {
|
||||
return chosenList;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Third pass using doTrigger(true) to force fill minimum choices
|
||||
if (chosenList.size() < min) {
|
||||
choices.removeAll(chosenList);
|
||||
for (AbilitySub sub : choices) {
|
||||
sub.setActivatingPlayer(ai);
|
||||
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||
if (aic.doTrigger(sub, true)) {
|
||||
chosenList.add(sub);
|
||||
if (chosenList.size() == min) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chosenList.size() < min) {
|
||||
chosenList.clear(); // not enough choices
|
||||
}
|
||||
return chosenList;
|
||||
}
|
||||
|
||||
private List<AbilitySub> chooseTriskaidekaphobia(List<AbilitySub> choices, final Player ai) {
|
||||
List<AbilitySub> chosenList = Lists.newArrayList();
|
||||
if (choices == null || choices.isEmpty()) { return chosenList; }
|
||||
|
||||
AbilitySub gain = choices.get(0);
|
||||
AbilitySub lose = choices.get(1);
|
||||
FCollection<Player> opponents = ai.getOpponents();
|
||||
|
||||
boolean oppTainted = false;
|
||||
boolean allyTainted = ai.isCardInPlay("Tainted Remedy");
|
||||
final int aiLife = ai.getLife();
|
||||
|
||||
//Check if Opponent controls Tainted Remedy
|
||||
for (Player p : opponents) {
|
||||
if (p.isCardInPlay("Tainted Remedy")) {
|
||||
oppTainted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if ai or ally of ai does control Tainted Remedy, prefer gain life instead of lose
|
||||
if (!allyTainted) {
|
||||
for (Player p : ai.getAllies()) {
|
||||
if (p.isCardInPlay("Tainted Remedy")) {
|
||||
allyTainted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ai.canLoseLife() || ai.cantLose()) {
|
||||
// ai cant lose life, or cant lose the game, don't think about others
|
||||
chosenList.add(allyTainted ? gain : lose);
|
||||
} else if (oppTainted || ai.getGame().isCardInPlay("Rain of Gore")) {
|
||||
// Rain of Gore does negate lifegain, so don't benefit the others
|
||||
// same for if a oppoent does control Tainted Remedy
|
||||
// but if ai cant gain life, the effects are negated
|
||||
chosenList.add(ai.canGainLife() ? lose : gain);
|
||||
} else if (ai.getGame().isCardInPlay("Sulfuric Vortex")) {
|
||||
// no life gain, but extra life loss.
|
||||
if (aiLife >= 17)
|
||||
chosenList.add(lose);
|
||||
// try to prevent to get to 13 with extra lose
|
||||
else if (aiLife < 13 || ((aiLife - 13) % 2) == 1) {
|
||||
chosenList.add(gain);
|
||||
} else {
|
||||
chosenList.add(lose);
|
||||
}
|
||||
} else if (ai.canGainLife() && aiLife <= 5) {
|
||||
// critical Life try to gain more
|
||||
chosenList.add(gain);
|
||||
} else if(!ai.canGainLife() && aiLife == 14 ) {
|
||||
// ai cant gain life, but try to avoid falling to 13
|
||||
// but if a oppoent does control Tainted Remedy its irrelevant
|
||||
chosenList.add(oppTainted ? lose : gain);
|
||||
} else if (allyTainted) {
|
||||
// Tainted Remedy negation logic, try gain instead of lose
|
||||
// because negation does turn it into lose for opponents
|
||||
boolean oppCritical = false;
|
||||
// an oppoent is Critical = 14, and can't gain life, try to lose life instead
|
||||
// but only if ai doesn't kill itself with that.
|
||||
if (aiLife != 14) {
|
||||
for (Player p : opponents) {
|
||||
if (p.getLife() == 14 && !p.canGainLife() && p.canLoseLife()) {
|
||||
oppCritical = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
chosenList.add(aiLife == 12 || oppCritical ? lose : gain);
|
||||
} else {
|
||||
// normal logic, try to gain life if its critical
|
||||
boolean oppCritical = false;
|
||||
// an oppoent is Critical = 12, and can gain life, try to gain life instead
|
||||
// but only if ai doesn't kill itself with that.
|
||||
if (aiLife != 12) {
|
||||
for (Player p : opponents) {
|
||||
if (p.getLife() == 12 && p.canGainLife()) {
|
||||
oppCritical = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
chosenList.add(aiLife == 14 || aiLife <= 10 || oppCritical ? gain : lose);
|
||||
}
|
||||
return chosenList;
|
||||
}
|
||||
|
||||
// Choice selection for charms that require multiple choices (eg. Cryptic Command, DTK commands)
|
||||
private List<AbilitySub> chooseMultipleOptionsAi(List<AbilitySub> choices, final Player ai, int min) {
|
||||
AbilitySub goodChoice = null;
|
||||
List<AbilitySub> chosenList = Lists.newArrayList();
|
||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||
for (AbilitySub sub : choices) {
|
||||
sub.setActivatingPlayer(ai);
|
||||
// Assign generic good choice to fill up choices if necessary
|
||||
if ("Good".equals(sub.getParam("AILogic")) && aic.doTrigger(sub, false)) {
|
||||
goodChoice = sub;
|
||||
} else {
|
||||
// Standard canPlayAi()
|
||||
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
||||
chosenList.add(sub);
|
||||
if (chosenList.size() == min) {
|
||||
break; // enough choices
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add generic good choice if one more choice is needed
|
||||
if (chosenList.size() == min - 1 && goodChoice != null) {
|
||||
chosenList.add(0, goodChoice); // hack to make Dromoka's Command fight targets work
|
||||
}
|
||||
if (chosenList.size() != min) {
|
||||
chosenList.clear();
|
||||
}
|
||||
return chosenList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents) {
|
||||
return Aggregates.random(opponents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,269 +1,269 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
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.CounterType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
public class ChooseCardAi extends SpellAbilityAi {
|
||||
|
||||
/**
|
||||
* The rest of the logic not covered by the canPlayAI template is defined here
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
// search targetable Opponents
|
||||
final List<Player> oppList = Lists.newArrayList(Iterables.filter(
|
||||
ai.getOpponents(), PlayerPredicates.isTargetableBy(sa)));
|
||||
|
||||
if (oppList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sa.getTargets().add(Iterables.getFirst(oppList, null));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility with the specified AiLogic
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
ZoneType choiceZone = ZoneType.Battlefield;
|
||||
if (sa.hasParam("ChoiceZone")) {
|
||||
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
|
||||
}
|
||||
CardCollectionView choices = ai.getGame().getCardsIn(choiceZone);
|
||||
if (sa.hasParam("Choices")) {
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||
}
|
||||
if (sa.hasParam("TargetControls")) {
|
||||
choices = CardLists.filterControlledBy(choices, ai.getOpponents());
|
||||
}
|
||||
if (aiLogic.equals("AtLeast1") || aiLogic.equals("OppPreferred")) {
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
|
||||
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);
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("Never")) {
|
||||
return false;
|
||||
} else if (aiLogic.equals("NeedsPrevention")) {
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
final Combat combat = game.getCombat();
|
||||
choices = CardLists.filter(choices, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||
return false;
|
||||
}
|
||||
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
||||
}
|
||||
});
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("Ashiok")) {
|
||||
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
|
||||
for (int i = loyalty; i >= 0; i--) {
|
||||
host.setSVar("ChosenX", "Number$" + i);
|
||||
choices = ai.getGame().getCardsIn(choiceZone);
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||
if (!choices.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("RandomNonLand")) {
|
||||
if (CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("Duneblast")) {
|
||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||
CardCollection oppCreatures = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible");
|
||||
oppCreatures = CardLists.getNotKeyword(oppCreatures, "Indestructible");
|
||||
|
||||
// Use it as a wrath, when the human creatures threat the ai's life
|
||||
if (aiCreatures.isEmpty() && ComputerUtilCombat.sumDamageIfUnblocked(oppCreatures, ai) >= ai.getLife()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
|
||||
aiCreatures.remove(chosen);
|
||||
int minGain = 200;
|
||||
|
||||
if ((ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) >= ComputerUtilCard
|
||||
.evaluateCreatureList(oppCreatures)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
if (sa.hasParam("AILogic") && !checkAiLogic(ai, sa, sa.getParam("AILogic"))) {
|
||||
return false;
|
||||
}
|
||||
return checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player ctrl = host.getController();
|
||||
final String logic = sa.getParam("AILogic");
|
||||
Card choice = null;
|
||||
if (logic == null) {
|
||||
// Base Logic is choose "best"
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
} else if ("WorstCard".equals(logic)) {
|
||||
choice = ComputerUtilCard.getWorstAI(options);
|
||||
} else if (logic.equals("BestBlocker")) {
|
||||
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
|
||||
options = CardLists.filter(options, Presets.UNTAPPED);
|
||||
}
|
||||
choice = ComputerUtilCard.getBestCreatureAI(options);
|
||||
} else if (logic.equals("Clone") || logic.equals("Vesuva")) {
|
||||
final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" :
|
||||
"Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
||||
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||
if (!newOptions.isEmpty()) {
|
||||
options = newOptions;
|
||||
}
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) {
|
||||
choice = null;
|
||||
}
|
||||
} else if ("RandomNonLand".equals(logic)) {
|
||||
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host);
|
||||
choice = Aggregates.random(options);
|
||||
} else if (logic.equals("Untap")) {
|
||||
final String filter = "Permanent.YouCtrl,Permanent.tapped";
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||
if (!newOptions.isEmpty()) {
|
||||
options = newOptions;
|
||||
}
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
} else if (logic.equals("NeedsPrevention")) {
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
CardCollectionView better = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||
return false;
|
||||
}
|
||||
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
||||
}
|
||||
});
|
||||
if (!better.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestAI(better);
|
||||
} else {
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
} else if ("OppPreferred".equals(logic)) {
|
||||
CardCollectionView oppControlled = CardLists.filterControlledBy(options, ai.getOpponents());
|
||||
if (!oppControlled.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestAI(oppControlled);
|
||||
} else {
|
||||
CardCollectionView aiControlled = CardLists.filterControlledBy(options, ai);
|
||||
choice = ComputerUtilCard.getWorstAI(aiControlled);
|
||||
}
|
||||
} else if ("LowestCMCCreature".equals(logic)) {
|
||||
CardCollection creats = CardLists.filter(options, Presets.CREATURES);
|
||||
creats = CardLists.filterToughness(creats, 1);
|
||||
if (creats.isEmpty()) {
|
||||
choice = ComputerUtilCard.getWorstAI(options);
|
||||
} else {
|
||||
CardLists.sortByCmcDesc(creats);
|
||||
Collections.reverse(creats);
|
||||
choice = creats.get(0);
|
||||
}
|
||||
} else if ("TangleWire".equals(logic)) {
|
||||
CardCollectionView betterList = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (c.isCreature()) {
|
||||
return false;
|
||||
}
|
||||
for (SpellAbility sa : c.getAllSpellAbilities()) {
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
System.out.println("Tangle Wire" + options + " - " + betterList);
|
||||
if (!betterList.isEmpty()) {
|
||||
choice = betterList.get(0);
|
||||
} else {
|
||||
choice = ComputerUtilCard.getWorstPermanentAI(options, false, false, false, false);
|
||||
}
|
||||
} else if (logic.equals("Duneblast")) {
|
||||
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
|
||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible");
|
||||
|
||||
if (aiCreatures.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
|
||||
return chosen;
|
||||
} else {
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
return choice;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
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.CounterType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
public class ChooseCardAi extends SpellAbilityAi {
|
||||
|
||||
/**
|
||||
* The rest of the logic not covered by the canPlayAI template is defined here
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
// search targetable Opponents
|
||||
final List<Player> oppList = Lists.newArrayList(Iterables.filter(
|
||||
ai.getOpponents(), PlayerPredicates.isTargetableBy(sa)));
|
||||
|
||||
if (oppList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sa.getTargets().add(Iterables.getFirst(oppList, null));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility with the specified AiLogic
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
ZoneType choiceZone = ZoneType.Battlefield;
|
||||
if (sa.hasParam("ChoiceZone")) {
|
||||
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
|
||||
}
|
||||
CardCollectionView choices = ai.getGame().getCardsIn(choiceZone);
|
||||
if (sa.hasParam("Choices")) {
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||
}
|
||||
if (sa.hasParam("TargetControls")) {
|
||||
choices = CardLists.filterControlledBy(choices, ai.getOpponents());
|
||||
}
|
||||
if (aiLogic.equals("AtLeast1") || aiLogic.equals("OppPreferred")) {
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
|
||||
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);
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("Never")) {
|
||||
return false;
|
||||
} else if (aiLogic.equals("NeedsPrevention")) {
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
final Combat combat = game.getCombat();
|
||||
choices = CardLists.filter(choices, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||
return false;
|
||||
}
|
||||
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
||||
}
|
||||
});
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("Ashiok")) {
|
||||
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
|
||||
for (int i = loyalty; i >= 0; i--) {
|
||||
host.setSVar("ChosenX", "Number$" + i);
|
||||
choices = ai.getGame().getCardsIn(choiceZone);
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||
if (!choices.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("RandomNonLand")) {
|
||||
if (CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("Duneblast")) {
|
||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||
CardCollection oppCreatures = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible");
|
||||
oppCreatures = CardLists.getNotKeyword(oppCreatures, "Indestructible");
|
||||
|
||||
// Use it as a wrath, when the human creatures threat the ai's life
|
||||
if (aiCreatures.isEmpty() && ComputerUtilCombat.sumDamageIfUnblocked(oppCreatures, ai) >= ai.getLife()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
|
||||
aiCreatures.remove(chosen);
|
||||
int minGain = 200;
|
||||
|
||||
if ((ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) >= ComputerUtilCard
|
||||
.evaluateCreatureList(oppCreatures)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
if (sa.hasParam("AILogic") && !checkAiLogic(ai, sa, sa.getParam("AILogic"))) {
|
||||
return false;
|
||||
}
|
||||
return checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player ctrl = host.getController();
|
||||
final String logic = sa.getParam("AILogic");
|
||||
Card choice = null;
|
||||
if (logic == null) {
|
||||
// Base Logic is choose "best"
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
} else if ("WorstCard".equals(logic)) {
|
||||
choice = ComputerUtilCard.getWorstAI(options);
|
||||
} else if (logic.equals("BestBlocker")) {
|
||||
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
|
||||
options = CardLists.filter(options, Presets.UNTAPPED);
|
||||
}
|
||||
choice = ComputerUtilCard.getBestCreatureAI(options);
|
||||
} else if (logic.equals("Clone") || logic.equals("Vesuva")) {
|
||||
final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" :
|
||||
"Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
||||
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||
if (!newOptions.isEmpty()) {
|
||||
options = newOptions;
|
||||
}
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) {
|
||||
choice = null;
|
||||
}
|
||||
} else if ("RandomNonLand".equals(logic)) {
|
||||
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host);
|
||||
choice = Aggregates.random(options);
|
||||
} else if (logic.equals("Untap")) {
|
||||
final String filter = "Permanent.YouCtrl,Permanent.tapped";
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||
if (!newOptions.isEmpty()) {
|
||||
options = newOptions;
|
||||
}
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
} else if (logic.equals("NeedsPrevention")) {
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
CardCollectionView better = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||
return false;
|
||||
}
|
||||
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
||||
}
|
||||
});
|
||||
if (!better.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestAI(better);
|
||||
} else {
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
} else if ("OppPreferred".equals(logic)) {
|
||||
CardCollectionView oppControlled = CardLists.filterControlledBy(options, ai.getOpponents());
|
||||
if (!oppControlled.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestAI(oppControlled);
|
||||
} else {
|
||||
CardCollectionView aiControlled = CardLists.filterControlledBy(options, ai);
|
||||
choice = ComputerUtilCard.getWorstAI(aiControlled);
|
||||
}
|
||||
} else if ("LowestCMCCreature".equals(logic)) {
|
||||
CardCollection creats = CardLists.filter(options, Presets.CREATURES);
|
||||
creats = CardLists.filterToughness(creats, 1);
|
||||
if (creats.isEmpty()) {
|
||||
choice = ComputerUtilCard.getWorstAI(options);
|
||||
} else {
|
||||
CardLists.sortByCmcDesc(creats);
|
||||
Collections.reverse(creats);
|
||||
choice = creats.get(0);
|
||||
}
|
||||
} else if ("TangleWire".equals(logic)) {
|
||||
CardCollectionView betterList = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (c.isCreature()) {
|
||||
return false;
|
||||
}
|
||||
for (SpellAbility sa : c.getAllSpellAbilities()) {
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
System.out.println("Tangle Wire" + options + " - " + betterList);
|
||||
if (!betterList.isEmpty()) {
|
||||
choice = betterList.get(0);
|
||||
} else {
|
||||
choice = ComputerUtilCard.getWorstPermanentAI(options, false, false, false, false);
|
||||
}
|
||||
} else if (logic.equals("Duneblast")) {
|
||||
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
|
||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible");
|
||||
|
||||
if (aiCreatures.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
|
||||
return chosen;
|
||||
} else {
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
return choice;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,107 +1,107 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.ai.*;
|
||||
import forge.card.CardDb;
|
||||
import forge.card.CardRules;
|
||||
import forge.card.CardSplitType;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.ICardFace;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.item.PaperCard;
|
||||
|
||||
public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
if (sa.hasParam("AILogic")) {
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String logic = sa.getParam("AILogic");
|
||||
if (logic.equals("MomirAvatar")) {
|
||||
return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
|
||||
} else if (logic.equals("CursedScroll")) {
|
||||
return SpecialCardAi.CursedScroll.consider(ai, sa);
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
} else {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
// TODO - there is no AILogic implemented yet
|
||||
return false;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
|
||||
// this function is only for "Alhammarret, High Arbiter"
|
||||
|
||||
if (faces.isEmpty()) {
|
||||
return "";
|
||||
} else if (faces.size() == 1) {
|
||||
return Iterables.getFirst(faces, null).getName();
|
||||
}
|
||||
|
||||
List<Card> cards = Lists.newArrayList();
|
||||
final CardDb cardDb = StaticData.instance().getCommonCards();
|
||||
|
||||
for (ICardFace face : faces) {
|
||||
final CardRules rules = cardDb.getRules(face.getName());
|
||||
boolean isOther = rules.getOtherPart() == face;
|
||||
final PaperCard paper = cardDb.getCard(rules.getName());
|
||||
final Card card = Card.fromPaperCard(paper, ai);
|
||||
|
||||
if (rules.getSplitType() == CardSplitType.Split) {
|
||||
Card copy = CardUtil.getLKICopy(card);
|
||||
// for calcing i need only one split side
|
||||
if (isOther) {
|
||||
copy.getCurrentState().copyFrom(card, card.getState(CardStateName.RightSplit));
|
||||
} else {
|
||||
copy.getCurrentState().copyFrom(card, card.getState(CardStateName.LeftSplit));
|
||||
}
|
||||
copy.updateStateForView();
|
||||
|
||||
cards.add(copy);
|
||||
} else if (!isOther) {
|
||||
// other can't be cast that way, not need to prevent that
|
||||
cards.add(card);
|
||||
}
|
||||
}
|
||||
|
||||
return ComputerUtilCard.getBestAI(cards).getName();
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.ai.*;
|
||||
import forge.card.CardDb;
|
||||
import forge.card.CardRules;
|
||||
import forge.card.CardSplitType;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.ICardFace;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.item.PaperCard;
|
||||
|
||||
public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
if (sa.hasParam("AILogic")) {
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String logic = sa.getParam("AILogic");
|
||||
if (logic.equals("MomirAvatar")) {
|
||||
return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
|
||||
} else if (logic.equals("CursedScroll")) {
|
||||
return SpecialCardAi.CursedScroll.consider(ai, sa);
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
} else {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
// TODO - there is no AILogic implemented yet
|
||||
return false;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
|
||||
// this function is only for "Alhammarret, High Arbiter"
|
||||
|
||||
if (faces.isEmpty()) {
|
||||
return "";
|
||||
} else if (faces.size() == 1) {
|
||||
return Iterables.getFirst(faces, null).getName();
|
||||
}
|
||||
|
||||
List<Card> cards = Lists.newArrayList();
|
||||
final CardDb cardDb = StaticData.instance().getCommonCards();
|
||||
|
||||
for (ICardFace face : faces) {
|
||||
final CardRules rules = cardDb.getRules(face.getName());
|
||||
boolean isOther = rules.getOtherPart() == face;
|
||||
final PaperCard paper = cardDb.getCard(rules.getName());
|
||||
final Card card = Card.fromPaperCard(paper, ai);
|
||||
|
||||
if (rules.getSplitType() == CardSplitType.Split) {
|
||||
Card copy = CardUtil.getLKICopy(card);
|
||||
// for calcing i need only one split side
|
||||
if (isOther) {
|
||||
copy.getCurrentState().copyFrom(card, card.getState(CardStateName.RightSplit));
|
||||
} else {
|
||||
copy.getCurrentState().copyFrom(card, card.getState(CardStateName.LeftSplit));
|
||||
}
|
||||
copy.updateStateForView();
|
||||
|
||||
cards.add(copy);
|
||||
} else if (!isOther) {
|
||||
// other can't be cast that way, not need to prevent that
|
||||
cards.add(card);
|
||||
}
|
||||
}
|
||||
|
||||
return ComputerUtilCard.getBestAI(cards).getName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,97 +1,97 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class ChooseColorAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return false;
|
||||
}
|
||||
final String logic = sa.getParam("AILogic");
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("Nykthos, Shrine to Nyx".equals(sourceName)) {
|
||||
return SpecialCardAi.NykthosShrineToNyx.consider(ai, sa);
|
||||
}
|
||||
|
||||
if ("Oona, Queen of the Fae".equals(sourceName)) {
|
||||
if (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
// Set PayX here to maximum value.
|
||||
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(x));
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("Addle".equals(sourceName)) {
|
||||
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 = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
ailist = CardLists.filter(ailist, CardPredicates.isColor(color));
|
||||
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));
|
||||
|
||||
int excess = ComputerUtilCard.evaluatePermanentList(opplist) - ComputerUtilCard.evaluatePermanentList(ailist);
|
||||
if (excess > 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (logic.equals("MostProminentInComputerDeck")) {
|
||||
if ("Astral Cornucopia".equals(sourceName)) {
|
||||
// activate in Main 2 hoping that the extra mana surplus will make a difference
|
||||
// if there are some nonland permanents in hand
|
||||
CardCollectionView permanents = CardLists.filter(ai.getCardsIn(ZoneType.Hand),
|
||||
CardPredicates.Presets.NONLAND_PERMANENTS);
|
||||
|
||||
return permanents.size() > 0 && ph.is(PhaseType.MAIN2, ai);
|
||||
}
|
||||
}
|
||||
|
||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory || canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class ChooseColorAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return false;
|
||||
}
|
||||
final String logic = sa.getParam("AILogic");
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("Nykthos, Shrine to Nyx".equals(sourceName)) {
|
||||
return SpecialCardAi.NykthosShrineToNyx.consider(ai, sa);
|
||||
}
|
||||
|
||||
if ("Oona, Queen of the Fae".equals(sourceName)) {
|
||||
if (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
// Set PayX here to maximum value.
|
||||
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(x));
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("Addle".equals(sourceName)) {
|
||||
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 = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
ailist = CardLists.filter(ailist, CardPredicates.isColor(color));
|
||||
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));
|
||||
|
||||
int excess = ComputerUtilCard.evaluatePermanentList(opplist) - ComputerUtilCard.evaluatePermanentList(ailist);
|
||||
if (excess > 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (logic.equals("MostProminentInComputerDeck")) {
|
||||
if ("Astral Cornucopia".equals(sourceName)) {
|
||||
// activate in Main 2 hoping that the extra mana surplus will make a difference
|
||||
// if there are some nonland permanents in hand
|
||||
CardCollectionView permanents = CardLists.filter(ai.getCardsIn(ZoneType.Hand),
|
||||
CardPredicates.Presets.NONLAND_PERMANENTS);
|
||||
|
||||
return permanents.size() > 0 && ph.is(PhaseType.MAIN2, ai);
|
||||
}
|
||||
}
|
||||
|
||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory || canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ChooseDirectionAi 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 String logic = sa.getParam("AILogic");
|
||||
if (logic == null) {
|
||||
return false;
|
||||
} else {
|
||||
// TODO: default ai
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ChooseDirectionAi 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 String logic = sa.getParam("AILogic");
|
||||
if (logic == null) {
|
||||
return false;
|
||||
} else {
|
||||
// TODO: default ai
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,342 +1,342 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.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.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;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
|
||||
public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) {
|
||||
return true;
|
||||
} else if (aiLogic.startsWith("Fabricate")) {
|
||||
return true;
|
||||
} else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) {
|
||||
for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) {
|
||||
if (SpellApiToAi.Converter.get(sb.getApi()).canPlayAIWithSubs(ai, sb)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
return sa.hasParam("AILogic");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
if ("CombustibleGearhulk".equals(sa.getParam("AILogic"))) {
|
||||
for (final Player p : aiPlayer.getOpponents()) {
|
||||
if (p.canBeTargetedBy(sa)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(p);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true; // perhaps the opponent(s) had Sigarda, Heron's Grace or another effect giving hexproof in play, still play the creature as 6/6
|
||||
}
|
||||
|
||||
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
Card host = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final Game game = host.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
final String logic = sa.getParam("AILogic");
|
||||
if (logic == null) {
|
||||
return spells.get(0);
|
||||
} else if ("Random".equals(logic)) {
|
||||
return Aggregates.random(spells);
|
||||
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
|
||||
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() {
|
||||
@Override
|
||||
public boolean apply(final SpellAbility sp) {
|
||||
return !sp.getDescription().contains("Creature") && !sp.getDescription().contains("Land");
|
||||
}
|
||||
}));
|
||||
return Aggregates.random(filtered);
|
||||
} else if ("PayUnlessCost".equals(logic)) {
|
||||
for (final SpellAbility sp : spells) {
|
||||
String unlessCost = sp.getParam("UnlessCost");
|
||||
sp.setActivatingPlayer(sa.getActivatingPlayer());
|
||||
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>(player))
|
||||
&& ComputerUtilCost.canPayCost(paycost, player)) {
|
||||
return sp;
|
||||
}
|
||||
}
|
||||
return spells.get(0);
|
||||
} else if ("Khans".equals(logic) || "Dragons".equals(logic)) { // Fate Reforged sieges
|
||||
for (final SpellAbility sp : spells) {
|
||||
if (sp.getDescription().equals(logic)) {
|
||||
return sp;
|
||||
}
|
||||
}
|
||||
} else if ("SelfOthers".equals(logic)) {
|
||||
SpellAbility self = null, others = null;
|
||||
for (final SpellAbility sp : spells) {
|
||||
if (sp.getDescription().equals("Self")) {
|
||||
self = sp;
|
||||
} else {
|
||||
others = sp;
|
||||
}
|
||||
}
|
||||
String hostname = host.getName();
|
||||
if (hostname.equals("May Civilization Collapse")) {
|
||||
if (player.getLandsInPlay().isEmpty()) {
|
||||
return self;
|
||||
}
|
||||
} else if (hostname.equals("Feed the Machine")) {
|
||||
if (player.getCreaturesInPlay().isEmpty()) {
|
||||
return self;
|
||||
}
|
||||
} else if (hostname.equals("Surrender Your Thoughts")) {
|
||||
if (player.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||
return self;
|
||||
}
|
||||
} else if (hostname.equals("The Fate of the Flammable")) {
|
||||
if (!player.canLoseLife()) {
|
||||
return self;
|
||||
}
|
||||
}
|
||||
return others;
|
||||
} else if ("Fatespinner".equals(logic)) {
|
||||
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
|
||||
for (final SpellAbility sp : spells) {
|
||||
if (sp.getDescription().equals("FatespinnerSkipDraw")) {
|
||||
skipDraw = sp;
|
||||
} else if (sp.getDescription().equals("FatespinnerSkipMain")) {
|
||||
//skipMain = sp;
|
||||
} else {
|
||||
skipCombat = sp;
|
||||
}
|
||||
}
|
||||
// FatespinnerSkipDraw,FatespinnerSkipMain,FatespinnerSkipCombat
|
||||
if (player.hasKeyword("Skip your draw step.")) {
|
||||
return skipDraw;
|
||||
}
|
||||
if (player.hasKeyword("Skip your next combat phase.")) {
|
||||
return skipCombat;
|
||||
}
|
||||
|
||||
// TODO If combat is poor, Skip Combat
|
||||
// Todo if hand is empty or mostly empty, skip main phase
|
||||
// Todo if hand has gas, skip draw
|
||||
return Aggregates.random(spells);
|
||||
|
||||
} else if ("SinProdder".equals(logic)) {
|
||||
SpellAbility allow = null, deny = null;
|
||||
for (final SpellAbility sp : spells) {
|
||||
if (sp.getDescription().equals("Allow")) {
|
||||
allow = sp;
|
||||
} else {
|
||||
deny = sp;
|
||||
}
|
||||
}
|
||||
|
||||
Card imprinted = host.getImprintedCards().getFirst();
|
||||
int dmg = imprinted.getCMC();
|
||||
Player owner = imprinted.getOwner();
|
||||
|
||||
//useless cards in hand
|
||||
if (imprinted.getName().equals("Bridge from Below") ||
|
||||
imprinted.getName().equals("Haakon, Stromgald Scourge")) {
|
||||
return allow;
|
||||
}
|
||||
|
||||
//bad cards when are thrown from the library to the graveyard, but Yixlid can prevent that
|
||||
if (!player.getGame().isCardInPlay("Yixlid Jailer") && (
|
||||
imprinted.getName().equals("Gaea's Blessing") ||
|
||||
imprinted.getName().equals("Narcomoeba"))) {
|
||||
return allow;
|
||||
}
|
||||
|
||||
// milling against Tamiyo is pointless
|
||||
if (owner.isCardInCommand("Emblem - Tamiyo, the Moon Sage")) {
|
||||
return allow;
|
||||
}
|
||||
|
||||
// milling a land against Gitrog result in card draw
|
||||
if (imprinted.isLand() && owner.isCardInPlay("The Gitrog Monster")) {
|
||||
// try to mill owner
|
||||
if (owner.getCardsIn(ZoneType.Library).size() < 5) {
|
||||
return deny;
|
||||
}
|
||||
return allow;
|
||||
}
|
||||
|
||||
// milling a creature against Sidisi result in more creatures
|
||||
if (imprinted.isCreature() && owner.isCardInPlay("Sidisi, Brood Tyrant")) {
|
||||
return allow;
|
||||
}
|
||||
|
||||
//if Iona does prevent from casting, allow it to draw
|
||||
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
|
||||
if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
|
||||
return allow;
|
||||
}
|
||||
}
|
||||
|
||||
if (dmg == 0) {
|
||||
// If CMC = 0, mill it!
|
||||
return deny;
|
||||
} else if (dmg + 3 > player.getLife()) {
|
||||
// if low on life, do nothing.
|
||||
return allow;
|
||||
} else if (player.getLife() - dmg > 15) {
|
||||
// TODO Check "danger" level of card
|
||||
// If lots of life, and card might be dangerous? Mill it!
|
||||
return deny;
|
||||
}
|
||||
// if unsure, random?
|
||||
return Aggregates.random(spells);
|
||||
} else if (logic.startsWith("Fabricate")) {
|
||||
final int n = Integer.valueOf(logic.substring("Fabricate".length()));
|
||||
SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1);
|
||||
|
||||
// check for something which might prevent the counters to be placed on host
|
||||
if (!host.canReceiveCounters(CounterType.P1P1)) {
|
||||
return tokenSA;
|
||||
}
|
||||
|
||||
// if host would leave the play or if host is useless, create tokens
|
||||
if (host.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(player, host)) {
|
||||
return tokenSA;
|
||||
}
|
||||
|
||||
// need a copy for one with extra +1/+1 counter boost,
|
||||
// without causing triggers to run
|
||||
final Card copy = CardUtil.getLKICopy(host);
|
||||
copy.setCounters(CounterType.P1P1, copy.getCounters(CounterType.P1P1) + n);
|
||||
copy.setZone(host.getZone());
|
||||
|
||||
// if host would put into the battlefield attacking
|
||||
if (combat != null && combat.isAttacking(host)) {
|
||||
final Player defender = combat.getDefenderPlayerByAttacker(host);
|
||||
if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy)) {
|
||||
return counterSA;
|
||||
}
|
||||
return tokenSA;
|
||||
}
|
||||
|
||||
// if the host has haste and can attack
|
||||
if (CombatUtil.canAttack(copy)) {
|
||||
for (final Player opp : player.getOpponents()) {
|
||||
if (CombatUtil.canAttack(copy, opp) &&
|
||||
opp.canLoseLife() &&
|
||||
!ComputerUtilCard.canBeBlockedProfitably(opp, copy))
|
||||
return counterSA;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO check for trigger to turn token ETB into +1/+1 counter for host
|
||||
// TODO check for trigger to turn token ETB into damage or life loss for opponent
|
||||
// in this cases Token might be prefered even if they would not survive
|
||||
final Card tokenCard = TokenAi.spawnToken(player, tokenSA, true);
|
||||
|
||||
// Token would not survive
|
||||
if (tokenCard.getNetToughness() < 1) {
|
||||
return counterSA;
|
||||
}
|
||||
|
||||
// Special Card logic, this one try to median its power with the number of artifacts
|
||||
if ("Marionette Master".equals(sourceName)) {
|
||||
CardCollection list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS);
|
||||
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
|
||||
} else if ("Cultivator of Blades".equals(sourceName)) {
|
||||
// Cultivator does try to median with number of Creatures
|
||||
CardCollection list = player.getCreaturesInPlay();
|
||||
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
|
||||
}
|
||||
|
||||
// evaluate Creature with +1/+1
|
||||
int evalCounter = ComputerUtilCard.evaluateCreature(copy);
|
||||
|
||||
final CardCollection tokenList = new CardCollection(host);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
tokenList.add(TokenAi.spawnToken(player, tokenSA));
|
||||
}
|
||||
|
||||
// evaluate Host with Tokens
|
||||
int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList);
|
||||
|
||||
return evalToken >= evalCounter ? tokenSA : counterSA;
|
||||
} else if ("CombustibleGearhulk".equals(logic)) {
|
||||
Player controller = sa.getActivatingPlayer();
|
||||
List<ZoneType> zones = ZoneType.listValueOf("Graveyard, Battlefield, Exile");
|
||||
int life = player.getLife();
|
||||
CardCollectionView revealedCards = controller.getCardsIn(zones);
|
||||
|
||||
if (revealedCards.size() < 5) {
|
||||
// Not very many revealed cards, just guess based on lifetotal
|
||||
return life < 7 ? spells.get(0) : spells.get(1);
|
||||
}
|
||||
|
||||
int totalCMC = 0;
|
||||
for(Card c : revealedCards) {
|
||||
totalCMC += c.getCMC();
|
||||
}
|
||||
|
||||
int bestGuessDamage = totalCMC * 3 / revealedCards.size();
|
||||
return life <= bestGuessDamage ? spells.get(0) : spells.get(1);
|
||||
} else if ("Pump".equals(logic) || "BestOption".equals(logic)) {
|
||||
List<SpellAbility> filtered = Lists.newArrayList();
|
||||
// filter first for the spells which can be done
|
||||
for (SpellAbility sp : spells) {
|
||||
if (SpellApiToAi.Converter.get(sp.getApi()).canPlayAIWithSubs(player, sp)) {
|
||||
filtered.add(sp);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO find better way to check
|
||||
if (!filtered.isEmpty()) {
|
||||
return filtered.get(0);
|
||||
}
|
||||
}
|
||||
return spells.get(0); // return first choice if no logic found
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.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.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;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
|
||||
public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) {
|
||||
return true;
|
||||
} else if (aiLogic.startsWith("Fabricate")) {
|
||||
return true;
|
||||
} else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) {
|
||||
for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) {
|
||||
if (SpellApiToAi.Converter.get(sb.getApi()).canPlayAIWithSubs(ai, sb)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
return sa.hasParam("AILogic");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
if ("CombustibleGearhulk".equals(sa.getParam("AILogic"))) {
|
||||
for (final Player p : aiPlayer.getOpponents()) {
|
||||
if (p.canBeTargetedBy(sa)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(p);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true; // perhaps the opponent(s) had Sigarda, Heron's Grace or another effect giving hexproof in play, still play the creature as 6/6
|
||||
}
|
||||
|
||||
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
Card host = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final Game game = host.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
final String logic = sa.getParam("AILogic");
|
||||
if (logic == null) {
|
||||
return spells.get(0);
|
||||
} else if ("Random".equals(logic)) {
|
||||
return Aggregates.random(spells);
|
||||
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
|
||||
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() {
|
||||
@Override
|
||||
public boolean apply(final SpellAbility sp) {
|
||||
return !sp.getDescription().contains("Creature") && !sp.getDescription().contains("Land");
|
||||
}
|
||||
}));
|
||||
return Aggregates.random(filtered);
|
||||
} else if ("PayUnlessCost".equals(logic)) {
|
||||
for (final SpellAbility sp : spells) {
|
||||
String unlessCost = sp.getParam("UnlessCost");
|
||||
sp.setActivatingPlayer(sa.getActivatingPlayer());
|
||||
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>(player))
|
||||
&& ComputerUtilCost.canPayCost(paycost, player)) {
|
||||
return sp;
|
||||
}
|
||||
}
|
||||
return spells.get(0);
|
||||
} else if ("Khans".equals(logic) || "Dragons".equals(logic)) { // Fate Reforged sieges
|
||||
for (final SpellAbility sp : spells) {
|
||||
if (sp.getDescription().equals(logic)) {
|
||||
return sp;
|
||||
}
|
||||
}
|
||||
} else if ("SelfOthers".equals(logic)) {
|
||||
SpellAbility self = null, others = null;
|
||||
for (final SpellAbility sp : spells) {
|
||||
if (sp.getDescription().equals("Self")) {
|
||||
self = sp;
|
||||
} else {
|
||||
others = sp;
|
||||
}
|
||||
}
|
||||
String hostname = host.getName();
|
||||
if (hostname.equals("May Civilization Collapse")) {
|
||||
if (player.getLandsInPlay().isEmpty()) {
|
||||
return self;
|
||||
}
|
||||
} else if (hostname.equals("Feed the Machine")) {
|
||||
if (player.getCreaturesInPlay().isEmpty()) {
|
||||
return self;
|
||||
}
|
||||
} else if (hostname.equals("Surrender Your Thoughts")) {
|
||||
if (player.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||
return self;
|
||||
}
|
||||
} else if (hostname.equals("The Fate of the Flammable")) {
|
||||
if (!player.canLoseLife()) {
|
||||
return self;
|
||||
}
|
||||
}
|
||||
return others;
|
||||
} else if ("Fatespinner".equals(logic)) {
|
||||
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
|
||||
for (final SpellAbility sp : spells) {
|
||||
if (sp.getDescription().equals("FatespinnerSkipDraw")) {
|
||||
skipDraw = sp;
|
||||
} else if (sp.getDescription().equals("FatespinnerSkipMain")) {
|
||||
//skipMain = sp;
|
||||
} else {
|
||||
skipCombat = sp;
|
||||
}
|
||||
}
|
||||
// FatespinnerSkipDraw,FatespinnerSkipMain,FatespinnerSkipCombat
|
||||
if (player.hasKeyword("Skip your draw step.")) {
|
||||
return skipDraw;
|
||||
}
|
||||
if (player.hasKeyword("Skip your next combat phase.")) {
|
||||
return skipCombat;
|
||||
}
|
||||
|
||||
// TODO If combat is poor, Skip Combat
|
||||
// Todo if hand is empty or mostly empty, skip main phase
|
||||
// Todo if hand has gas, skip draw
|
||||
return Aggregates.random(spells);
|
||||
|
||||
} else if ("SinProdder".equals(logic)) {
|
||||
SpellAbility allow = null, deny = null;
|
||||
for (final SpellAbility sp : spells) {
|
||||
if (sp.getDescription().equals("Allow")) {
|
||||
allow = sp;
|
||||
} else {
|
||||
deny = sp;
|
||||
}
|
||||
}
|
||||
|
||||
Card imprinted = host.getImprintedCards().getFirst();
|
||||
int dmg = imprinted.getCMC();
|
||||
Player owner = imprinted.getOwner();
|
||||
|
||||
//useless cards in hand
|
||||
if (imprinted.getName().equals("Bridge from Below") ||
|
||||
imprinted.getName().equals("Haakon, Stromgald Scourge")) {
|
||||
return allow;
|
||||
}
|
||||
|
||||
//bad cards when are thrown from the library to the graveyard, but Yixlid can prevent that
|
||||
if (!player.getGame().isCardInPlay("Yixlid Jailer") && (
|
||||
imprinted.getName().equals("Gaea's Blessing") ||
|
||||
imprinted.getName().equals("Narcomoeba"))) {
|
||||
return allow;
|
||||
}
|
||||
|
||||
// milling against Tamiyo is pointless
|
||||
if (owner.isCardInCommand("Emblem - Tamiyo, the Moon Sage")) {
|
||||
return allow;
|
||||
}
|
||||
|
||||
// milling a land against Gitrog result in card draw
|
||||
if (imprinted.isLand() && owner.isCardInPlay("The Gitrog Monster")) {
|
||||
// try to mill owner
|
||||
if (owner.getCardsIn(ZoneType.Library).size() < 5) {
|
||||
return deny;
|
||||
}
|
||||
return allow;
|
||||
}
|
||||
|
||||
// milling a creature against Sidisi result in more creatures
|
||||
if (imprinted.isCreature() && owner.isCardInPlay("Sidisi, Brood Tyrant")) {
|
||||
return allow;
|
||||
}
|
||||
|
||||
//if Iona does prevent from casting, allow it to draw
|
||||
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
|
||||
if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
|
||||
return allow;
|
||||
}
|
||||
}
|
||||
|
||||
if (dmg == 0) {
|
||||
// If CMC = 0, mill it!
|
||||
return deny;
|
||||
} else if (dmg + 3 > player.getLife()) {
|
||||
// if low on life, do nothing.
|
||||
return allow;
|
||||
} else if (player.getLife() - dmg > 15) {
|
||||
// TODO Check "danger" level of card
|
||||
// If lots of life, and card might be dangerous? Mill it!
|
||||
return deny;
|
||||
}
|
||||
// if unsure, random?
|
||||
return Aggregates.random(spells);
|
||||
} else if (logic.startsWith("Fabricate")) {
|
||||
final int n = Integer.valueOf(logic.substring("Fabricate".length()));
|
||||
SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1);
|
||||
|
||||
// check for something which might prevent the counters to be placed on host
|
||||
if (!host.canReceiveCounters(CounterType.P1P1)) {
|
||||
return tokenSA;
|
||||
}
|
||||
|
||||
// if host would leave the play or if host is useless, create tokens
|
||||
if (host.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(player, host)) {
|
||||
return tokenSA;
|
||||
}
|
||||
|
||||
// need a copy for one with extra +1/+1 counter boost,
|
||||
// without causing triggers to run
|
||||
final Card copy = CardUtil.getLKICopy(host);
|
||||
copy.setCounters(CounterType.P1P1, copy.getCounters(CounterType.P1P1) + n);
|
||||
copy.setZone(host.getZone());
|
||||
|
||||
// if host would put into the battlefield attacking
|
||||
if (combat != null && combat.isAttacking(host)) {
|
||||
final Player defender = combat.getDefenderPlayerByAttacker(host);
|
||||
if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy)) {
|
||||
return counterSA;
|
||||
}
|
||||
return tokenSA;
|
||||
}
|
||||
|
||||
// if the host has haste and can attack
|
||||
if (CombatUtil.canAttack(copy)) {
|
||||
for (final Player opp : player.getOpponents()) {
|
||||
if (CombatUtil.canAttack(copy, opp) &&
|
||||
opp.canLoseLife() &&
|
||||
!ComputerUtilCard.canBeBlockedProfitably(opp, copy))
|
||||
return counterSA;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO check for trigger to turn token ETB into +1/+1 counter for host
|
||||
// TODO check for trigger to turn token ETB into damage or life loss for opponent
|
||||
// in this cases Token might be prefered even if they would not survive
|
||||
final Card tokenCard = TokenAi.spawnToken(player, tokenSA, true);
|
||||
|
||||
// Token would not survive
|
||||
if (tokenCard.getNetToughness() < 1) {
|
||||
return counterSA;
|
||||
}
|
||||
|
||||
// Special Card logic, this one try to median its power with the number of artifacts
|
||||
if ("Marionette Master".equals(sourceName)) {
|
||||
CardCollection list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS);
|
||||
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
|
||||
} else if ("Cultivator of Blades".equals(sourceName)) {
|
||||
// Cultivator does try to median with number of Creatures
|
||||
CardCollection list = player.getCreaturesInPlay();
|
||||
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
|
||||
}
|
||||
|
||||
// evaluate Creature with +1/+1
|
||||
int evalCounter = ComputerUtilCard.evaluateCreature(copy);
|
||||
|
||||
final CardCollection tokenList = new CardCollection(host);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
tokenList.add(TokenAi.spawnToken(player, tokenSA));
|
||||
}
|
||||
|
||||
// evaluate Host with Tokens
|
||||
int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList);
|
||||
|
||||
return evalToken >= evalCounter ? tokenSA : counterSA;
|
||||
} else if ("CombustibleGearhulk".equals(logic)) {
|
||||
Player controller = sa.getActivatingPlayer();
|
||||
List<ZoneType> zones = ZoneType.listValueOf("Graveyard, Battlefield, Exile");
|
||||
int life = player.getLife();
|
||||
CardCollectionView revealedCards = controller.getCardsIn(zones);
|
||||
|
||||
if (revealedCards.size() < 5) {
|
||||
// Not very many revealed cards, just guess based on lifetotal
|
||||
return life < 7 ? spells.get(0) : spells.get(1);
|
||||
}
|
||||
|
||||
int totalCMC = 0;
|
||||
for(Card c : revealedCards) {
|
||||
totalCMC += c.getCMC();
|
||||
}
|
||||
|
||||
int bestGuessDamage = totalCMC * 3 / revealedCards.size();
|
||||
return life <= bestGuessDamage ? spells.get(0) : spells.get(1);
|
||||
} else if ("Pump".equals(logic) || "BestOption".equals(logic)) {
|
||||
List<SpellAbility> filtered = Lists.newArrayList();
|
||||
// filter first for the spells which can be done
|
||||
for (SpellAbility sp : spells) {
|
||||
if (SpellApiToAi.Converter.get(sp.getApi()).canPlayAIWithSubs(player, sp)) {
|
||||
filtered.add(sp);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO find better way to check
|
||||
if (!filtered.isEmpty()) {
|
||||
return filtered.get(0);
|
||||
}
|
||||
}
|
||||
return spells.get(0); // return first choice if no logic found
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,36 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class ChooseNumberAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return false;
|
||||
}
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory || canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class ChooseNumberAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return false;
|
||||
}
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory || canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,81 +1,81 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ChoosePlayerAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices) {
|
||||
Player chosen = null;
|
||||
if ("Curse".equals(sa.getParam("AILogic"))) {
|
||||
for (Player pc : choices) {
|
||||
if (pc.isOpponentOf(ai)) {
|
||||
chosen = pc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (chosen == null) {
|
||||
chosen = Iterables.getFirst(choices, null);
|
||||
System.out.println("No good curse choices. Picking first available: " + chosen);
|
||||
}
|
||||
}
|
||||
else if ("Pump".equals(sa.getParam("AILogic"))) {
|
||||
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
|
||||
}
|
||||
else if ("BestAllyBoardPosition".equals(sa.getParam("AILogic"))) {
|
||||
List<Player> prefChoices = Lists.newArrayList(choices);
|
||||
prefChoices.removeAll(ai.getOpponents());
|
||||
if (!prefChoices.isEmpty()) {
|
||||
chosen = ComputerUtil.evaluateBoardPosition(prefChoices);
|
||||
}
|
||||
if (chosen == null) {
|
||||
chosen = Iterables.getFirst(choices, null);
|
||||
System.out.println("No good curse choices. Picking first available: " + chosen);
|
||||
}
|
||||
} else if ("MostCardsInHand".equals(sa.getParam("AILogic"))) {
|
||||
int cardsInHand = 0;
|
||||
for (final Player p : choices) {
|
||||
int hand = p.getCardsIn(ZoneType.Hand).size();
|
||||
if (hand >= cardsInHand) {
|
||||
chosen = p;
|
||||
cardsInHand = hand;
|
||||
}
|
||||
}
|
||||
} else if ("LeastCreatures".equals(sa.getParam("AILogic"))) {
|
||||
int creats = 50;
|
||||
for (final Player p : choices) {
|
||||
int curr = p.getCreaturesInPlay().size();
|
||||
if (curr <= creats) {
|
||||
chosen = p;
|
||||
creats = curr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.out.println("Default player choice logic.");
|
||||
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
|
||||
}
|
||||
return chosen;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ChoosePlayerAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices) {
|
||||
Player chosen = null;
|
||||
if ("Curse".equals(sa.getParam("AILogic"))) {
|
||||
for (Player pc : choices) {
|
||||
if (pc.isOpponentOf(ai)) {
|
||||
chosen = pc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (chosen == null) {
|
||||
chosen = Iterables.getFirst(choices, null);
|
||||
System.out.println("No good curse choices. Picking first available: " + chosen);
|
||||
}
|
||||
}
|
||||
else if ("Pump".equals(sa.getParam("AILogic"))) {
|
||||
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
|
||||
}
|
||||
else if ("BestAllyBoardPosition".equals(sa.getParam("AILogic"))) {
|
||||
List<Player> prefChoices = Lists.newArrayList(choices);
|
||||
prefChoices.removeAll(ai.getOpponents());
|
||||
if (!prefChoices.isEmpty()) {
|
||||
chosen = ComputerUtil.evaluateBoardPosition(prefChoices);
|
||||
}
|
||||
if (chosen == null) {
|
||||
chosen = Iterables.getFirst(choices, null);
|
||||
System.out.println("No good curse choices. Picking first available: " + chosen);
|
||||
}
|
||||
} else if ("MostCardsInHand".equals(sa.getParam("AILogic"))) {
|
||||
int cardsInHand = 0;
|
||||
for (final Player p : choices) {
|
||||
int hand = p.getCardsIn(ZoneType.Hand).size();
|
||||
if (hand >= cardsInHand) {
|
||||
chosen = p;
|
||||
cardsInHand = hand;
|
||||
}
|
||||
}
|
||||
} else if ("LeastCreatures".equals(sa.getParam("AILogic"))) {
|
||||
int creats = 50;
|
||||
for (final Player p : choices) {
|
||||
int curr = p.getCreaturesInPlay().size();
|
||||
if (curr <= creats) {
|
||||
chosen = p;
|
||||
creats = curr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.out.println("Default player choice logic.");
|
||||
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
|
||||
}
|
||||
return chosen;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,196 +1,196 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class ChooseSourceAi 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(final Player ai, SpellAbility sa) {
|
||||
// TODO: AI Support! Currently this is copied from AF ChooseCard.
|
||||
// When implementing AI, I believe AI also needs to be made aware of the damage sources chosen
|
||||
// to be prevented (e.g. so the AI doesn't attack with a creature that will not deal any damage
|
||||
// to the player because a CoP was pre-activated on it - unless, of course, there's another
|
||||
// possible reason to attack with that creature).
|
||||
final Card host = sa.getHostCard();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("AILogic")) {
|
||||
final Game game = ai.getGame();
|
||||
if (sa.getParam("AILogic").equals("NeedsPrevention")) {
|
||||
if (!game.getStack().isEmpty()) {
|
||||
final SpellAbility topStack = game.getStack().peekAbility();
|
||||
if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
final ApiType threatApi = topStack.getApi();
|
||||
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card threatSource = topStack.getHostCard();
|
||||
List<? extends GameObject> objects = getTargets(topStack);
|
||||
if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) {
|
||||
objects = AbilityUtils.getDefinedPlayers(threatSource, topStack.getParam("ValidPlayers"), topStack);
|
||||
}
|
||||
|
||||
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
|
||||
return false;
|
||||
}
|
||||
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
|
||||
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
||||
return false;
|
||||
}
|
||||
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
|
||||
if (sa.hasParam("Choices")) {
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||
}
|
||||
final Combat combat = game.getCombat();
|
||||
choices = CardLists.filter(choices, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||
return false;
|
||||
}
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
|
||||
}
|
||||
});
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
if (!game.getStack().isEmpty()) {
|
||||
Card choseCard = chooseCardOnStack(sa, ai, game);
|
||||
if (choseCard != null) {
|
||||
return choseCard;
|
||||
}
|
||||
}
|
||||
|
||||
final Combat combat = game.getCombat();
|
||||
|
||||
List<Card> permanentSources = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield
|
||||
|| combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||
return false;
|
||||
}
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
|
||||
}
|
||||
});
|
||||
return ComputerUtilCard.getBestCreatureAI(permanentSources);
|
||||
|
||||
} else {
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
}
|
||||
|
||||
private Card chooseCardOnStack(SpellAbility sa, Player ai, Game game) {
|
||||
for (SpellAbilityStackInstance si : game.getStack()) {
|
||||
final Card source = si.getSourceCard();
|
||||
final SpellAbility abilityOnStack = si.getSpellAbility(true);
|
||||
|
||||
if (sa.hasParam("Choices") && !abilityOnStack.getHostCard().isValid(sa.getParam("Choices"), ai, sa.getHostCard(), sa)) {
|
||||
continue;
|
||||
}
|
||||
final ApiType threatApi = abilityOnStack.getApi();
|
||||
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<? extends GameObject> objects = getTargets(abilityOnStack);
|
||||
|
||||
if (!abilityOnStack.usesTargeting() && !abilityOnStack.hasParam("Defined") && abilityOnStack.hasParam("ValidPlayers"))
|
||||
objects = AbilityUtils.getDefinedPlayers(source, abilityOnStack.getParam("ValidPlayers"), abilityOnStack);
|
||||
|
||||
if (!objects.contains(ai) || abilityOnStack.hasParam("NoPrevention")) {
|
||||
continue;
|
||||
}
|
||||
int dmg = AbilityUtils.calculateAmount(source, abilityOnStack.getParam("NumDmg"), abilityOnStack);
|
||||
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) {
|
||||
continue;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<GameObject> getTargets(final SpellAbility sa) {
|
||||
return sa.usesTargeting() && (!sa.hasParam("Defined"))
|
||||
? Lists.newArrayList(sa.getTargets().getTargets())
|
||||
: AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class ChooseSourceAi 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(final Player ai, SpellAbility sa) {
|
||||
// TODO: AI Support! Currently this is copied from AF ChooseCard.
|
||||
// When implementing AI, I believe AI also needs to be made aware of the damage sources chosen
|
||||
// to be prevented (e.g. so the AI doesn't attack with a creature that will not deal any damage
|
||||
// to the player because a CoP was pre-activated on it - unless, of course, there's another
|
||||
// possible reason to attack with that creature).
|
||||
final Card host = sa.getHostCard();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("AILogic")) {
|
||||
final Game game = ai.getGame();
|
||||
if (sa.getParam("AILogic").equals("NeedsPrevention")) {
|
||||
if (!game.getStack().isEmpty()) {
|
||||
final SpellAbility topStack = game.getStack().peekAbility();
|
||||
if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
final ApiType threatApi = topStack.getApi();
|
||||
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card threatSource = topStack.getHostCard();
|
||||
List<? extends GameObject> objects = getTargets(topStack);
|
||||
if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) {
|
||||
objects = AbilityUtils.getDefinedPlayers(threatSource, topStack.getParam("ValidPlayers"), topStack);
|
||||
}
|
||||
|
||||
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
|
||||
return false;
|
||||
}
|
||||
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
|
||||
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
||||
return false;
|
||||
}
|
||||
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
|
||||
if (sa.hasParam("Choices")) {
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||
}
|
||||
final Combat combat = game.getCombat();
|
||||
choices = CardLists.filter(choices, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||
return false;
|
||||
}
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
|
||||
}
|
||||
});
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
if (!game.getStack().isEmpty()) {
|
||||
Card choseCard = chooseCardOnStack(sa, ai, game);
|
||||
if (choseCard != null) {
|
||||
return choseCard;
|
||||
}
|
||||
}
|
||||
|
||||
final Combat combat = game.getCombat();
|
||||
|
||||
List<Card> permanentSources = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield
|
||||
|| combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||
return false;
|
||||
}
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
|
||||
}
|
||||
});
|
||||
return ComputerUtilCard.getBestCreatureAI(permanentSources);
|
||||
|
||||
} else {
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
}
|
||||
|
||||
private Card chooseCardOnStack(SpellAbility sa, Player ai, Game game) {
|
||||
for (SpellAbilityStackInstance si : game.getStack()) {
|
||||
final Card source = si.getSourceCard();
|
||||
final SpellAbility abilityOnStack = si.getSpellAbility(true);
|
||||
|
||||
if (sa.hasParam("Choices") && !abilityOnStack.getHostCard().isValid(sa.getParam("Choices"), ai, sa.getHostCard(), sa)) {
|
||||
continue;
|
||||
}
|
||||
final ApiType threatApi = abilityOnStack.getApi();
|
||||
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<? extends GameObject> objects = getTargets(abilityOnStack);
|
||||
|
||||
if (!abilityOnStack.usesTargeting() && !abilityOnStack.hasParam("Defined") && abilityOnStack.hasParam("ValidPlayers"))
|
||||
objects = AbilityUtils.getDefinedPlayers(source, abilityOnStack.getParam("ValidPlayers"), abilityOnStack);
|
||||
|
||||
if (!objects.contains(ai) || abilityOnStack.hasParam("NoPrevention")) {
|
||||
continue;
|
||||
}
|
||||
int dmg = AbilityUtils.calculateAmount(source, abilityOnStack.getParam("NumDmg"), abilityOnStack);
|
||||
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) {
|
||||
continue;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<GameObject> getTargets(final SpellAbility sa) {
|
||||
return sa.usesTargeting() && (!sa.hasParam("Defined"))
|
||||
? Lists.newArrayList(sa.getTargets().getTargets())
|
||||
: AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,130 +1,130 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardType;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import java.util.List;
|
||||
|
||||
public class ChooseTypeAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return false;
|
||||
} else if ("MostProminentComputerControls".equals(sa.getParam("AILogic"))) {
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
|
||||
return doMirrorEntityLogic(aiPlayer, sa);
|
||||
}
|
||||
}
|
||||
|
||||
return doTriggerAINoCost(aiPlayer, sa, false);
|
||||
}
|
||||
|
||||
private boolean doMirrorEntityLogic(Player aiPlayer, SpellAbility sa) {
|
||||
if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN)) {
|
||||
return false;
|
||||
}
|
||||
if (!aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1, aiPlayer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CardCollectionView otb = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||
List<String> valid = Lists.newArrayList(CardType.getAllCreatureTypes());
|
||||
|
||||
String chosenType = ComputerUtilCard.getMostProminentType(otb, valid);
|
||||
if (chosenType.isEmpty()) {
|
||||
// Account for the situation when only changelings are on the battlefield
|
||||
boolean allChangeling = false;
|
||||
for (Card c : otb) {
|
||||
if (c.isCreature() && c.hasStartOfKeyword(Keyword.CHANGELING.toString())) {
|
||||
chosenType = Aggregates.random(valid); // just choose a random type for changelings
|
||||
allChangeling = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allChangeling) {
|
||||
// Still empty, probably no creatures on board
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int maxX = ComputerUtilMana.determineMaxAffordableX(aiPlayer, sa);
|
||||
int avgPower = 0;
|
||||
|
||||
// predict the opposition
|
||||
CardCollection oppCreatures = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), CardPredicates.Presets.UNTAPPED);
|
||||
int maxOppPower = 0;
|
||||
int maxOppToughness = 0;
|
||||
int oppUsefulCreatures = 0;
|
||||
|
||||
for (Card oppCre : oppCreatures) {
|
||||
if (ComputerUtilCard.isUselessCreature(aiPlayer, oppCre)) {
|
||||
continue;
|
||||
}
|
||||
if (oppCre.getNetPower() > maxOppPower) {
|
||||
maxOppPower = oppCre.getNetPower();
|
||||
}
|
||||
if (oppCre.getNetToughness() > maxOppToughness) {
|
||||
maxOppToughness = oppCre.getNetToughness();
|
||||
}
|
||||
oppUsefulCreatures++;
|
||||
}
|
||||
|
||||
if (maxX > 1) {
|
||||
CardCollection cre = CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield),
|
||||
Predicates.and(CardPredicates.isType(chosenType), CardPredicates.Presets.UNTAPPED));
|
||||
if (!cre.isEmpty()) {
|
||||
for (Card c: cre) {
|
||||
avgPower += c.getNetPower();
|
||||
}
|
||||
avgPower /= cre.size();
|
||||
|
||||
boolean overpower = cre.size() > oppUsefulCreatures;
|
||||
if (!overpower) {
|
||||
maxX = Math.max(0, maxX - 3); // conserve some mana unless the board position looks overpowering
|
||||
}
|
||||
|
||||
if (maxX > avgPower && maxX > maxOppPower && maxX >= maxOppToughness) {
|
||||
sa.setSVar("PayX", String.valueOf(maxX));
|
||||
AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
for (final Player p : AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa)) {
|
||||
if (p.isOpponentOf(ai) && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardType;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import java.util.List;
|
||||
|
||||
public class ChooseTypeAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return false;
|
||||
} else if ("MostProminentComputerControls".equals(sa.getParam("AILogic"))) {
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
|
||||
return doMirrorEntityLogic(aiPlayer, sa);
|
||||
}
|
||||
}
|
||||
|
||||
return doTriggerAINoCost(aiPlayer, sa, false);
|
||||
}
|
||||
|
||||
private boolean doMirrorEntityLogic(Player aiPlayer, SpellAbility sa) {
|
||||
if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN)) {
|
||||
return false;
|
||||
}
|
||||
if (!aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1, aiPlayer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CardCollectionView otb = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||
List<String> valid = Lists.newArrayList(CardType.getAllCreatureTypes());
|
||||
|
||||
String chosenType = ComputerUtilCard.getMostProminentType(otb, valid);
|
||||
if (chosenType.isEmpty()) {
|
||||
// Account for the situation when only changelings are on the battlefield
|
||||
boolean allChangeling = false;
|
||||
for (Card c : otb) {
|
||||
if (c.isCreature() && c.hasStartOfKeyword(Keyword.CHANGELING.toString())) {
|
||||
chosenType = Aggregates.random(valid); // just choose a random type for changelings
|
||||
allChangeling = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allChangeling) {
|
||||
// Still empty, probably no creatures on board
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int maxX = ComputerUtilMana.determineMaxAffordableX(aiPlayer, sa);
|
||||
int avgPower = 0;
|
||||
|
||||
// predict the opposition
|
||||
CardCollection oppCreatures = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), CardPredicates.Presets.UNTAPPED);
|
||||
int maxOppPower = 0;
|
||||
int maxOppToughness = 0;
|
||||
int oppUsefulCreatures = 0;
|
||||
|
||||
for (Card oppCre : oppCreatures) {
|
||||
if (ComputerUtilCard.isUselessCreature(aiPlayer, oppCre)) {
|
||||
continue;
|
||||
}
|
||||
if (oppCre.getNetPower() > maxOppPower) {
|
||||
maxOppPower = oppCre.getNetPower();
|
||||
}
|
||||
if (oppCre.getNetToughness() > maxOppToughness) {
|
||||
maxOppToughness = oppCre.getNetToughness();
|
||||
}
|
||||
oppUsefulCreatures++;
|
||||
}
|
||||
|
||||
if (maxX > 1) {
|
||||
CardCollection cre = CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield),
|
||||
Predicates.and(CardPredicates.isType(chosenType), CardPredicates.Presets.UNTAPPED));
|
||||
if (!cre.isEmpty()) {
|
||||
for (Card c: cre) {
|
||||
avgPower += c.getNetPower();
|
||||
}
|
||||
avgPower /= cre.size();
|
||||
|
||||
boolean overpower = cre.size() > oppUsefulCreatures;
|
||||
if (!overpower) {
|
||||
maxX = Math.max(0, maxX - 3); // conserve some mana unless the board position looks overpowering
|
||||
}
|
||||
|
||||
if (maxX > avgPower && maxX > maxOppPower && maxX >= maxOppToughness) {
|
||||
sa.setSVar("PayX", String.valueOf(maxX));
|
||||
AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
for (final Player p : AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa)) {
|
||||
if (p.isOpponentOf(ai) && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,110 +1,110 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class ClashAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
boolean legalAction = true;
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
legalAction = selectTarget(aiPlayer, sa);
|
||||
}
|
||||
|
||||
return legalAction;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
boolean legalAction = true;
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
legalAction = selectTarget(ai, sa);
|
||||
}
|
||||
|
||||
return legalAction;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, java.lang.Iterable)
|
||||
*/
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
for (Player p : options) {
|
||||
if (p.getCardsIn(ZoneType.Library).size() == 0)
|
||||
return p;
|
||||
}
|
||||
|
||||
CardCollectionView col = ai.getCardsIn(ZoneType.Library);
|
||||
if (!col.isEmpty() && col.getFirst().mayPlayerLook(ai)) {
|
||||
final Card top = col.get(0);
|
||||
for (Player p : options) {
|
||||
final Card oppTop = p.getCardsIn(ZoneType.Library).getFirst();
|
||||
// TODO add logic for SplitCards
|
||||
if (top.getCMC() > oppTop.getCMC()) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
private boolean selectTarget(Player ai, SpellAbility sa) {
|
||||
String valid = sa.getParam("ValidTgts");
|
||||
|
||||
PlayerCollection players = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
// use chooseSinglePlayer function to the select player
|
||||
Player chosen = chooseSinglePlayer(ai, sa, players);
|
||||
if (chosen != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(chosen);
|
||||
}
|
||||
|
||||
if ("Creature".equals(valid)) {
|
||||
// Springjack Knight
|
||||
// TODO: Whirlpool Whelm also uses creature targeting but it's trickier to support
|
||||
CardCollectionView aiCreats = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||
CardCollectionView oppCreats = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||
|
||||
Card tgt = aiCreats.isEmpty() ? ComputerUtilCard.getWorstCreatureAI(oppCreats) : ComputerUtilCard.getBestCreatureAI(aiCreats);
|
||||
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(tgt);
|
||||
} else {
|
||||
return false; // cut short if this part of the clause is not satisfiable (with current card pool should never get here)
|
||||
}
|
||||
}
|
||||
|
||||
return sa.getTargets().getNumTargeted() > 0;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class ClashAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
boolean legalAction = true;
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
legalAction = selectTarget(aiPlayer, sa);
|
||||
}
|
||||
|
||||
return legalAction;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
boolean legalAction = true;
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
legalAction = selectTarget(ai, sa);
|
||||
}
|
||||
|
||||
return legalAction;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, java.lang.Iterable)
|
||||
*/
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
for (Player p : options) {
|
||||
if (p.getCardsIn(ZoneType.Library).size() == 0)
|
||||
return p;
|
||||
}
|
||||
|
||||
CardCollectionView col = ai.getCardsIn(ZoneType.Library);
|
||||
if (!col.isEmpty() && col.getFirst().mayPlayerLook(ai)) {
|
||||
final Card top = col.get(0);
|
||||
for (Player p : options) {
|
||||
final Card oppTop = p.getCardsIn(ZoneType.Library).getFirst();
|
||||
// TODO add logic for SplitCards
|
||||
if (top.getCMC() > oppTop.getCMC()) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
private boolean selectTarget(Player ai, SpellAbility sa) {
|
||||
String valid = sa.getParam("ValidTgts");
|
||||
|
||||
PlayerCollection players = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
// use chooseSinglePlayer function to the select player
|
||||
Player chosen = chooseSinglePlayer(ai, sa, players);
|
||||
if (chosen != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(chosen);
|
||||
}
|
||||
|
||||
if ("Creature".equals(valid)) {
|
||||
// Springjack Knight
|
||||
// TODO: Whirlpool Whelm also uses creature targeting but it's trickier to support
|
||||
CardCollectionView aiCreats = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||
CardCollectionView oppCreats = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||
|
||||
Card tgt = aiCreats.isEmpty() ? ComputerUtilCard.getWorstCreatureAI(oppCreats) : ComputerUtilCard.getBestCreatureAI(aiCreats);
|
||||
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(tgt);
|
||||
} else {
|
||||
return false; // cut short if this part of the clause is not satisfiable (with current card pool should never get here)
|
||||
}
|
||||
}
|
||||
|
||||
return sa.getTargets().getNumTargeted() > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,195 +1,195 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CloneAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = source.getGame();
|
||||
|
||||
boolean useAbility = true;
|
||||
|
||||
// if (card.getController().isComputer()) {
|
||||
// final List<Card> creatures = AllZoneUtil.getCreaturesInPlay();
|
||||
// if (!creatures.isEmpty()) {
|
||||
// cardToCopy = CardFactoryUtil.getBestCreatureAI(creatures);
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO - add some kind of check to answer
|
||||
// "Am I going to attack with this?"
|
||||
// TODO - add some kind of check for during human turn to answer
|
||||
// "Can I use this to block something?"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
for (final Card c : defined) {
|
||||
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
|
||||
|
||||
// for creatures that could be improved (like Figure of Destiny)
|
||||
if (c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
|
||||
int power = -5;
|
||||
if (sa.hasParam("Power")) {
|
||||
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
||||
}
|
||||
int toughness = -5;
|
||||
if (sa.hasParam("Toughness")) {
|
||||
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
|
||||
}
|
||||
if ((power + toughness) > (c.getCurrentPower() + c.getCurrentToughness())) {
|
||||
bFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!bFlag) { // All of the defined stuff is cloned, not very
|
||||
// useful
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
useAbility &= cloneTgtAI(sa);
|
||||
}
|
||||
|
||||
return useAbility;
|
||||
} // end cloneCanPlayAI()
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
// AI should only activate this during Human's turn
|
||||
boolean chance = true;
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
chance = cloneTgtAI(sa);
|
||||
}
|
||||
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
boolean chance = true;
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
chance = cloneTgtAI(sa);
|
||||
}
|
||||
|
||||
// Improve AI for triggers. If source is a creature with:
|
||||
// When ETB, sacrifice a creature. Check to see if the AI has something
|
||||
// to sacrifice
|
||||
|
||||
// Eventually, we can call the trigger of ETB abilities with
|
||||
// not mandatory as part of the checks to cast something
|
||||
|
||||
return chance || mandatory;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* cloneTgtAI.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean cloneTgtAI(final SpellAbility sa) {
|
||||
// Specific logic for cards
|
||||
if ("CloneAttacker".equals(sa.getParam("AILogic"))) {
|
||||
CardCollection valid = CardLists.getValidCards(sa.getHostCard().getController().getCardsIn(ZoneType.Battlefield), sa.getParam("ValidTgts"), sa.getHostCard().getController(), sa.getHostCard());
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(valid));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Default:
|
||||
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
|
||||
// two are the only things
|
||||
// that clone a target. Those can just use SVar:RemAIDeck:True until
|
||||
// this can do a reasonably
|
||||
// good job of picking a good target
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
// Didn't confirm in the original code
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
|
||||
* forge.game.player.Player)
|
||||
*/
|
||||
@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 boolean isVesuva = "Vesuva".equals(host.getName());
|
||||
|
||||
final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
||||
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||
if (!newOptions.isEmpty()) {
|
||||
options = newOptions;
|
||||
}
|
||||
Card choice = ComputerUtilCard.getBestAI(options);
|
||||
if (isVesuva && "Vesuva".equals(choice.getName())) {
|
||||
choice = null;
|
||||
}
|
||||
|
||||
return choice;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CloneAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = source.getGame();
|
||||
|
||||
boolean useAbility = true;
|
||||
|
||||
// if (card.getController().isComputer()) {
|
||||
// final List<Card> creatures = AllZoneUtil.getCreaturesInPlay();
|
||||
// if (!creatures.isEmpty()) {
|
||||
// cardToCopy = CardFactoryUtil.getBestCreatureAI(creatures);
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO - add some kind of check to answer
|
||||
// "Am I going to attack with this?"
|
||||
// TODO - add some kind of check for during human turn to answer
|
||||
// "Can I use this to block something?"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
for (final Card c : defined) {
|
||||
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
|
||||
|
||||
// for creatures that could be improved (like Figure of Destiny)
|
||||
if (c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
|
||||
int power = -5;
|
||||
if (sa.hasParam("Power")) {
|
||||
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
||||
}
|
||||
int toughness = -5;
|
||||
if (sa.hasParam("Toughness")) {
|
||||
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
|
||||
}
|
||||
if ((power + toughness) > (c.getCurrentPower() + c.getCurrentToughness())) {
|
||||
bFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!bFlag) { // All of the defined stuff is cloned, not very
|
||||
// useful
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
useAbility &= cloneTgtAI(sa);
|
||||
}
|
||||
|
||||
return useAbility;
|
||||
} // end cloneCanPlayAI()
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
// AI should only activate this during Human's turn
|
||||
boolean chance = true;
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
chance = cloneTgtAI(sa);
|
||||
}
|
||||
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
boolean chance = true;
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
chance = cloneTgtAI(sa);
|
||||
}
|
||||
|
||||
// Improve AI for triggers. If source is a creature with:
|
||||
// When ETB, sacrifice a creature. Check to see if the AI has something
|
||||
// to sacrifice
|
||||
|
||||
// Eventually, we can call the trigger of ETB abilities with
|
||||
// not mandatory as part of the checks to cast something
|
||||
|
||||
return chance || mandatory;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* cloneTgtAI.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean cloneTgtAI(final SpellAbility sa) {
|
||||
// Specific logic for cards
|
||||
if ("CloneAttacker".equals(sa.getParam("AILogic"))) {
|
||||
CardCollection valid = CardLists.getValidCards(sa.getHostCard().getController().getCardsIn(ZoneType.Battlefield), sa.getParam("ValidTgts"), sa.getHostCard().getController(), sa.getHostCard());
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(valid));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Default:
|
||||
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
|
||||
// two are the only things
|
||||
// that clone a target. Those can just use SVar:RemAIDeck:True until
|
||||
// this can do a reasonably
|
||||
// good job of picking a good target
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
// Didn't confirm in the original code
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
|
||||
* forge.game.player.Player)
|
||||
*/
|
||||
@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 boolean isVesuva = "Vesuva".equals(host.getName());
|
||||
|
||||
final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
||||
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||
if (!newOptions.isEmpty()) {
|
||||
options = newOptions;
|
||||
}
|
||||
Card choice = ComputerUtilCard.getBestAI(options);
|
||||
if (isVesuva && "Vesuva".equals(choice.getName())) {
|
||||
choice = null;
|
||||
}
|
||||
|
||||
return choice;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,113 +1,113 @@
|
||||
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;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class ControlExchangeAi 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, final SpellAbility sa) {
|
||||
Card object1 = null;
|
||||
Card object2 = null;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
|
||||
CardCollection list =
|
||||
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>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !c.hasSVar("RemAIDeck") && c.canBeTargetedBy(sa);
|
||||
}
|
||||
});
|
||||
object1 = ComputerUtilCard.getBestAI(list);
|
||||
if (sa.hasParam("Defined")) {
|
||||
object2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
|
||||
} else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
|
||||
CardCollectionView list2 = ai.getCardsIn(ZoneType.Battlefield);
|
||||
list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
object2 = ComputerUtilCard.getWorstAI(list2);
|
||||
sa.getTargets().add(object2);
|
||||
}
|
||||
if (object1 == null || object2 == null) {
|
||||
return false;
|
||||
}
|
||||
if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) {
|
||||
sa.getTargets().add(object1);
|
||||
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
if (!sa.usesTargeting()) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (mandatory) {
|
||||
return chkAIDrawback(sa, aiPlayer);
|
||||
} else {
|
||||
return canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
if (!sa.usesTargeting()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
|
||||
tgt.getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||
|
||||
// only select the cards that can be targeted
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
if (list.isEmpty())
|
||||
return false;
|
||||
|
||||
Card best = ComputerUtilCard.getBestAI(list);
|
||||
|
||||
// if Param has Defined, check if the best Target is better than the Defined
|
||||
if (sa.hasParam("Defined")) {
|
||||
final Card object = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
|
||||
// TODO add evaluate Land if able
|
||||
final Card realBest = ComputerUtilCard.getBestAI(Lists.newArrayList(best, object));
|
||||
|
||||
// Defined card is better than this one, try to avoid trade
|
||||
if (!best.equals(realBest)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// add best Target
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
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;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class ControlExchangeAi 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, final SpellAbility sa) {
|
||||
Card object1 = null;
|
||||
Card object2 = null;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
|
||||
CardCollection list =
|
||||
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>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !c.hasSVar("RemAIDeck") && c.canBeTargetedBy(sa);
|
||||
}
|
||||
});
|
||||
object1 = ComputerUtilCard.getBestAI(list);
|
||||
if (sa.hasParam("Defined")) {
|
||||
object2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
|
||||
} else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
|
||||
CardCollectionView list2 = ai.getCardsIn(ZoneType.Battlefield);
|
||||
list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
object2 = ComputerUtilCard.getWorstAI(list2);
|
||||
sa.getTargets().add(object2);
|
||||
}
|
||||
if (object1 == null || object2 == null) {
|
||||
return false;
|
||||
}
|
||||
if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) {
|
||||
sa.getTargets().add(object1);
|
||||
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
if (!sa.usesTargeting()) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (mandatory) {
|
||||
return chkAIDrawback(sa, aiPlayer);
|
||||
} else {
|
||||
return canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
if (!sa.usesTargeting()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
|
||||
tgt.getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||
|
||||
// only select the cards that can be targeted
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
if (list.isEmpty())
|
||||
return false;
|
||||
|
||||
Card best = ComputerUtilCard.getBestAI(list);
|
||||
|
||||
// if Param has Defined, check if the best Target is better than the Defined
|
||||
if (sa.hasParam("Defined")) {
|
||||
final Card object = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
|
||||
// TODO add evaluate Land if able
|
||||
final Card realBest = ComputerUtilCard.getBestAI(Lists.newArrayList(best, object));
|
||||
|
||||
// Defined card is better than this one, try to avoid trade
|
||||
if (!best.equals(realBest)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// add best Target
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,319 +1,319 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
|
||||
//AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx
|
||||
|
||||
//GainControl specific sa:
|
||||
// LoseControl - the lose control conditions (as a comma separated list)
|
||||
// -Untap - source card becomes untapped
|
||||
// -LoseControl - you lose control of source card
|
||||
// -LeavesPlay - source card leaves the battlefield
|
||||
// -PowerGT - (not implemented yet for Old Man of the Sea)
|
||||
// AddKWs - Keywords to add to the controlled card
|
||||
// (as a "&"-separated list; like Haste, Sacrifice CARDNAME at EOT, any standard keyword)
|
||||
// OppChoice - set to True if opponent chooses creature (for Preacher) - not implemented yet
|
||||
// Untap - set to True if target card should untap when control is taken
|
||||
// DestroyTgt - actions upon which the tgt should be destroyed. same list as LoseControl
|
||||
// NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactory_GainControl class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
|
||||
*/
|
||||
public class ControlGainAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||
|
||||
final List<String> lose = Lists.newArrayList();
|
||||
|
||||
if (sa.hasParam("LoseControl")) {
|
||||
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Game game = ai.getGame();
|
||||
final FCollectionView<Player> opponents = ai.getOpponents();
|
||||
|
||||
// if Defined, then don't worry about targeting
|
||||
if (tgt == null) {
|
||||
if (sa.hasParam("AllValid")) {
|
||||
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), opponents);
|
||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||
if (tgtCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
if (sa.hasParam("TargetingPlayer")) {
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
sa.setTargetingPlayer(targetingPlayer);
|
||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||
}
|
||||
if (tgt.isRandomTarget()) {
|
||||
sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false)));
|
||||
}
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
List<Player> oppList = Lists
|
||||
.newArrayList(Iterables.filter(opponents, PlayerPredicates.isTargetableBy(sa)));
|
||||
|
||||
if (oppList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sa.getTargets().add(oppList.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
// Don't steal something if I can't Attack without, or prevent it from
|
||||
// blocking at least
|
||||
if (lose.contains("EOT")
|
||||
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& !sa.isTrigger()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("Defined")) {
|
||||
// no need to target, we'll pick up the target from Defined
|
||||
return true;
|
||||
}
|
||||
|
||||
CardCollection list = new CardCollection();
|
||||
for (Player pl : opponents) {
|
||||
list.addAll(pl.getCardsIn(ZoneType.Battlefield));
|
||||
}
|
||||
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
// no valid targets, so we need to bail
|
||||
return false;
|
||||
}
|
||||
|
||||
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (!c.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
}
|
||||
if (sa.isTrigger()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// do not take perm control on something that leaves the play end of turn
|
||||
if (!lose.contains("EOT") && c.hasSVar("EndOfTurnLeavePlay")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c.isCreature()) {
|
||||
if (c.getNetCombatDamage() <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// can not attack any opponent
|
||||
boolean found = false;
|
||||
for (final Player opp : opponents) {
|
||||
if (ComputerUtilCombat.canAttackNextTurn(c, opp)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// do not take control on something it doesn't know how to use
|
||||
return !c.hasSVar("RemAIDeck");
|
||||
}
|
||||
});
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int creatures = 0, artifacts = 0, planeswalkers = 0, lands = 0, enchantments = 0;
|
||||
|
||||
for (final Card c : list) {
|
||||
if (c.isCreature()) {
|
||||
creatures++;
|
||||
}
|
||||
if (c.isArtifact()) {
|
||||
artifacts++;
|
||||
}
|
||||
if (c.isLand()) {
|
||||
lands++;
|
||||
}
|
||||
if (c.isEnchantment()) {
|
||||
enchantments++;
|
||||
}
|
||||
if (c.isPlaneswalker()) {
|
||||
planeswalkers++;
|
||||
}
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
Card t = null;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (t == null) {
|
||||
if (planeswalkers > 0) {
|
||||
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
|
||||
} else if (creatures > 0) {
|
||||
t = ComputerUtilCard.getBestCreatureAI(list);
|
||||
} else if (artifacts > 0) {
|
||||
t = ComputerUtilCard.getBestArtifactAI(list);
|
||||
} else if (lands > 0) {
|
||||
t = ComputerUtilCard.getBestLandAI(list);
|
||||
} else if (enchantments > 0) {
|
||||
t = ComputerUtilCard.getBestEnchantmentAI(list, sa, true);
|
||||
} else {
|
||||
t = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||
}
|
||||
|
||||
if (t != null) {
|
||||
if (t.isCreature())
|
||||
creatures--;
|
||||
if (t.isPlaneswalker())
|
||||
planeswalkers--;
|
||||
if (t.isLand())
|
||||
lands--;
|
||||
if (t.isArtifact())
|
||||
artifacts--;
|
||||
if (t.isEnchantment())
|
||||
enchantments--;
|
||||
}
|
||||
|
||||
if (!sa.canTarget(t)) {
|
||||
list.remove(t);
|
||||
t = null;
|
||||
if (list.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (t != null) {
|
||||
sa.getTargets().add(t);
|
||||
list.remove(t);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if(sa.hasParam("TargetingPlayer") || (!this.canPlayAI(ai, sa) && mandatory)) {
|
||||
List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
} else {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
|
||||
final Game game = ai.getGame();
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
if (sa.hasParam("AllValid")) {
|
||||
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||
if (tgtCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
final List<String> lose = Lists.newArrayList();
|
||||
|
||||
if (sa.hasParam("LoseControl")) {
|
||||
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
|
||||
}
|
||||
|
||||
if (lose.contains("EOT")
|
||||
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return this.canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpDrawbackAI()
|
||||
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
final List<Card> cards = Lists.newArrayList();
|
||||
for (Player p : options) {
|
||||
cards.addAll(p.getCreaturesInPlay());
|
||||
}
|
||||
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
||||
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
|
||||
//AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx
|
||||
|
||||
//GainControl specific sa:
|
||||
// LoseControl - the lose control conditions (as a comma separated list)
|
||||
// -Untap - source card becomes untapped
|
||||
// -LoseControl - you lose control of source card
|
||||
// -LeavesPlay - source card leaves the battlefield
|
||||
// -PowerGT - (not implemented yet for Old Man of the Sea)
|
||||
// AddKWs - Keywords to add to the controlled card
|
||||
// (as a "&"-separated list; like Haste, Sacrifice CARDNAME at EOT, any standard keyword)
|
||||
// OppChoice - set to True if opponent chooses creature (for Preacher) - not implemented yet
|
||||
// Untap - set to True if target card should untap when control is taken
|
||||
// DestroyTgt - actions upon which the tgt should be destroyed. same list as LoseControl
|
||||
// NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactory_GainControl class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
|
||||
*/
|
||||
public class ControlGainAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||
|
||||
final List<String> lose = Lists.newArrayList();
|
||||
|
||||
if (sa.hasParam("LoseControl")) {
|
||||
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Game game = ai.getGame();
|
||||
final FCollectionView<Player> opponents = ai.getOpponents();
|
||||
|
||||
// if Defined, then don't worry about targeting
|
||||
if (tgt == null) {
|
||||
if (sa.hasParam("AllValid")) {
|
||||
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), opponents);
|
||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||
if (tgtCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
if (sa.hasParam("TargetingPlayer")) {
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
sa.setTargetingPlayer(targetingPlayer);
|
||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||
}
|
||||
if (tgt.isRandomTarget()) {
|
||||
sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false)));
|
||||
}
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
List<Player> oppList = Lists
|
||||
.newArrayList(Iterables.filter(opponents, PlayerPredicates.isTargetableBy(sa)));
|
||||
|
||||
if (oppList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sa.getTargets().add(oppList.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
// Don't steal something if I can't Attack without, or prevent it from
|
||||
// blocking at least
|
||||
if (lose.contains("EOT")
|
||||
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& !sa.isTrigger()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("Defined")) {
|
||||
// no need to target, we'll pick up the target from Defined
|
||||
return true;
|
||||
}
|
||||
|
||||
CardCollection list = new CardCollection();
|
||||
for (Player pl : opponents) {
|
||||
list.addAll(pl.getCardsIn(ZoneType.Battlefield));
|
||||
}
|
||||
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
// no valid targets, so we need to bail
|
||||
return false;
|
||||
}
|
||||
|
||||
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (!c.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
}
|
||||
if (sa.isTrigger()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// do not take perm control on something that leaves the play end of turn
|
||||
if (!lose.contains("EOT") && c.hasSVar("EndOfTurnLeavePlay")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c.isCreature()) {
|
||||
if (c.getNetCombatDamage() <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// can not attack any opponent
|
||||
boolean found = false;
|
||||
for (final Player opp : opponents) {
|
||||
if (ComputerUtilCombat.canAttackNextTurn(c, opp)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// do not take control on something it doesn't know how to use
|
||||
return !c.hasSVar("RemAIDeck");
|
||||
}
|
||||
});
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int creatures = 0, artifacts = 0, planeswalkers = 0, lands = 0, enchantments = 0;
|
||||
|
||||
for (final Card c : list) {
|
||||
if (c.isCreature()) {
|
||||
creatures++;
|
||||
}
|
||||
if (c.isArtifact()) {
|
||||
artifacts++;
|
||||
}
|
||||
if (c.isLand()) {
|
||||
lands++;
|
||||
}
|
||||
if (c.isEnchantment()) {
|
||||
enchantments++;
|
||||
}
|
||||
if (c.isPlaneswalker()) {
|
||||
planeswalkers++;
|
||||
}
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
Card t = null;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (t == null) {
|
||||
if (planeswalkers > 0) {
|
||||
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
|
||||
} else if (creatures > 0) {
|
||||
t = ComputerUtilCard.getBestCreatureAI(list);
|
||||
} else if (artifacts > 0) {
|
||||
t = ComputerUtilCard.getBestArtifactAI(list);
|
||||
} else if (lands > 0) {
|
||||
t = ComputerUtilCard.getBestLandAI(list);
|
||||
} else if (enchantments > 0) {
|
||||
t = ComputerUtilCard.getBestEnchantmentAI(list, sa, true);
|
||||
} else {
|
||||
t = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||
}
|
||||
|
||||
if (t != null) {
|
||||
if (t.isCreature())
|
||||
creatures--;
|
||||
if (t.isPlaneswalker())
|
||||
planeswalkers--;
|
||||
if (t.isLand())
|
||||
lands--;
|
||||
if (t.isArtifact())
|
||||
artifacts--;
|
||||
if (t.isEnchantment())
|
||||
enchantments--;
|
||||
}
|
||||
|
||||
if (!sa.canTarget(t)) {
|
||||
list.remove(t);
|
||||
t = null;
|
||||
if (list.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (t != null) {
|
||||
sa.getTargets().add(t);
|
||||
list.remove(t);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if(sa.hasParam("TargetingPlayer") || (!this.canPlayAI(ai, sa) && mandatory)) {
|
||||
List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
} else {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
|
||||
final Game game = ai.getGame();
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
if (sa.hasParam("AllValid")) {
|
||||
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||
if (tgtCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
final List<String> lose = Lists.newArrayList();
|
||||
|
||||
if (sa.hasParam("LoseControl")) {
|
||||
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
|
||||
}
|
||||
|
||||
if (lose.contains("EOT")
|
||||
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return this.canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpDrawbackAI()
|
||||
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
final List<Card> cards = Lists.newArrayList();
|
||||
for (Player p : options) {
|
||||
cards.addAll(p.getCreaturesInPlay());
|
||||
}
|
||||
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
||||
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,162 +1,162 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
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
|
||||
|
||||
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("MimicVat".equals(aiLogic)) {
|
||||
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
|
||||
} else if ("AtEOT".equals(aiLogic)) {
|
||||
return ph.is(PhaseType.END_OF_TURN);
|
||||
} else if ("AtOppEOT".equals(aiLogic)) {
|
||||
return ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn() != aiPlayer;
|
||||
}
|
||||
|
||||
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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") && sa.getHostCard().getImprintedCards().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.usesTargeting() && sa.hasParam("TargetingPlayer")) {
|
||||
sa.resetTargets();
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
sa.setTargetingPlayer(targetingPlayer);
|
||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||
} else {
|
||||
return this.doTriggerAINoCost(aiPlayer, sa, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
// ////
|
||||
// Targeting
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
|
||||
CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa));
|
||||
|
||||
list = CardLists.filter(list, Predicates.not(CardPredicates.hasSVar("RemAIDeck")));
|
||||
//Nothing to target
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Saheeli Rai + Felidar Guardian combo support
|
||||
if (sa.getHostCard().getName().equals("Saheeli Rai")) {
|
||||
CardCollection felidarGuardian = CardLists.filter(list, CardPredicates.nameEquals("Felidar Guardian"));
|
||||
if (felidarGuardian.size() > 0) {
|
||||
// can copy a Felidar Guardian and combo off, so let's do it
|
||||
sa.getTargets().add(felidarGuardian.get(0));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// target loop
|
||||
while (sa.canAddMoreTarget()) {
|
||||
if (list.isEmpty()) {
|
||||
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !c.getType().isLegendary() || !c.getController().equals(aiPlayer);
|
||||
}
|
||||
});
|
||||
Card choice;
|
||||
if (!CardLists.filter(list, Presets.CREATURES).isEmpty()) {
|
||||
if (sa.hasParam("TargetingPlayer")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||
}
|
||||
} else {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
} else {
|
||||
// if no targeting, it should always be ok
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
//TODO: add logic here
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
// Select a card to attach to
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
|
||||
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
||||
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
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
|
||||
|
||||
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("MimicVat".equals(aiLogic)) {
|
||||
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
|
||||
} else if ("AtEOT".equals(aiLogic)) {
|
||||
return ph.is(PhaseType.END_OF_TURN);
|
||||
} else if ("AtOppEOT".equals(aiLogic)) {
|
||||
return ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn() != aiPlayer;
|
||||
}
|
||||
|
||||
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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") && sa.getHostCard().getImprintedCards().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.usesTargeting() && sa.hasParam("TargetingPlayer")) {
|
||||
sa.resetTargets();
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
sa.setTargetingPlayer(targetingPlayer);
|
||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||
} else {
|
||||
return this.doTriggerAINoCost(aiPlayer, sa, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
// ////
|
||||
// Targeting
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
|
||||
CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa));
|
||||
|
||||
list = CardLists.filter(list, Predicates.not(CardPredicates.hasSVar("RemAIDeck")));
|
||||
//Nothing to target
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Saheeli Rai + Felidar Guardian combo support
|
||||
if (sa.getHostCard().getName().equals("Saheeli Rai")) {
|
||||
CardCollection felidarGuardian = CardLists.filter(list, CardPredicates.nameEquals("Felidar Guardian"));
|
||||
if (felidarGuardian.size() > 0) {
|
||||
// can copy a Felidar Guardian and combo off, so let's do it
|
||||
sa.getTargets().add(felidarGuardian.get(0));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// target loop
|
||||
while (sa.canAddMoreTarget()) {
|
||||
if (list.isEmpty()) {
|
||||
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !c.getType().isLegendary() || !c.getController().equals(aiPlayer);
|
||||
}
|
||||
});
|
||||
Card choice;
|
||||
if (!CardLists.filter(list, Presets.CREATURES).isEmpty()) {
|
||||
if (sa.hasParam("TargetingPlayer")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||
}
|
||||
} else {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
} else {
|
||||
// if no targeting, it should always be ok
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
//TODO: add logic here
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
// Select a card to attach to
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
|
||||
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
||||
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,328 +1,328 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public class CounterAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
boolean toReturn = true;
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final Game game = ai.getGame();
|
||||
int tgtCMC = 0;
|
||||
SpellAbility tgtSA = null;
|
||||
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ("Force of Will".equals(sourceName)) {
|
||||
if (!SpecialCardAi.ForceOfWill.consider(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
|
||||
final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
||||
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai
|
||||
|| ai.getAllies().contains(topSA.getActivatingPlayer())) {
|
||||
// might as well check for player's friendliness
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if the top ability on the stack corresponds to the AI-specific targeting declaration, if provided
|
||||
if (sa.hasParam("AITgts") && (topSA.getHostCard() == null
|
||||
|| !topSA.getHostCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("CounterNoManaSpell") && topSA.getTotalManaSpent() > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("OppDiscardsHand".equals(sa.getParam("AILogic"))) {
|
||||
if (topSA.getActivatingPlayer().getCardsIn(ZoneType.Hand).size() < 2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
if (sa.canTargetSpellAbility(topSA)) {
|
||||
sa.getTargets().add(topSA);
|
||||
if (topSA.getPayCosts().getTotalMana() != null) {
|
||||
tgtSA = topSA;
|
||||
tgtCMC = topSA.getPayCosts().getTotalMana().getCMC();
|
||||
tgtCMC += topSA.getPayCosts().getTotalMana().countX() > 0 ? 3 : 0; // TODO: somehow determine the value of X paid and account for it?
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
||||
|
||||
if (unlessCost != null && !unlessCost.endsWith(">")) {
|
||||
Player opp = tgtSA.getActivatingPlayer();
|
||||
int usableManaSources = ComputerUtilMana.getAvailableManaEstimate(opp);
|
||||
|
||||
int toPay = 0;
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
setPayX = true;
|
||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
} else {
|
||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||
}
|
||||
|
||||
if (toPay == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (toPay <= usableManaSources) {
|
||||
// If this is a reusable Resource, feel free to play it most of
|
||||
// the time
|
||||
if (!SpellAbilityAi.playReusable(ai,sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (setPayX) {
|
||||
source.setSVar("PayX", Integer.toString(toPay));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Improve AI
|
||||
|
||||
// Will return true if this spell can counter (or is Reusable and can
|
||||
// force the Human into making decisions)
|
||||
|
||||
// But really it should be more picky about how it counters things
|
||||
|
||||
if (sa.hasParam("AILogic")) {
|
||||
String logic = sa.getParam("AILogic");
|
||||
if ("Never".equals(logic)) {
|
||||
return false;
|
||||
} else if (logic.startsWith("MinCMC.")) {
|
||||
int minCMC = Integer.parseInt(logic.substring(7));
|
||||
if (tgtCMC < minCMC) {
|
||||
return false;
|
||||
}
|
||||
} else if ("NullBrooch".equals(logic)) {
|
||||
if (!SpecialCardAi.NullBrooch.consider(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Specific constraints for the AI to use/not use counterspells against specific groups of spells
|
||||
// (specified in the AI profile)
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
boolean ctrCmc0ManaPerms = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS);
|
||||
boolean ctrDamageSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_DAMAGE_SPELLS);
|
||||
boolean ctrRemovalSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_REMOVAL_SPELLS);
|
||||
boolean ctrPumpSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_PUMP_SPELLS);
|
||||
boolean ctrAuraSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_AURAS);
|
||||
boolean ctrOtherCounters = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_OTHER_COUNTERSPELLS);
|
||||
int ctrChanceCMC1 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_1);
|
||||
int ctrChanceCMC2 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_2);
|
||||
int ctrChanceCMC3 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_3);
|
||||
String ctrNamed = aic.getProperty(AiProps.ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS);
|
||||
boolean dontCounter = false;
|
||||
|
||||
if (tgtCMC == 1 && !MyRandom.percentTrue(ctrChanceCMC1)) {
|
||||
dontCounter = true;
|
||||
} else if (tgtCMC == 2 && !MyRandom.percentTrue(ctrChanceCMC2)) {
|
||||
dontCounter = true;
|
||||
} else if (tgtCMC == 3 && !MyRandom.percentTrue(ctrChanceCMC3)) {
|
||||
dontCounter = true;
|
||||
}
|
||||
|
||||
if (tgtSA != null && tgtCMC < aic.getIntProperty(AiProps.MIN_SPELL_CMC_TO_COUNTER)) {
|
||||
dontCounter = true;
|
||||
Card tgtSource = tgtSA.getHostCard();
|
||||
if ((tgtSource != null && tgtCMC == 0 && tgtSource.isPermanent() && !tgtSource.getManaAbilities().isEmpty() && ctrCmc0ManaPerms)
|
||||
|| (tgtSA.getApi() == ApiType.DealDamage || tgtSA.getApi() == ApiType.LoseLife || tgtSA.getApi() == ApiType.DamageAll && ctrDamageSpells)
|
||||
|| (tgtSA.getApi() == ApiType.Counter && ctrOtherCounters)
|
||||
|| ((tgtSA.getApi() == ApiType.Pump || tgtSA.getApi() == ApiType.PumpAll) && ctrPumpSpells)
|
||||
|| (tgtSA.getApi() == ApiType.Attach && ctrAuraSpells)
|
||||
|| (tgtSA.getApi() == ApiType.Destroy || tgtSA.getApi() == ApiType.DestroyAll || tgtSA.getApi() == ApiType.Sacrifice
|
||||
|| tgtSA.getApi() == ApiType.SacrificeAll && ctrRemovalSpells)) {
|
||||
dontCounter = false;
|
||||
}
|
||||
|
||||
if (tgtSource != null && !ctrNamed.isEmpty() && !"none".equalsIgnoreCase(ctrNamed)) {
|
||||
for (String name : StringUtils.split(ctrNamed, ";")) {
|
||||
if (name.equals(tgtSource.getName())) {
|
||||
dontCounter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// should not refrain from countering a CMC X spell if that's the only CMC
|
||||
// counterable with that particular counterspell type (e.g. Mental Misstep vs. CMC 1 spells)
|
||||
if (sa.getParamOrDefault("ValidTgts", "").startsWith("Card.cmcEQ")) {
|
||||
int validTgtCMC = AbilityUtils.calculateAmount(source, sa.getParam("ValidTgts").substring(10), sa);
|
||||
if (tgtCMC == validTgtCMC) {
|
||||
dontCounter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dontCounter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return doTriggerAINoCost(aiPlayer, sa, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
if (tgt != null) {
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
Pair<SpellAbility, Boolean> pair = chooseTargetSpellAbility(game, sa, ai, mandatory);
|
||||
SpellAbility tgtSA = pair.getLeft();
|
||||
|
||||
if (tgtSA == null) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(tgtSA);
|
||||
if (!mandatory && !pair.getRight()) {
|
||||
// If not mandatory and not preferred, bail out after setting target
|
||||
return false;
|
||||
}
|
||||
|
||||
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
if (unlessCost != null) {
|
||||
Player opp = tgtSA.getActivatingPlayer();
|
||||
int usableManaSources = ComputerUtilMana.getAvailableManaEstimate(opp);
|
||||
|
||||
int toPay = 0;
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
setPayX = true;
|
||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
} else {
|
||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||
}
|
||||
|
||||
if (!mandatory) {
|
||||
if (toPay == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (toPay <= usableManaSources) {
|
||||
// If this is a reusable Resource, feel free to play it most
|
||||
// of the time
|
||||
if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (setPayX) {
|
||||
source.setSVar("PayX", Integer.toString(toPay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Improve AI
|
||||
|
||||
// Will return true if this spell can counter (or is Reusable and can
|
||||
// force the Human into making decisions)
|
||||
|
||||
// But really it should be more picky about how it counters things
|
||||
return true;
|
||||
}
|
||||
|
||||
public Pair<SpellAbility, Boolean> chooseTargetSpellAbility(Game game, SpellAbility sa, Player ai, boolean mandatory) {
|
||||
SpellAbility tgtSA;
|
||||
SpellAbility leastBadOption = null;
|
||||
SpellAbility bestOption = null;
|
||||
|
||||
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
|
||||
SpellAbilityStackInstance si = null;
|
||||
while(it.hasNext()) {
|
||||
si = it.next();
|
||||
tgtSA = si.getSpellAbility(true);
|
||||
if (!sa.canTargetSpellAbility(tgtSA)) {
|
||||
continue;
|
||||
}
|
||||
if (leastBadOption == null) {
|
||||
leastBadOption = tgtSA;
|
||||
}
|
||||
|
||||
if (!CardFactoryUtil.isCounterableBy(tgtSA.getHostCard(), sa) ||
|
||||
tgtSA.getActivatingPlayer() == ai ||
|
||||
!tgtSA.getActivatingPlayer().isOpponentOf(ai)) {
|
||||
// Is this a "better" least bad option
|
||||
if (leastBadOption.getActivatingPlayer().isOpponentOf(ai)) {
|
||||
// NOOP
|
||||
} else if (sa.getActivatingPlayer().isOpponentOf(ai)) {
|
||||
// Target opponents uncounterable stuff, before our own stuff
|
||||
leastBadOption = tgtSA;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bestOption == null) {
|
||||
bestOption = tgtSA;
|
||||
} else {
|
||||
// TODO Determine if this option is better than the current best option
|
||||
boolean betterThanBest = false;
|
||||
if (betterThanBest) {
|
||||
bestOption = tgtSA;
|
||||
}
|
||||
// Don't really need to keep updating leastBadOption once we have a bestOption
|
||||
}
|
||||
}
|
||||
|
||||
return new ImmutablePair<>(bestOption != null ? bestOption : leastBadOption, bestOption != null);
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public class CounterAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
boolean toReturn = true;
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final Game game = ai.getGame();
|
||||
int tgtCMC = 0;
|
||||
SpellAbility tgtSA = null;
|
||||
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ("Force of Will".equals(sourceName)) {
|
||||
if (!SpecialCardAi.ForceOfWill.consider(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
|
||||
final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
||||
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai
|
||||
|| ai.getAllies().contains(topSA.getActivatingPlayer())) {
|
||||
// might as well check for player's friendliness
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if the top ability on the stack corresponds to the AI-specific targeting declaration, if provided
|
||||
if (sa.hasParam("AITgts") && (topSA.getHostCard() == null
|
||||
|| !topSA.getHostCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("CounterNoManaSpell") && topSA.getTotalManaSpent() > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("OppDiscardsHand".equals(sa.getParam("AILogic"))) {
|
||||
if (topSA.getActivatingPlayer().getCardsIn(ZoneType.Hand).size() < 2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
if (sa.canTargetSpellAbility(topSA)) {
|
||||
sa.getTargets().add(topSA);
|
||||
if (topSA.getPayCosts().getTotalMana() != null) {
|
||||
tgtSA = topSA;
|
||||
tgtCMC = topSA.getPayCosts().getTotalMana().getCMC();
|
||||
tgtCMC += topSA.getPayCosts().getTotalMana().countX() > 0 ? 3 : 0; // TODO: somehow determine the value of X paid and account for it?
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
||||
|
||||
if (unlessCost != null && !unlessCost.endsWith(">")) {
|
||||
Player opp = tgtSA.getActivatingPlayer();
|
||||
int usableManaSources = ComputerUtilMana.getAvailableManaEstimate(opp);
|
||||
|
||||
int toPay = 0;
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
setPayX = true;
|
||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
} else {
|
||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||
}
|
||||
|
||||
if (toPay == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (toPay <= usableManaSources) {
|
||||
// If this is a reusable Resource, feel free to play it most of
|
||||
// the time
|
||||
if (!SpellAbilityAi.playReusable(ai,sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (setPayX) {
|
||||
source.setSVar("PayX", Integer.toString(toPay));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Improve AI
|
||||
|
||||
// Will return true if this spell can counter (or is Reusable and can
|
||||
// force the Human into making decisions)
|
||||
|
||||
// But really it should be more picky about how it counters things
|
||||
|
||||
if (sa.hasParam("AILogic")) {
|
||||
String logic = sa.getParam("AILogic");
|
||||
if ("Never".equals(logic)) {
|
||||
return false;
|
||||
} else if (logic.startsWith("MinCMC.")) {
|
||||
int minCMC = Integer.parseInt(logic.substring(7));
|
||||
if (tgtCMC < minCMC) {
|
||||
return false;
|
||||
}
|
||||
} else if ("NullBrooch".equals(logic)) {
|
||||
if (!SpecialCardAi.NullBrooch.consider(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Specific constraints for the AI to use/not use counterspells against specific groups of spells
|
||||
// (specified in the AI profile)
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
boolean ctrCmc0ManaPerms = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS);
|
||||
boolean ctrDamageSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_DAMAGE_SPELLS);
|
||||
boolean ctrRemovalSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_REMOVAL_SPELLS);
|
||||
boolean ctrPumpSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_PUMP_SPELLS);
|
||||
boolean ctrAuraSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_AURAS);
|
||||
boolean ctrOtherCounters = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_OTHER_COUNTERSPELLS);
|
||||
int ctrChanceCMC1 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_1);
|
||||
int ctrChanceCMC2 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_2);
|
||||
int ctrChanceCMC3 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_3);
|
||||
String ctrNamed = aic.getProperty(AiProps.ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS);
|
||||
boolean dontCounter = false;
|
||||
|
||||
if (tgtCMC == 1 && !MyRandom.percentTrue(ctrChanceCMC1)) {
|
||||
dontCounter = true;
|
||||
} else if (tgtCMC == 2 && !MyRandom.percentTrue(ctrChanceCMC2)) {
|
||||
dontCounter = true;
|
||||
} else if (tgtCMC == 3 && !MyRandom.percentTrue(ctrChanceCMC3)) {
|
||||
dontCounter = true;
|
||||
}
|
||||
|
||||
if (tgtSA != null && tgtCMC < aic.getIntProperty(AiProps.MIN_SPELL_CMC_TO_COUNTER)) {
|
||||
dontCounter = true;
|
||||
Card tgtSource = tgtSA.getHostCard();
|
||||
if ((tgtSource != null && tgtCMC == 0 && tgtSource.isPermanent() && !tgtSource.getManaAbilities().isEmpty() && ctrCmc0ManaPerms)
|
||||
|| (tgtSA.getApi() == ApiType.DealDamage || tgtSA.getApi() == ApiType.LoseLife || tgtSA.getApi() == ApiType.DamageAll && ctrDamageSpells)
|
||||
|| (tgtSA.getApi() == ApiType.Counter && ctrOtherCounters)
|
||||
|| ((tgtSA.getApi() == ApiType.Pump || tgtSA.getApi() == ApiType.PumpAll) && ctrPumpSpells)
|
||||
|| (tgtSA.getApi() == ApiType.Attach && ctrAuraSpells)
|
||||
|| (tgtSA.getApi() == ApiType.Destroy || tgtSA.getApi() == ApiType.DestroyAll || tgtSA.getApi() == ApiType.Sacrifice
|
||||
|| tgtSA.getApi() == ApiType.SacrificeAll && ctrRemovalSpells)) {
|
||||
dontCounter = false;
|
||||
}
|
||||
|
||||
if (tgtSource != null && !ctrNamed.isEmpty() && !"none".equalsIgnoreCase(ctrNamed)) {
|
||||
for (String name : StringUtils.split(ctrNamed, ";")) {
|
||||
if (name.equals(tgtSource.getName())) {
|
||||
dontCounter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// should not refrain from countering a CMC X spell if that's the only CMC
|
||||
// counterable with that particular counterspell type (e.g. Mental Misstep vs. CMC 1 spells)
|
||||
if (sa.getParamOrDefault("ValidTgts", "").startsWith("Card.cmcEQ")) {
|
||||
int validTgtCMC = AbilityUtils.calculateAmount(source, sa.getParam("ValidTgts").substring(10), sa);
|
||||
if (tgtCMC == validTgtCMC) {
|
||||
dontCounter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dontCounter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return doTriggerAINoCost(aiPlayer, sa, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
if (tgt != null) {
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
Pair<SpellAbility, Boolean> pair = chooseTargetSpellAbility(game, sa, ai, mandatory);
|
||||
SpellAbility tgtSA = pair.getLeft();
|
||||
|
||||
if (tgtSA == null) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(tgtSA);
|
||||
if (!mandatory && !pair.getRight()) {
|
||||
// If not mandatory and not preferred, bail out after setting target
|
||||
return false;
|
||||
}
|
||||
|
||||
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
if (unlessCost != null) {
|
||||
Player opp = tgtSA.getActivatingPlayer();
|
||||
int usableManaSources = ComputerUtilMana.getAvailableManaEstimate(opp);
|
||||
|
||||
int toPay = 0;
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
setPayX = true;
|
||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
} else {
|
||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||
}
|
||||
|
||||
if (!mandatory) {
|
||||
if (toPay == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (toPay <= usableManaSources) {
|
||||
// If this is a reusable Resource, feel free to play it most
|
||||
// of the time
|
||||
if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (setPayX) {
|
||||
source.setSVar("PayX", Integer.toString(toPay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Improve AI
|
||||
|
||||
// Will return true if this spell can counter (or is Reusable and can
|
||||
// force the Human into making decisions)
|
||||
|
||||
// But really it should be more picky about how it counters things
|
||||
return true;
|
||||
}
|
||||
|
||||
public Pair<SpellAbility, Boolean> chooseTargetSpellAbility(Game game, SpellAbility sa, Player ai, boolean mandatory) {
|
||||
SpellAbility tgtSA;
|
||||
SpellAbility leastBadOption = null;
|
||||
SpellAbility bestOption = null;
|
||||
|
||||
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
|
||||
SpellAbilityStackInstance si = null;
|
||||
while(it.hasNext()) {
|
||||
si = it.next();
|
||||
tgtSA = si.getSpellAbility(true);
|
||||
if (!sa.canTargetSpellAbility(tgtSA)) {
|
||||
continue;
|
||||
}
|
||||
if (leastBadOption == null) {
|
||||
leastBadOption = tgtSA;
|
||||
}
|
||||
|
||||
if (!CardFactoryUtil.isCounterableBy(tgtSA.getHostCard(), sa) ||
|
||||
tgtSA.getActivatingPlayer() == ai ||
|
||||
!tgtSA.getActivatingPlayer().isOpponentOf(ai)) {
|
||||
// Is this a "better" least bad option
|
||||
if (leastBadOption.getActivatingPlayer().isOpponentOf(ai)) {
|
||||
// NOOP
|
||||
} else if (sa.getActivatingPlayer().isOpponentOf(ai)) {
|
||||
// Target opponents uncounterable stuff, before our own stuff
|
||||
leastBadOption = tgtSA;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bestOption == null) {
|
||||
bestOption = tgtSA;
|
||||
} else {
|
||||
// TODO Determine if this option is better than the current best option
|
||||
boolean betterThanBest = false;
|
||||
if (betterThanBest) {
|
||||
bestOption = tgtSA;
|
||||
}
|
||||
// Don't really need to keep updating leastBadOption once we have a bestOption
|
||||
}
|
||||
}
|
||||
|
||||
return new ImmutablePair<>(bestOption != null ? bestOption : leastBadOption, bestOption != null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,489 +1,489 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CountersMoveAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!moveTgtAI(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final Card host = sa.getHostCard();
|
||||
final String type = sa.getParam("CounterType");
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CounterType.P1P1.equals(cType) && sa.hasParam("Source")) {
|
||||
int amount = calcAmount(sa, cType);
|
||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||
// opponent Creature with +1/+1 counter does attack
|
||||
// try to steal counter from it to kill it
|
||||
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
for (final Card c : srcCards) {
|
||||
// source is not controlled by current player
|
||||
if (!ph.isPlayerTurn(c.getController())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int a = c.getCounters(cType);
|
||||
if (a < amount) {
|
||||
continue;
|
||||
}
|
||||
if (ph.getCombat().isAttacking(c)) {
|
||||
// get copy of creature with removed Counter
|
||||
final Card cpy = CardUtil.getLKICopy(c);
|
||||
// cant use substract on Copy
|
||||
cpy.setCounters(cType, a - amount);
|
||||
|
||||
// a removed counter would kill it
|
||||
if (cpy.getNetToughness() <= cpy.getDamage()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// something you can't block, try to reduce its
|
||||
// attack
|
||||
if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// for Simic Fluxmage and other
|
||||
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
|
||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
|
||||
}
|
||||
}
|
||||
// for Simic Fluxmage and other
|
||||
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
||||
return false;
|
||||
}
|
||||
// Make sure that removing the last counter doesn't kill the creature
|
||||
if ("Self".equals(sa.getParam("Source"))) {
|
||||
if (host != null && host.getNetToughness() - 1 <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
|
||||
if (!moveTgtAI(ai, sa) && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sa.isTargetNumberValid() && mandatory) {
|
||||
final Game game = ai.getGame();
|
||||
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
if (tgtCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card card = ComputerUtilCard.getWorstAI(tgtCards);
|
||||
sa.getTargets().add(card);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
// no target Probably something like Graft
|
||||
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
final String type = sa.getParam("CounterType");
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||
|
||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
||||
|
||||
if (srcCards.isEmpty() || destCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card src = srcCards.get(0);
|
||||
final Card dest = destCards.get(0);
|
||||
|
||||
// for such Trigger, do not move counter to another players creature
|
||||
if (!dest.getController().equals(ai)) {
|
||||
return false;
|
||||
} else if (ComputerUtilCard.isUselessCreature(ai, dest)) {
|
||||
return false;
|
||||
} else if (dest.hasSVar("EndOfTurnLeavePlay")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cType != null) {
|
||||
if (!dest.canReceiveCounters(cType)) {
|
||||
return false;
|
||||
}
|
||||
final int amount = calcAmount(sa, cType);
|
||||
int a = src.getCounters(cType);
|
||||
if (a < amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card srcCopy = CardUtil.getLKICopy(src);
|
||||
// cant use substract on Copy
|
||||
srcCopy.setCounters(cType, a - amount);
|
||||
|
||||
final Card destCopy = CardUtil.getLKICopy(dest);
|
||||
destCopy.setCounters(cType, dest.getCounters(cType) + amount);
|
||||
|
||||
int oldEval = ComputerUtilCard.evaluateCreature(src) + ComputerUtilCard.evaluateCreature(dest);
|
||||
int newEval = ComputerUtilCard.evaluateCreature(srcCopy) + ComputerUtilCard.evaluateCreature(destCopy);
|
||||
|
||||
if (newEval < oldEval) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for some specific AI preferences
|
||||
if (src.hasStartOfKeyword("Graft") && "DontMoveCounterIfLethal".equals(src.getSVar("AIGraftPreference"))) {
|
||||
if (cType == CounterType.P1P1 && src.getNetToughness() - src.getTempToughnessBoost() - 1 <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// no target
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!moveTgtAI(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int calcAmount(final SpellAbility sa, final CounterType cType) {
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
|
||||
// TODO handle proper calculation of X values based on Cost
|
||||
int amount = 0;
|
||||
|
||||
if (amountStr.equals("All") || amountStr.equals("Any")) {
|
||||
// sa has Source, otherwise Source is the Target
|
||||
if (sa.hasParam("Source")) {
|
||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||
for (final Card c : srcCards) {
|
||||
amount += c.getCounters(cType);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(host, amountStr, sa);
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
private boolean moveTgtAI(final Player ai, final SpellAbility sa) {
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
final String type = sa.getParam("CounterType");
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||
|
||||
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
if (sa.hasParam("Defined")) {
|
||||
final int amount = calcAmount(sa, cType);
|
||||
tgtCards = CardLists.filter(tgtCards, CardPredicates.hasCounter(cType));
|
||||
|
||||
// SA uses target for Source
|
||||
// Target => Defined
|
||||
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
||||
|
||||
if (destCards.isEmpty()) {
|
||||
// something went wrong
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card dest = destCards.get(0);
|
||||
|
||||
// remove dest from targets, because move doesn't work that way
|
||||
tgtCards.remove(dest);
|
||||
|
||||
if (cType != null && !dest.canReceiveCounters(cType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prefered logic for this: try to steal counter
|
||||
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
||||
if (!oppList.isEmpty()) {
|
||||
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
// do not weak a useless creature if able
|
||||
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card srcCardCpy = CardUtil.getLKICopy(card);
|
||||
// cant use substract on Copy
|
||||
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
||||
|
||||
// do not steal a P1P1 from Undying if it would die
|
||||
// this way
|
||||
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||
if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword("Undying") || card.isToken()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// if no Prefered found, try normal list
|
||||
if (best.isEmpty()) {
|
||||
best = oppList;
|
||||
}
|
||||
|
||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||
|
||||
if (card != null) {
|
||||
sa.getTargets().add(card);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// from your creature, try to take from the weakest
|
||||
FCollection<Player> ally = ai.getAllies();
|
||||
ally.add(ai);
|
||||
|
||||
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ally);
|
||||
if (!aiList.isEmpty()) {
|
||||
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
// gain from useless
|
||||
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// source would leave the game
|
||||
if (card.hasSVar("EndOfTurnLeavePlay")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// try to remove P1P1 from undying or evolve
|
||||
if (CounterType.P1P1.equals(cType)) {
|
||||
if (card.hasKeyword("Undying") || card.hasKeyword("Evolve")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (best.isEmpty()) {
|
||||
best = aiList;
|
||||
}
|
||||
|
||||
Card card = ComputerUtilCard.getWorstCreatureAI(best);
|
||||
|
||||
if (card != null) {
|
||||
sa.getTargets().add(card);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
// SA uses target for Defined
|
||||
// Source => Targeted
|
||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||
|
||||
if (srcCards.isEmpty()) {
|
||||
// something went wrong
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card src = srcCards.get(0);
|
||||
if (cType != null) {
|
||||
if (src.getCounters(cType) <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
|
||||
if (!aiList.isEmpty()) {
|
||||
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
// gain from useless
|
||||
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// source would leave the game
|
||||
if (card.hasSVar("EndOfTurnLeavePlay")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cType != null) {
|
||||
if (CounterType.P1P1.equals(cType) && card.hasKeyword("Undying")) {
|
||||
return false;
|
||||
}
|
||||
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!card.canReceiveCounters(cType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (best.isEmpty()) {
|
||||
best = aiList;
|
||||
}
|
||||
|
||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||
|
||||
if (card != null) {
|
||||
sa.getTargets().add(card);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// move counter to opponents creature but only if you can not steal
|
||||
// them
|
||||
// try to move to something useless or something that would leave
|
||||
// play
|
||||
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
||||
if (!oppList.isEmpty()) {
|
||||
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
// gain from useless
|
||||
if (!ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// source would leave the game
|
||||
if (!card.hasSVar("EndOfTurnLeavePlay")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (best.isEmpty()) {
|
||||
best = aiList;
|
||||
}
|
||||
|
||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||
|
||||
if (card != null) {
|
||||
sa.getTargets().add(card);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// used for multiple sources -> defied
|
||||
// or for source -> multiple defined
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||
Player targetedPlayer) {
|
||||
if (sa.hasParam("AiLogic")) {
|
||||
String logic = sa.getParam("AiLogic");
|
||||
|
||||
if ("ToValid".equals(logic)) {
|
||||
// cards like Forgotten Ancient
|
||||
// can put counter on any creature, but should only put one on
|
||||
// Ai controlled ones
|
||||
List<Card> aiCards = CardLists.filterControlledBy(options, ai);
|
||||
return ComputerUtilCard.getBestCreatureAI(aiCards);
|
||||
} else if ("FromValid".equals(logic)) {
|
||||
// cards like Aetherborn Marauder
|
||||
return ComputerUtilCard.getWorstCreatureAI(options);
|
||||
}
|
||||
}
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
// used when selecting how many counters to move
|
||||
@Override
|
||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||
// TODO improve logic behind it
|
||||
// like keeping the last counter on a 0/0 creature
|
||||
return max;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CountersMoveAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!moveTgtAI(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final Card host = sa.getHostCard();
|
||||
final String type = sa.getParam("CounterType");
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CounterType.P1P1.equals(cType) && sa.hasParam("Source")) {
|
||||
int amount = calcAmount(sa, cType);
|
||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||
// opponent Creature with +1/+1 counter does attack
|
||||
// try to steal counter from it to kill it
|
||||
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
for (final Card c : srcCards) {
|
||||
// source is not controlled by current player
|
||||
if (!ph.isPlayerTurn(c.getController())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int a = c.getCounters(cType);
|
||||
if (a < amount) {
|
||||
continue;
|
||||
}
|
||||
if (ph.getCombat().isAttacking(c)) {
|
||||
// get copy of creature with removed Counter
|
||||
final Card cpy = CardUtil.getLKICopy(c);
|
||||
// cant use substract on Copy
|
||||
cpy.setCounters(cType, a - amount);
|
||||
|
||||
// a removed counter would kill it
|
||||
if (cpy.getNetToughness() <= cpy.getDamage()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// something you can't block, try to reduce its
|
||||
// attack
|
||||
if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// for Simic Fluxmage and other
|
||||
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
|
||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
|
||||
}
|
||||
}
|
||||
// for Simic Fluxmage and other
|
||||
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
||||
return false;
|
||||
}
|
||||
// Make sure that removing the last counter doesn't kill the creature
|
||||
if ("Self".equals(sa.getParam("Source"))) {
|
||||
if (host != null && host.getNetToughness() - 1 <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
|
||||
if (!moveTgtAI(ai, sa) && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sa.isTargetNumberValid() && mandatory) {
|
||||
final Game game = ai.getGame();
|
||||
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
if (tgtCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card card = ComputerUtilCard.getWorstAI(tgtCards);
|
||||
sa.getTargets().add(card);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
// no target Probably something like Graft
|
||||
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
final String type = sa.getParam("CounterType");
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||
|
||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
||||
|
||||
if (srcCards.isEmpty() || destCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card src = srcCards.get(0);
|
||||
final Card dest = destCards.get(0);
|
||||
|
||||
// for such Trigger, do not move counter to another players creature
|
||||
if (!dest.getController().equals(ai)) {
|
||||
return false;
|
||||
} else if (ComputerUtilCard.isUselessCreature(ai, dest)) {
|
||||
return false;
|
||||
} else if (dest.hasSVar("EndOfTurnLeavePlay")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cType != null) {
|
||||
if (!dest.canReceiveCounters(cType)) {
|
||||
return false;
|
||||
}
|
||||
final int amount = calcAmount(sa, cType);
|
||||
int a = src.getCounters(cType);
|
||||
if (a < amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card srcCopy = CardUtil.getLKICopy(src);
|
||||
// cant use substract on Copy
|
||||
srcCopy.setCounters(cType, a - amount);
|
||||
|
||||
final Card destCopy = CardUtil.getLKICopy(dest);
|
||||
destCopy.setCounters(cType, dest.getCounters(cType) + amount);
|
||||
|
||||
int oldEval = ComputerUtilCard.evaluateCreature(src) + ComputerUtilCard.evaluateCreature(dest);
|
||||
int newEval = ComputerUtilCard.evaluateCreature(srcCopy) + ComputerUtilCard.evaluateCreature(destCopy);
|
||||
|
||||
if (newEval < oldEval) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for some specific AI preferences
|
||||
if (src.hasStartOfKeyword("Graft") && "DontMoveCounterIfLethal".equals(src.getSVar("AIGraftPreference"))) {
|
||||
if (cType == CounterType.P1P1 && src.getNetToughness() - src.getTempToughnessBoost() - 1 <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// no target
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!moveTgtAI(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int calcAmount(final SpellAbility sa, final CounterType cType) {
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
|
||||
// TODO handle proper calculation of X values based on Cost
|
||||
int amount = 0;
|
||||
|
||||
if (amountStr.equals("All") || amountStr.equals("Any")) {
|
||||
// sa has Source, otherwise Source is the Target
|
||||
if (sa.hasParam("Source")) {
|
||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||
for (final Card c : srcCards) {
|
||||
amount += c.getCounters(cType);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(host, amountStr, sa);
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
private boolean moveTgtAI(final Player ai, final SpellAbility sa) {
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
final String type = sa.getParam("CounterType");
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||
|
||||
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
if (sa.hasParam("Defined")) {
|
||||
final int amount = calcAmount(sa, cType);
|
||||
tgtCards = CardLists.filter(tgtCards, CardPredicates.hasCounter(cType));
|
||||
|
||||
// SA uses target for Source
|
||||
// Target => Defined
|
||||
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
||||
|
||||
if (destCards.isEmpty()) {
|
||||
// something went wrong
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card dest = destCards.get(0);
|
||||
|
||||
// remove dest from targets, because move doesn't work that way
|
||||
tgtCards.remove(dest);
|
||||
|
||||
if (cType != null && !dest.canReceiveCounters(cType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prefered logic for this: try to steal counter
|
||||
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
||||
if (!oppList.isEmpty()) {
|
||||
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
// do not weak a useless creature if able
|
||||
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card srcCardCpy = CardUtil.getLKICopy(card);
|
||||
// cant use substract on Copy
|
||||
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
||||
|
||||
// do not steal a P1P1 from Undying if it would die
|
||||
// this way
|
||||
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||
if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword("Undying") || card.isToken()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// if no Prefered found, try normal list
|
||||
if (best.isEmpty()) {
|
||||
best = oppList;
|
||||
}
|
||||
|
||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||
|
||||
if (card != null) {
|
||||
sa.getTargets().add(card);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// from your creature, try to take from the weakest
|
||||
FCollection<Player> ally = ai.getAllies();
|
||||
ally.add(ai);
|
||||
|
||||
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ally);
|
||||
if (!aiList.isEmpty()) {
|
||||
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
// gain from useless
|
||||
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// source would leave the game
|
||||
if (card.hasSVar("EndOfTurnLeavePlay")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// try to remove P1P1 from undying or evolve
|
||||
if (CounterType.P1P1.equals(cType)) {
|
||||
if (card.hasKeyword("Undying") || card.hasKeyword("Evolve")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (best.isEmpty()) {
|
||||
best = aiList;
|
||||
}
|
||||
|
||||
Card card = ComputerUtilCard.getWorstCreatureAI(best);
|
||||
|
||||
if (card != null) {
|
||||
sa.getTargets().add(card);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
// SA uses target for Defined
|
||||
// Source => Targeted
|
||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||
|
||||
if (srcCards.isEmpty()) {
|
||||
// something went wrong
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card src = srcCards.get(0);
|
||||
if (cType != null) {
|
||||
if (src.getCounters(cType) <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
|
||||
if (!aiList.isEmpty()) {
|
||||
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
// gain from useless
|
||||
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// source would leave the game
|
||||
if (card.hasSVar("EndOfTurnLeavePlay")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cType != null) {
|
||||
if (CounterType.P1P1.equals(cType) && card.hasKeyword("Undying")) {
|
||||
return false;
|
||||
}
|
||||
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!card.canReceiveCounters(cType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (best.isEmpty()) {
|
||||
best = aiList;
|
||||
}
|
||||
|
||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||
|
||||
if (card != null) {
|
||||
sa.getTargets().add(card);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// move counter to opponents creature but only if you can not steal
|
||||
// them
|
||||
// try to move to something useless or something that would leave
|
||||
// play
|
||||
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
||||
if (!oppList.isEmpty()) {
|
||||
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
// gain from useless
|
||||
if (!ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// source would leave the game
|
||||
if (!card.hasSVar("EndOfTurnLeavePlay")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (best.isEmpty()) {
|
||||
best = aiList;
|
||||
}
|
||||
|
||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||
|
||||
if (card != null) {
|
||||
sa.getTargets().add(card);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// used for multiple sources -> defied
|
||||
// or for source -> multiple defined
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||
Player targetedPlayer) {
|
||||
if (sa.hasParam("AiLogic")) {
|
||||
String logic = sa.getParam("AiLogic");
|
||||
|
||||
if ("ToValid".equals(logic)) {
|
||||
// cards like Forgotten Ancient
|
||||
// can put counter on any creature, but should only put one on
|
||||
// Ai controlled ones
|
||||
List<Card> aiCards = CardLists.filterControlledBy(options, ai);
|
||||
return ComputerUtilCard.getBestCreatureAI(aiCards);
|
||||
} else if ("FromValid".equals(logic)) {
|
||||
// cards like Aetherborn Marauder
|
||||
return ComputerUtilCard.getWorstCreatureAI(options);
|
||||
}
|
||||
}
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
// used when selecting how many counters to move
|
||||
@Override
|
||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||
// TODO improve logic behind it
|
||||
// like keeping the last counter on a 0/0 creature
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +1,98 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class CountersProliferateAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
|
||||
final List<Card> cperms = Lists.newArrayList();
|
||||
final List<Player> allies = ai.getAllies();
|
||||
allies.add(ai);
|
||||
boolean allyExpOrEnergy = false;
|
||||
|
||||
for (final Player p : allies) {
|
||||
// player has experience or energy counter
|
||||
if (p.getCounters(CounterType.EXPERIENCE) + p.getCounters(CounterType.ENERGY) >= 1) {
|
||||
allyExpOrEnergy = true;
|
||||
}
|
||||
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
if (crd.hasCounters()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// iterate only over existing counters
|
||||
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
final List<Card> hperms = Lists.newArrayList();
|
||||
boolean opponentPoison = false;
|
||||
|
||||
for (final Player o : ai.getOpponents()) {
|
||||
opponentPoison |= o.getPoisonCounters() >= 1;
|
||||
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
if (crd.hasCounters()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// iterate only over existing counters
|
||||
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||
if (e.getValue() >= 1 && ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
if (cperms.isEmpty() && hperms.isEmpty() && !opponentPoison && !allyExpOrEnergy) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
boolean chance = true;
|
||||
|
||||
// TODO Make sure Human has poison counters or there are some counters
|
||||
// we want to proliferate
|
||||
return chance;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class CountersProliferateAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
|
||||
final List<Card> cperms = Lists.newArrayList();
|
||||
final List<Player> allies = ai.getAllies();
|
||||
allies.add(ai);
|
||||
boolean allyExpOrEnergy = false;
|
||||
|
||||
for (final Player p : allies) {
|
||||
// player has experience or energy counter
|
||||
if (p.getCounters(CounterType.EXPERIENCE) + p.getCounters(CounterType.ENERGY) >= 1) {
|
||||
allyExpOrEnergy = true;
|
||||
}
|
||||
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
if (crd.hasCounters()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// iterate only over existing counters
|
||||
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
final List<Card> hperms = Lists.newArrayList();
|
||||
boolean opponentPoison = false;
|
||||
|
||||
for (final Player o : ai.getOpponents()) {
|
||||
opponentPoison |= o.getPoisonCounters() >= 1;
|
||||
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
if (crd.hasCounters()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// iterate only over existing counters
|
||||
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||
if (e.getValue() >= 1 && ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
if (cperms.isEmpty() && hperms.isEmpty() && !opponentPoison && !allyExpOrEnergy) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
boolean chance = true;
|
||||
|
||||
// TODO Make sure Human has poison counters or there are some counters
|
||||
// we want to proliferate
|
||||
return chance;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,183 +1,183 @@
|
||||
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;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class CountersPutAllAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// AI needs to be expanded, since this function can be pretty complex
|
||||
// based on what
|
||||
// the expected targets could be
|
||||
final Random r = MyRandom.getRandom();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
List<Card> hList;
|
||||
List<Card> cList;
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
final String valid = sa.getParam("ValidCards");
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
final boolean curse = sa.isCurse();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
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
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 8, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (logic.equals("AtEOTOrBlock")) {
|
||||
if (!ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && !ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
} else if (logic.equals("AtOppEOT")) {
|
||||
if (!(ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && ai.getGame().getPhaseHandler().getNextTurn() == ai)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (tgt != null) {
|
||||
Player pl = curse ? ComputerUtil.getOpponentFor(ai) : ai;
|
||||
sa.getTargets().add(pl);
|
||||
|
||||
hList = CardLists.filterControlledBy(hList, pl);
|
||||
cList = CardLists.filterControlledBy(cList, pl);
|
||||
}
|
||||
|
||||
// TODO improve X value to don't overpay when extra mana won't do
|
||||
// anything more useful
|
||||
final int amount;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(amount));
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
if (curse) {
|
||||
if (type.equals("M1M1")) {
|
||||
final List<Card> killable = CardLists.filter(hList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.getNetToughness() <= amount;
|
||||
}
|
||||
});
|
||||
if (!(killable.size() > 2)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// make sure compy doesn't harm his stuff more than human's
|
||||
// stuff
|
||||
if (cList.size() > hList.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// human has more things that will benefit, don't play
|
||||
if (hList.size() >= cList.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check for cards that could profit from the ability
|
||||
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
||||
if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
|
||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
|
||||
&& sa instanceof AbilitySub
|
||||
&& (!phase.getNextTurn().equals(ai)
|
||||
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
|
||||
boolean combatants = false;
|
||||
for (Card c : hList) {
|
||||
if (!c.equals(source) && c.isUntapped()) {
|
||||
combatants = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!combatants) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
return chance;
|
||||
}
|
||||
|
||||
return ((r.nextFloat() < .6667) && chance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return player.getCreaturesInPlay().size() >= ComputerUtil.getOpponentFor(player).getCreaturesInPlay().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
List<Player> players = Lists.newArrayList();
|
||||
if (!sa.isCurse()) {
|
||||
players.add(aiPlayer);
|
||||
}
|
||||
players.addAll(aiPlayer.getOpponents());
|
||||
players.addAll(aiPlayer.getAllies());
|
||||
if (sa.isCurse()) {
|
||||
players.add(aiPlayer);
|
||||
}
|
||||
|
||||
for (final Player p : players) {
|
||||
if (p.canBeTargetedBy(sa) && sa.canTarget(p)) {
|
||||
boolean preferred = false;
|
||||
preferred = (sa.isCurse() && p.isOpponentOf(aiPlayer)) || (!sa.isCurse() && p == aiPlayer);
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(p);
|
||||
return preferred || mandatory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mandatory;
|
||||
}
|
||||
}
|
||||
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;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class CountersPutAllAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// AI needs to be expanded, since this function can be pretty complex
|
||||
// based on what
|
||||
// the expected targets could be
|
||||
final Random r = MyRandom.getRandom();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
List<Card> hList;
|
||||
List<Card> cList;
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
final String valid = sa.getParam("ValidCards");
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
final boolean curse = sa.isCurse();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
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
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 8, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (logic.equals("AtEOTOrBlock")) {
|
||||
if (!ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && !ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
} else if (logic.equals("AtOppEOT")) {
|
||||
if (!(ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && ai.getGame().getPhaseHandler().getNextTurn() == ai)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (tgt != null) {
|
||||
Player pl = curse ? ComputerUtil.getOpponentFor(ai) : ai;
|
||||
sa.getTargets().add(pl);
|
||||
|
||||
hList = CardLists.filterControlledBy(hList, pl);
|
||||
cList = CardLists.filterControlledBy(cList, pl);
|
||||
}
|
||||
|
||||
// TODO improve X value to don't overpay when extra mana won't do
|
||||
// anything more useful
|
||||
final int amount;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(amount));
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
if (curse) {
|
||||
if (type.equals("M1M1")) {
|
||||
final List<Card> killable = CardLists.filter(hList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.getNetToughness() <= amount;
|
||||
}
|
||||
});
|
||||
if (!(killable.size() > 2)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// make sure compy doesn't harm his stuff more than human's
|
||||
// stuff
|
||||
if (cList.size() > hList.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// human has more things that will benefit, don't play
|
||||
if (hList.size() >= cList.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check for cards that could profit from the ability
|
||||
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
||||
if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
|
||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
|
||||
&& sa instanceof AbilitySub
|
||||
&& (!phase.getNextTurn().equals(ai)
|
||||
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
|
||||
boolean combatants = false;
|
||||
for (Card c : hList) {
|
||||
if (!c.equals(source) && c.isUntapped()) {
|
||||
combatants = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!combatants) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
return chance;
|
||||
}
|
||||
|
||||
return ((r.nextFloat() < .6667) && chance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return player.getCreaturesInPlay().size() >= ComputerUtil.getOpponentFor(player).getCreaturesInPlay().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
List<Player> players = Lists.newArrayList();
|
||||
if (!sa.isCurse()) {
|
||||
players.add(aiPlayer);
|
||||
}
|
||||
players.addAll(aiPlayer.getOpponents());
|
||||
players.addAll(aiPlayer.getAllies());
|
||||
if (sa.isCurse()) {
|
||||
players.add(aiPlayer);
|
||||
}
|
||||
|
||||
for (final Player p : players) {
|
||||
if (p.canBeTargetedBy(sa) && sa.canTarget(p)) {
|
||||
boolean preferred = false;
|
||||
preferred = (sa.isCurse() && p.isOpponentOf(aiPlayer)) || (!sa.isCurse() && p == aiPlayer);
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(p);
|
||||
return preferred || mandatory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mandatory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,287 +1,287 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.card.*;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController.BinaryChoiceType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactory_PutOrRemoveCountersAi class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id$
|
||||
*/
|
||||
public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
if (sa.usesTargeting()) {
|
||||
return doTgt(ai, sa, false);
|
||||
}
|
||||
return super.checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
final int amount = Integer.valueOf(sa.getParam("CounterNum"));
|
||||
|
||||
// remove counter with Time might use Exile Zone too
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
// need to targetable
|
||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
|
||||
|
||||
if (sa.hasParam("CounterType")) {
|
||||
// currently only Jhoira's Timebug
|
||||
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
|
||||
|
||||
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
|
||||
|
||||
if (countersList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// currently can only target cards you control or you own
|
||||
final Card best = ComputerUtilCard.getBestAI(countersList);
|
||||
|
||||
// currently both cards only has one target
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
} else {
|
||||
// currently only Clockspinning
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
// logic to remove some counter
|
||||
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounters());
|
||||
|
||||
if (!countersList.isEmpty()) {
|
||||
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = CardLists.filter(countersList,
|
||||
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
sa.getTargets().add(depthsList.getFirst());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get rid of Planeswalkers, currently only if it can kill them
|
||||
// with one touch
|
||||
CardCollection planeswalkerList = CardLists.filter(
|
||||
CardLists.filterControlledBy(countersList, ai.getOpponents()),
|
||||
CardPredicates.Presets.PLANEWALKERS,
|
||||
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||
return true;
|
||||
}
|
||||
|
||||
// do as M1M1 part
|
||||
CardCollection aiList = CardLists.filterControlledBy(countersList, ai);
|
||||
|
||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
||||
|
||||
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
|
||||
if (!aiPersistList.isEmpty()) {
|
||||
aiM1M1List = aiPersistList;
|
||||
}
|
||||
|
||||
if (!aiM1M1List.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
|
||||
return true;
|
||||
}
|
||||
|
||||
// do as P1P1 part
|
||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
||||
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
|
||||
|
||||
if (!aiUndyingList.isEmpty()) {
|
||||
aiP1P1List = aiUndyingList;
|
||||
}
|
||||
if (!aiP1P1List.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
|
||||
return true;
|
||||
}
|
||||
|
||||
// fallback to remove any counter from opponent
|
||||
CardCollection oppList = CardLists.filterControlledBy(countersList, ai.getOpponents());
|
||||
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
|
||||
if (!oppList.isEmpty()) {
|
||||
final Card best = ComputerUtilCard.getBestAI(oppList);
|
||||
|
||||
for (final CounterType aType : best.getCounters().keySet()) {
|
||||
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
} else if (!ComputerUtil.isUselessCounter(aType)) {
|
||||
// whould remove positive counter
|
||||
if (best.getCounters(aType) <= amount) {
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mandatory) {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return doTgt(ai, sa, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
|
||||
* forge.game.spellability.SpellAbility, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||
|
||||
if (options.size() > 1) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
Card tgt = (Card) params.get("Target");
|
||||
|
||||
// planeswalker has high priority for loyalty counters
|
||||
if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) {
|
||||
return CounterType.LOYALTY;
|
||||
}
|
||||
|
||||
if (tgt.getController().isOpponentOf(ai)) {
|
||||
// creatures with BaseToughness below or equal zero might be
|
||||
// killed if their counters are removed
|
||||
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
||||
if (options.contains(CounterType.P1P1)) {
|
||||
return CounterType.P1P1;
|
||||
} else if (options.contains(CounterType.M1M1)) {
|
||||
return CounterType.M1M1;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback logic, select positive counter to remove it
|
||||
for (final CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// this counters are treat first to be removed
|
||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
return CounterType.ICE;
|
||||
}
|
||||
} else if (tgt.hasKeyword("Undying") && options.contains(CounterType.P1P1)) {
|
||||
return CounterType.P1P1;
|
||||
} else if (tgt.hasKeyword("Persist") && options.contains(CounterType.M1M1)) {
|
||||
return CounterType.M1M1;
|
||||
}
|
||||
|
||||
// fallback logic, select positive counter to add more
|
||||
for (final CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.chooseCounterType(options, sa, params);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.ai.SpellAbilityAi#chooseBinary(forge.game.player.PlayerController.
|
||||
* BinaryChoiceType, forge.game.spellability.SpellAbility, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
|
||||
if (kindOfChoice.equals(BinaryChoiceType.AddOrRemove)) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
Card tgt = (Card) params.get("Target");
|
||||
CounterType type = (CounterType) params.get("CounterType");
|
||||
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
if (tgt.getController().isOpponentOf(ai)) {
|
||||
if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ComputerUtil.isNegativeCounter(type, tgt);
|
||||
} else {
|
||||
if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
return false;
|
||||
}
|
||||
} else if (type.equals(CounterType.M1M1) && tgt.hasKeyword("Persist")) {
|
||||
return false;
|
||||
} else if (type.equals(CounterType.P1P1) && tgt.hasKeyword("Undying")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !ComputerUtil.isNegativeCounter(type, tgt);
|
||||
}
|
||||
}
|
||||
return super.chooseBinary(kindOfChoice, sa, params);
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.card.*;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController.BinaryChoiceType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactory_PutOrRemoveCountersAi class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id$
|
||||
*/
|
||||
public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
if (sa.usesTargeting()) {
|
||||
return doTgt(ai, sa, false);
|
||||
}
|
||||
return super.checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
final int amount = Integer.valueOf(sa.getParam("CounterNum"));
|
||||
|
||||
// remove counter with Time might use Exile Zone too
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
// need to targetable
|
||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
|
||||
|
||||
if (sa.hasParam("CounterType")) {
|
||||
// currently only Jhoira's Timebug
|
||||
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
|
||||
|
||||
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
|
||||
|
||||
if (countersList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// currently can only target cards you control or you own
|
||||
final Card best = ComputerUtilCard.getBestAI(countersList);
|
||||
|
||||
// currently both cards only has one target
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
} else {
|
||||
// currently only Clockspinning
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
// logic to remove some counter
|
||||
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounters());
|
||||
|
||||
if (!countersList.isEmpty()) {
|
||||
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = CardLists.filter(countersList,
|
||||
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
sa.getTargets().add(depthsList.getFirst());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get rid of Planeswalkers, currently only if it can kill them
|
||||
// with one touch
|
||||
CardCollection planeswalkerList = CardLists.filter(
|
||||
CardLists.filterControlledBy(countersList, ai.getOpponents()),
|
||||
CardPredicates.Presets.PLANEWALKERS,
|
||||
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||
return true;
|
||||
}
|
||||
|
||||
// do as M1M1 part
|
||||
CardCollection aiList = CardLists.filterControlledBy(countersList, ai);
|
||||
|
||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
||||
|
||||
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
|
||||
if (!aiPersistList.isEmpty()) {
|
||||
aiM1M1List = aiPersistList;
|
||||
}
|
||||
|
||||
if (!aiM1M1List.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
|
||||
return true;
|
||||
}
|
||||
|
||||
// do as P1P1 part
|
||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
||||
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
|
||||
|
||||
if (!aiUndyingList.isEmpty()) {
|
||||
aiP1P1List = aiUndyingList;
|
||||
}
|
||||
if (!aiP1P1List.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
|
||||
return true;
|
||||
}
|
||||
|
||||
// fallback to remove any counter from opponent
|
||||
CardCollection oppList = CardLists.filterControlledBy(countersList, ai.getOpponents());
|
||||
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
|
||||
if (!oppList.isEmpty()) {
|
||||
final Card best = ComputerUtilCard.getBestAI(oppList);
|
||||
|
||||
for (final CounterType aType : best.getCounters().keySet()) {
|
||||
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
} else if (!ComputerUtil.isUselessCounter(aType)) {
|
||||
// whould remove positive counter
|
||||
if (best.getCounters(aType) <= amount) {
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mandatory) {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return doTgt(ai, sa, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
|
||||
* forge.game.spellability.SpellAbility, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||
|
||||
if (options.size() > 1) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
Card tgt = (Card) params.get("Target");
|
||||
|
||||
// planeswalker has high priority for loyalty counters
|
||||
if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) {
|
||||
return CounterType.LOYALTY;
|
||||
}
|
||||
|
||||
if (tgt.getController().isOpponentOf(ai)) {
|
||||
// creatures with BaseToughness below or equal zero might be
|
||||
// killed if their counters are removed
|
||||
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
||||
if (options.contains(CounterType.P1P1)) {
|
||||
return CounterType.P1P1;
|
||||
} else if (options.contains(CounterType.M1M1)) {
|
||||
return CounterType.M1M1;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback logic, select positive counter to remove it
|
||||
for (final CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// this counters are treat first to be removed
|
||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
return CounterType.ICE;
|
||||
}
|
||||
} else if (tgt.hasKeyword("Undying") && options.contains(CounterType.P1P1)) {
|
||||
return CounterType.P1P1;
|
||||
} else if (tgt.hasKeyword("Persist") && options.contains(CounterType.M1M1)) {
|
||||
return CounterType.M1M1;
|
||||
}
|
||||
|
||||
// fallback logic, select positive counter to add more
|
||||
for (final CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.chooseCounterType(options, sa, params);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.ai.SpellAbilityAi#chooseBinary(forge.game.player.PlayerController.
|
||||
* BinaryChoiceType, forge.game.spellability.SpellAbility, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
|
||||
if (kindOfChoice.equals(BinaryChoiceType.AddOrRemove)) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
Card tgt = (Card) params.get("Target");
|
||||
CounterType type = (CounterType) params.get("CounterType");
|
||||
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
if (tgt.getController().isOpponentOf(ai)) {
|
||||
if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ComputerUtil.isNegativeCounter(type, tgt);
|
||||
} else {
|
||||
if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
return false;
|
||||
}
|
||||
} else if (type.equals(CounterType.M1M1) && tgt.hasKeyword("Persist")) {
|
||||
return false;
|
||||
} else if (type.equals(CounterType.P1P1) && tgt.hasKeyword("Undying")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !ComputerUtil.isNegativeCounter(type, tgt);
|
||||
}
|
||||
}
|
||||
return super.chooseBinary(kindOfChoice, sa, params);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,374 +1,374 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CountersRemoveAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
||||
final String type = sa.getParam("CounterType");
|
||||
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases") && !type.equals("M1M1")) {
|
||||
return false;
|
||||
}
|
||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler,
|
||||
* java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
|
||||
if ("EndOfOpponentsTurn".equals(logic)) {
|
||||
if (!ph.is(PhaseType.END_OF_TURN) || !ph.getNextTurn().equals(ai)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return super.checkPhaseRestrictions(ai, sa, ph, logic);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
|
||||
final String type = sa.getParam("CounterType");
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
return doTgt(ai, sa, false);
|
||||
}
|
||||
|
||||
if (!type.matches("Any") && !type.matches("All")) {
|
||||
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
|
||||
if (currCounters < 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return super.checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
|
||||
// remove counter with Time might use Exile Zone too
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
CardCollection list = new CardCollection(game.getCardsIn(tgt.getZone()));
|
||||
// need to targetable
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
|
||||
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
if (type.matches("All")) {
|
||||
// Logic Part for Vampire Hexmage
|
||||
// Break Dark Depths
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||
CardPredicates.hasCounter(CounterType.ICE, 3));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
sa.getTargets().add(depthsList.getFirst());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get rid of Planeswalkers:
|
||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
||||
|
||||
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS,
|
||||
CardPredicates.hasCounter(CounterType.LOYALTY, 5));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||
return true;
|
||||
}
|
||||
|
||||
} else if (type.matches("Any")) {
|
||||
// variable amount for Hex Parasite
|
||||
int amount;
|
||||
boolean xPay = false;
|
||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
if (manaLeft == 0) {
|
||||
return false;
|
||||
}
|
||||
amount = manaLeft;
|
||||
xPay = true;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
// try to remove them from Dark Depths and Planeswalkers too
|
||||
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||
CardPredicates.hasCounter(CounterType.ICE));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
Card depth = depthsList.getFirst();
|
||||
int ice = depth.getCounters(CounterType.ICE);
|
||||
if (amount >= ice) {
|
||||
sa.getTargets().add(depth);
|
||||
if (xPay) {
|
||||
source.setSVar("PayX", Integer.toString(ice));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get rid of Planeswalkers:
|
||||
list = game.getPlayers().getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
||||
|
||||
CardCollection planeswalkerList = CardLists.filter(list,
|
||||
Predicates.and(CardPredicates.Presets.PLANEWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
|
||||
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
|
||||
sa.getTargets().add(best);
|
||||
if (xPay) {
|
||||
source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// some rules only for amount = 1
|
||||
if (!xPay) {
|
||||
// do as M1M1 part
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
|
||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
||||
|
||||
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
|
||||
if (!aiPersistList.isEmpty()) {
|
||||
aiM1M1List = aiPersistList;
|
||||
}
|
||||
|
||||
if (!aiM1M1List.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
|
||||
return true;
|
||||
}
|
||||
|
||||
// do as P1P1 part
|
||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
||||
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
|
||||
|
||||
if (!aiUndyingList.isEmpty()) {
|
||||
aiP1P1List = aiUndyingList;
|
||||
}
|
||||
if (!aiP1P1List.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
|
||||
return true;
|
||||
}
|
||||
|
||||
// fallback to remove any counter from opponent
|
||||
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
|
||||
if (!oppList.isEmpty()) {
|
||||
final Card best = ComputerUtilCard.getBestAI(oppList);
|
||||
|
||||
for (final CounterType aType : best.getCounters().keySet()) {
|
||||
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type.equals("M1M1")) {
|
||||
// no special amount for that one yet
|
||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount));
|
||||
|
||||
CardCollection aiPersist = CardLists.getKeyword(aiList, "Persist");
|
||||
if (!aiPersist.isEmpty()) {
|
||||
aiList = aiPersist;
|
||||
}
|
||||
|
||||
// TODO do not remove -1/-1 counters from cards which does need
|
||||
// them for abilities
|
||||
|
||||
if (!aiList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
||||
return true;
|
||||
}
|
||||
|
||||
} else if (type.equals("P1P1")) {
|
||||
// no special amount for that one yet
|
||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
|
||||
list = CardLists.filter(list, CardPredicates.hasCounter(CounterType.P1P1, amount));
|
||||
|
||||
// currently only logic for Bloodcrazed Hoplite, but add logic for
|
||||
// targeting ai creatures too
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
if (!aiList.isEmpty()) {
|
||||
CardCollection aiListUndying = CardLists.getKeyword(aiList, "Undying");
|
||||
if (!aiListUndying.isEmpty()) {
|
||||
aiList = aiListUndying;
|
||||
}
|
||||
if (!aiList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// need to target opponent creatures
|
||||
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||
if (!oppList.isEmpty()) {
|
||||
CardCollection oppListNotUndying = CardLists.getNotKeyword(oppList, "Undying");
|
||||
if (!oppListNotUndying.isEmpty()) {
|
||||
oppList = oppListNotUndying;
|
||||
}
|
||||
|
||||
if (!oppList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppList));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (type.equals("TIME")) {
|
||||
int amount;
|
||||
boolean xPay = false;
|
||||
// Timecrafting has X R
|
||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
if (manaLeft == 0) {
|
||||
return false;
|
||||
}
|
||||
amount = manaLeft;
|
||||
xPay = true;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterType.TIME, amount));
|
||||
|
||||
if (!timeList.isEmpty()) {
|
||||
Card best = ComputerUtilCard.getBestAI(timeList);
|
||||
|
||||
int timeCount = best.getCounters(CounterType.TIME);
|
||||
sa.getTargets().add(best);
|
||||
if (xPay) {
|
||||
source.setSVar("PayX", Integer.toString(timeCount));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (mandatory) {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
return doTgt(aiPlayer, sa, mandatory);
|
||||
}
|
||||
return mandatory;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseNumber(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, int, int, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||
// TODO Auto-generated method stub
|
||||
return super.chooseNumber(player, sa, min, max, params);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
|
||||
* forge.game.spellability.SpellAbility, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||
if (options.size() <= 1) {
|
||||
return super.chooseCounterType(options, sa, params);
|
||||
}
|
||||
Player ai = sa.getActivatingPlayer();
|
||||
Card target = (Card) params.get("Target");
|
||||
|
||||
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 (options.contains(CounterType.M1M1) && target.hasKeyword("Persist")) {
|
||||
return CounterType.M1M1;
|
||||
} else if (options.contains(CounterType.P1P1) && target.hasKeyword("Undying")) {
|
||||
return CounterType.M1M1;
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (ComputerUtil.isNegativeCounter(type, target)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.chooseCounterType(options, sa, params);
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CountersRemoveAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
||||
final String type = sa.getParam("CounterType");
|
||||
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases") && !type.equals("M1M1")) {
|
||||
return false;
|
||||
}
|
||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler,
|
||||
* java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
|
||||
if ("EndOfOpponentsTurn".equals(logic)) {
|
||||
if (!ph.is(PhaseType.END_OF_TURN) || !ph.getNextTurn().equals(ai)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return super.checkPhaseRestrictions(ai, sa, ph, logic);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
|
||||
final String type = sa.getParam("CounterType");
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
return doTgt(ai, sa, false);
|
||||
}
|
||||
|
||||
if (!type.matches("Any") && !type.matches("All")) {
|
||||
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
|
||||
if (currCounters < 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return super.checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
|
||||
// remove counter with Time might use Exile Zone too
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
CardCollection list = new CardCollection(game.getCardsIn(tgt.getZone()));
|
||||
// need to targetable
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
|
||||
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
if (type.matches("All")) {
|
||||
// Logic Part for Vampire Hexmage
|
||||
// Break Dark Depths
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||
CardPredicates.hasCounter(CounterType.ICE, 3));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
sa.getTargets().add(depthsList.getFirst());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get rid of Planeswalkers:
|
||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
||||
|
||||
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS,
|
||||
CardPredicates.hasCounter(CounterType.LOYALTY, 5));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||
return true;
|
||||
}
|
||||
|
||||
} else if (type.matches("Any")) {
|
||||
// variable amount for Hex Parasite
|
||||
int amount;
|
||||
boolean xPay = false;
|
||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
if (manaLeft == 0) {
|
||||
return false;
|
||||
}
|
||||
amount = manaLeft;
|
||||
xPay = true;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
// try to remove them from Dark Depths and Planeswalkers too
|
||||
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||
CardPredicates.hasCounter(CounterType.ICE));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
Card depth = depthsList.getFirst();
|
||||
int ice = depth.getCounters(CounterType.ICE);
|
||||
if (amount >= ice) {
|
||||
sa.getTargets().add(depth);
|
||||
if (xPay) {
|
||||
source.setSVar("PayX", Integer.toString(ice));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get rid of Planeswalkers:
|
||||
list = game.getPlayers().getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
||||
|
||||
CardCollection planeswalkerList = CardLists.filter(list,
|
||||
Predicates.and(CardPredicates.Presets.PLANEWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
|
||||
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
|
||||
sa.getTargets().add(best);
|
||||
if (xPay) {
|
||||
source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// some rules only for amount = 1
|
||||
if (!xPay) {
|
||||
// do as M1M1 part
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
|
||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
||||
|
||||
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
|
||||
if (!aiPersistList.isEmpty()) {
|
||||
aiM1M1List = aiPersistList;
|
||||
}
|
||||
|
||||
if (!aiM1M1List.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
|
||||
return true;
|
||||
}
|
||||
|
||||
// do as P1P1 part
|
||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
||||
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
|
||||
|
||||
if (!aiUndyingList.isEmpty()) {
|
||||
aiP1P1List = aiUndyingList;
|
||||
}
|
||||
if (!aiP1P1List.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
|
||||
return true;
|
||||
}
|
||||
|
||||
// fallback to remove any counter from opponent
|
||||
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
|
||||
if (!oppList.isEmpty()) {
|
||||
final Card best = ComputerUtilCard.getBestAI(oppList);
|
||||
|
||||
for (final CounterType aType : best.getCounters().keySet()) {
|
||||
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
||||
sa.getTargets().add(best);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type.equals("M1M1")) {
|
||||
// no special amount for that one yet
|
||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount));
|
||||
|
||||
CardCollection aiPersist = CardLists.getKeyword(aiList, "Persist");
|
||||
if (!aiPersist.isEmpty()) {
|
||||
aiList = aiPersist;
|
||||
}
|
||||
|
||||
// TODO do not remove -1/-1 counters from cards which does need
|
||||
// them for abilities
|
||||
|
||||
if (!aiList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
||||
return true;
|
||||
}
|
||||
|
||||
} else if (type.equals("P1P1")) {
|
||||
// no special amount for that one yet
|
||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
|
||||
list = CardLists.filter(list, CardPredicates.hasCounter(CounterType.P1P1, amount));
|
||||
|
||||
// currently only logic for Bloodcrazed Hoplite, but add logic for
|
||||
// targeting ai creatures too
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
if (!aiList.isEmpty()) {
|
||||
CardCollection aiListUndying = CardLists.getKeyword(aiList, "Undying");
|
||||
if (!aiListUndying.isEmpty()) {
|
||||
aiList = aiListUndying;
|
||||
}
|
||||
if (!aiList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// need to target opponent creatures
|
||||
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||
if (!oppList.isEmpty()) {
|
||||
CardCollection oppListNotUndying = CardLists.getNotKeyword(oppList, "Undying");
|
||||
if (!oppListNotUndying.isEmpty()) {
|
||||
oppList = oppListNotUndying;
|
||||
}
|
||||
|
||||
if (!oppList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppList));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (type.equals("TIME")) {
|
||||
int amount;
|
||||
boolean xPay = false;
|
||||
// Timecrafting has X R
|
||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
if (manaLeft == 0) {
|
||||
return false;
|
||||
}
|
||||
amount = manaLeft;
|
||||
xPay = true;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterType.TIME, amount));
|
||||
|
||||
if (!timeList.isEmpty()) {
|
||||
Card best = ComputerUtilCard.getBestAI(timeList);
|
||||
|
||||
int timeCount = best.getCounters(CounterType.TIME);
|
||||
sa.getTargets().add(best);
|
||||
if (xPay) {
|
||||
source.setSVar("PayX", Integer.toString(timeCount));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (mandatory) {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
return doTgt(aiPlayer, sa, mandatory);
|
||||
}
|
||||
return mandatory;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseNumber(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, int, int, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||
// TODO Auto-generated method stub
|
||||
return super.chooseNumber(player, sa, min, max, params);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
|
||||
* forge.game.spellability.SpellAbility, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||
if (options.size() <= 1) {
|
||||
return super.chooseCounterType(options, sa, params);
|
||||
}
|
||||
Player ai = sa.getActivatingPlayer();
|
||||
Card target = (Card) params.get("Target");
|
||||
|
||||
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 (options.contains(CounterType.M1M1) && target.hasKeyword("Persist")) {
|
||||
return CounterType.M1M1;
|
||||
} else if (options.contains(CounterType.P1P1) && target.hasKeyword("Undying")) {
|
||||
return CounterType.M1M1;
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (ComputerUtil.isNegativeCounter(type, target)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.chooseCounterType(options, sa, params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,103 +1,103 @@
|
||||
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;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
|
||||
int restDamage = d;
|
||||
final Game game = comp.getGame();
|
||||
Player enemy = ComputerUtil.getOpponentFor(comp);
|
||||
boolean dmgByCardsInHand = false;
|
||||
|
||||
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
|
||||
sa.getHostCard().getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) {
|
||||
dmgByCardsInHand = true;
|
||||
}
|
||||
|
||||
if (!sa.canTarget(enemy)) {
|
||||
return false;
|
||||
}
|
||||
if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// burn Planeswalkers
|
||||
if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!noPrevention) {
|
||||
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getHostCard(), false);
|
||||
} else {
|
||||
restDamage = enemy.staticReplaceDamage(restDamage, sa.getHostCard(), false);
|
||||
}
|
||||
|
||||
if (restDamage == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!enemy.canLoseLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
|
||||
|
||||
if ((enemy.getLife() - restDamage) < 5) {
|
||||
// drop the human to less than 5
|
||||
// life
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sa.isSpell()) {
|
||||
PhaseHandler phase = game.getPhaseHandler();
|
||||
// If this is a spell, cast it instead of discarding
|
||||
if ((phase.is(PhaseType.END_OF_TURN) || phase.is(PhaseType.MAIN2))
|
||||
&& phase.isPlayerTurn(comp) && (hand.size() > comp.getMaxHandSize())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// chance to burn player based on current hand size
|
||||
if (hand.size() > 2) {
|
||||
float value = 0;
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
//lower chance for sorcery as other spells may be cast in main2
|
||||
if (phase.isPlayerTurn(comp) && phase.is(PhaseType.MAIN2)) {
|
||||
value = 1.0f * restDamage / enemy.getLife();
|
||||
}
|
||||
} else {
|
||||
if (phase.isPlayerTurn(enemy)) {
|
||||
if (phase.is(PhaseType.END_OF_TURN)
|
||||
|| ((dmgByCardsInHand && phase.getPhase().isAfter(PhaseType.UPKEEP)))) {
|
||||
value = 1.5f * restDamage / enemy.getLife();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value > 0) { //more likely to burn with larger hand
|
||||
for (int i = 3; i < hand.size(); i++) {
|
||||
value *= 1.1f;
|
||||
}
|
||||
}
|
||||
if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time
|
||||
return false;
|
||||
} else {
|
||||
final float chance = MyRandom.getRandom().nextFloat();
|
||||
return chance < value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
|
||||
int restDamage = d;
|
||||
final Game game = comp.getGame();
|
||||
Player enemy = ComputerUtil.getOpponentFor(comp);
|
||||
boolean dmgByCardsInHand = false;
|
||||
|
||||
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
|
||||
sa.getHostCard().getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) {
|
||||
dmgByCardsInHand = true;
|
||||
}
|
||||
|
||||
if (!sa.canTarget(enemy)) {
|
||||
return false;
|
||||
}
|
||||
if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// burn Planeswalkers
|
||||
if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!noPrevention) {
|
||||
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getHostCard(), false);
|
||||
} else {
|
||||
restDamage = enemy.staticReplaceDamage(restDamage, sa.getHostCard(), false);
|
||||
}
|
||||
|
||||
if (restDamage == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!enemy.canLoseLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
|
||||
|
||||
if ((enemy.getLife() - restDamage) < 5) {
|
||||
// drop the human to less than 5
|
||||
// life
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sa.isSpell()) {
|
||||
PhaseHandler phase = game.getPhaseHandler();
|
||||
// If this is a spell, cast it instead of discarding
|
||||
if ((phase.is(PhaseType.END_OF_TURN) || phase.is(PhaseType.MAIN2))
|
||||
&& phase.isPlayerTurn(comp) && (hand.size() > comp.getMaxHandSize())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// chance to burn player based on current hand size
|
||||
if (hand.size() > 2) {
|
||||
float value = 0;
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
//lower chance for sorcery as other spells may be cast in main2
|
||||
if (phase.isPlayerTurn(comp) && phase.is(PhaseType.MAIN2)) {
|
||||
value = 1.0f * restDamage / enemy.getLife();
|
||||
}
|
||||
} else {
|
||||
if (phase.isPlayerTurn(enemy)) {
|
||||
if (phase.is(PhaseType.END_OF_TURN)
|
||||
|| ((dmgByCardsInHand && phase.getPhase().isAfter(PhaseType.UPKEEP)))) {
|
||||
value = 1.5f * restDamage / enemy.getLife();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value > 0) { //more likely to burn with larger hand
|
||||
for (int i = 3; i < hand.size(); i++) {
|
||||
value *= 1.1f;
|
||||
}
|
||||
}
|
||||
if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time
|
||||
return false;
|
||||
} else {
|
||||
final float chance = MyRandom.getRandom().nextFloat();
|
||||
return chance < value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,332 +1,332 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class DamageAllAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// AI needs to be expanded, since this function can be pretty complex
|
||||
// based on what the expected targets could be
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
final Random r = MyRandom.getRandom();
|
||||
if (r.nextFloat() > Math.pow(.9, sa.getActivationsThisTurn())) {
|
||||
return false;
|
||||
}
|
||||
// abCost stuff that should probably be centralized...
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for some costs
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// wait until stack is empty (prevents duplicate kills)
|
||||
if (!ai.getGame().getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int x = -1;
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$Converge")) {
|
||||
dmg = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||
}
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
}
|
||||
if (damage.equals("ChosenX")) {
|
||||
x = source.getCounters(CounterType.LOYALTY);
|
||||
}
|
||||
if (x == -1) {
|
||||
Player bestOpp = determineOppToKill(ai, sa, source, dmg);
|
||||
if (determineOppToKill(ai, sa, source, dmg) != null) {
|
||||
// we already know we can kill a player, so go for it
|
||||
return true;
|
||||
}
|
||||
// look for other value in this (damaging creatures or
|
||||
// creatures + player, e.g. Pestilence, etc.)
|
||||
return evaluateDamageAll(ai, sa, source, dmg) > 0;
|
||||
} else {
|
||||
int best = -1, best_x = -1;
|
||||
Player bestOpp = determineOppToKill(ai, sa, source, x);
|
||||
if (bestOpp != null) {
|
||||
// we can finish off a player, so go for it
|
||||
|
||||
// TODO: improve this by possibly damaging more creatures
|
||||
// on the battlefield belonging to other opponents at the same
|
||||
// time, if viable
|
||||
best_x = bestOpp.getLife();
|
||||
} else {
|
||||
// see if it's possible to get value from killing off creatures
|
||||
for (int i = 0; i <= x; i++) {
|
||||
final int value = evaluateDamageAll(ai, sa, source, i);
|
||||
if (value > best) {
|
||||
best = value;
|
||||
best_x = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best_x > 0) {
|
||||
if (sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
source.setSVar("PayX", Integer.toString(best_x));
|
||||
}
|
||||
if (damage.equals("ChosenX")) {
|
||||
source.setSVar("ChosenX", "Number$" + best_x);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Player determineOppToKill(Player ai, SpellAbility sa, Card source, int x) {
|
||||
// Attempt to determine which opponent can be finished off such that the most players
|
||||
// are killed at the same time, given X damage tops
|
||||
final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : "";
|
||||
int aiLife = ai.getLife();
|
||||
Player bestOpp = null; // default opponent, if all else fails
|
||||
|
||||
for (int dmg = 1; dmg <= x; dmg++) {
|
||||
// Don't kill yourself in the process
|
||||
if (validP.equals("Player") && aiLife <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)) {
|
||||
break;
|
||||
}
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
if ((validP.equals("Player") || validP.contains("Opponent"))
|
||||
&& (opp.getLife() <= ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
|
||||
bestOpp = opp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestOpp;
|
||||
}
|
||||
|
||||
private int evaluateDamageAll(Player ai, SpellAbility sa, final Card source, int dmg) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final CardCollection humanList = getKillableCreatures(sa, opp, dmg);
|
||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null && sa.canTarget(opp)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
computerList.clear();
|
||||
}
|
||||
|
||||
final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : "";
|
||||
// TODO: if damage is dependant on mana paid, maybe have X be human's max life
|
||||
// Don't kill yourself
|
||||
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int minGain = 200; // The minimum gain in destroyed creatures
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
|
||||
if (computerList.isEmpty()) {
|
||||
minGain = 10; // nothing to lose
|
||||
// no creatures to lose and player can be damaged
|
||||
// so do it if it's helping!
|
||||
// ----------------------------
|
||||
// needs future improvement on pestilence :
|
||||
// what if we lose creatures but can win by repeated activations?
|
||||
// that tactic only works if there are creatures left to keep pestilence in play
|
||||
// and can kill the player in a reasonable amount of time (no more than 2-3 turns?)
|
||||
if (validP.equals("Player")) {
|
||||
if (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) > 0) {
|
||||
// When using Pestilence to hurt players, do it at
|
||||
// the end of the opponent's turn only
|
||||
if ((!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic")))
|
||||
|| ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
|
||||
&& (ai.getGame().getNonactivePlayers().contains(ai)))))
|
||||
// Need further improvement : if able to kill immediately with repeated activations, do not wait
|
||||
// for phases! Will also need to implement considering repeated activations for killed creatures!
|
||||
// || (ai.sa.getPayCosts(). ??? )
|
||||
{
|
||||
// would take zero damage, and hurt opponent, do it!
|
||||
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)<1) {
|
||||
return 1;
|
||||
}
|
||||
// enemy is expected to die faster than AI from damage if repeated
|
||||
if (ai.getLife() > ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)
|
||||
* ((opp.getLife() + ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) - 1)
|
||||
/ ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
|
||||
// enemy below 10 life, go for it!
|
||||
if ((opp.getLife() < 10)
|
||||
&& (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) >= 1)) {
|
||||
return 1;
|
||||
}
|
||||
// At least half enemy remaining life can be removed in one go
|
||||
// worth doing even if enemy still has high health - one more copy of spell to win!
|
||||
if (opp.getLife() <= 2 * ComputerUtilCombat.predictDamageTo(opp, dmg, source, false)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
minGain = 100; // safety for errors in evaluate creature
|
||||
}
|
||||
} else if (sa.getSubAbility() != null && ai.getGame().getPhaseHandler().isPreCombatMain() && computerList.isEmpty()
|
||||
&& opp.getCreaturesInPlay().size() > 1 && !ai.getCreaturesInPlay().isEmpty()) {
|
||||
minGain = 126; // prepare for attack
|
||||
}
|
||||
|
||||
return ComputerUtilCard.evaluateCreatureList(humanList) - ComputerUtilCard.evaluateCreatureList(computerList)
|
||||
- minGain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
final Card source = sa.getHostCard();
|
||||
String validP = "";
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
}
|
||||
|
||||
if (sa.hasParam("ValidPlayers")) {
|
||||
validP = sa.getParam("ValidPlayers");
|
||||
}
|
||||
|
||||
// Evaluate creatures getting killed
|
||||
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if (tgt != null && sa.canTarget(enemy)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(enemy);
|
||||
computerList.clear();
|
||||
}
|
||||
// Don't get yourself killed
|
||||
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we can kill human, do it
|
||||
if ((validP.equals("Player") || validP.equals("Opponent") || validP.contains("Targeted"))
|
||||
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) > ComputerUtilCard
|
||||
.evaluateCreatureList(humanList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getKillableCreatures.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param player
|
||||
* a {@link forge.game.player.Player} object.
|
||||
* @param dmg
|
||||
* a int.
|
||||
* @return a {@link forge.game.card.CardCollection} object.
|
||||
*/
|
||||
private CardCollection getKillableCreatures(final SpellAbility sa, final Player player, final int dmg) {
|
||||
final Card source = sa.getHostCard();
|
||||
String validC = sa.hasParam("ValidCards") ? sa.getParam("ValidCards") : "";
|
||||
|
||||
// TODO: X may be something different than X paid
|
||||
CardCollection list =
|
||||
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC.split(","), source.getController(), source, sa);
|
||||
|
||||
final Predicate<Card> filterKillable = new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c));
|
||||
}
|
||||
};
|
||||
|
||||
list = CardLists.getNotKeyword(list, "Indestructible");
|
||||
list = CardLists.filter(list, filterKillable);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
String validP = "";
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
}
|
||||
|
||||
if (sa.hasParam("ValidPlayers")) {
|
||||
validP = sa.getParam("ValidPlayers");
|
||||
}
|
||||
|
||||
// Evaluate creatures getting killed
|
||||
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if (tgt != null && sa.canTarget(enemy)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(enemy);
|
||||
computerList.clear();
|
||||
}
|
||||
|
||||
// If it's not mandatory check a few things
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
// Don't get yourself killed
|
||||
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we can kill human, do it
|
||||
if ((validP.equals("Player") || validP.contains("Opponent") || validP.contains("Targeted"))
|
||||
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) + 50 >= ComputerUtilCard
|
||||
.evaluateCreatureList(humanList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class DamageAllAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// AI needs to be expanded, since this function can be pretty complex
|
||||
// based on what the expected targets could be
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
final Random r = MyRandom.getRandom();
|
||||
if (r.nextFloat() > Math.pow(.9, sa.getActivationsThisTurn())) {
|
||||
return false;
|
||||
}
|
||||
// abCost stuff that should probably be centralized...
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for some costs
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// wait until stack is empty (prevents duplicate kills)
|
||||
if (!ai.getGame().getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int x = -1;
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$Converge")) {
|
||||
dmg = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||
}
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
}
|
||||
if (damage.equals("ChosenX")) {
|
||||
x = source.getCounters(CounterType.LOYALTY);
|
||||
}
|
||||
if (x == -1) {
|
||||
Player bestOpp = determineOppToKill(ai, sa, source, dmg);
|
||||
if (determineOppToKill(ai, sa, source, dmg) != null) {
|
||||
// we already know we can kill a player, so go for it
|
||||
return true;
|
||||
}
|
||||
// look for other value in this (damaging creatures or
|
||||
// creatures + player, e.g. Pestilence, etc.)
|
||||
return evaluateDamageAll(ai, sa, source, dmg) > 0;
|
||||
} else {
|
||||
int best = -1, best_x = -1;
|
||||
Player bestOpp = determineOppToKill(ai, sa, source, x);
|
||||
if (bestOpp != null) {
|
||||
// we can finish off a player, so go for it
|
||||
|
||||
// TODO: improve this by possibly damaging more creatures
|
||||
// on the battlefield belonging to other opponents at the same
|
||||
// time, if viable
|
||||
best_x = bestOpp.getLife();
|
||||
} else {
|
||||
// see if it's possible to get value from killing off creatures
|
||||
for (int i = 0; i <= x; i++) {
|
||||
final int value = evaluateDamageAll(ai, sa, source, i);
|
||||
if (value > best) {
|
||||
best = value;
|
||||
best_x = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best_x > 0) {
|
||||
if (sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
source.setSVar("PayX", Integer.toString(best_x));
|
||||
}
|
||||
if (damage.equals("ChosenX")) {
|
||||
source.setSVar("ChosenX", "Number$" + best_x);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Player determineOppToKill(Player ai, SpellAbility sa, Card source, int x) {
|
||||
// Attempt to determine which opponent can be finished off such that the most players
|
||||
// are killed at the same time, given X damage tops
|
||||
final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : "";
|
||||
int aiLife = ai.getLife();
|
||||
Player bestOpp = null; // default opponent, if all else fails
|
||||
|
||||
for (int dmg = 1; dmg <= x; dmg++) {
|
||||
// Don't kill yourself in the process
|
||||
if (validP.equals("Player") && aiLife <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)) {
|
||||
break;
|
||||
}
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
if ((validP.equals("Player") || validP.contains("Opponent"))
|
||||
&& (opp.getLife() <= ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
|
||||
bestOpp = opp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestOpp;
|
||||
}
|
||||
|
||||
private int evaluateDamageAll(Player ai, SpellAbility sa, final Card source, int dmg) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final CardCollection humanList = getKillableCreatures(sa, opp, dmg);
|
||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null && sa.canTarget(opp)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
computerList.clear();
|
||||
}
|
||||
|
||||
final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : "";
|
||||
// TODO: if damage is dependant on mana paid, maybe have X be human's max life
|
||||
// Don't kill yourself
|
||||
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int minGain = 200; // The minimum gain in destroyed creatures
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
|
||||
if (computerList.isEmpty()) {
|
||||
minGain = 10; // nothing to lose
|
||||
// no creatures to lose and player can be damaged
|
||||
// so do it if it's helping!
|
||||
// ----------------------------
|
||||
// needs future improvement on pestilence :
|
||||
// what if we lose creatures but can win by repeated activations?
|
||||
// that tactic only works if there are creatures left to keep pestilence in play
|
||||
// and can kill the player in a reasonable amount of time (no more than 2-3 turns?)
|
||||
if (validP.equals("Player")) {
|
||||
if (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) > 0) {
|
||||
// When using Pestilence to hurt players, do it at
|
||||
// the end of the opponent's turn only
|
||||
if ((!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic")))
|
||||
|| ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
|
||||
&& (ai.getGame().getNonactivePlayers().contains(ai)))))
|
||||
// Need further improvement : if able to kill immediately with repeated activations, do not wait
|
||||
// for phases! Will also need to implement considering repeated activations for killed creatures!
|
||||
// || (ai.sa.getPayCosts(). ??? )
|
||||
{
|
||||
// would take zero damage, and hurt opponent, do it!
|
||||
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)<1) {
|
||||
return 1;
|
||||
}
|
||||
// enemy is expected to die faster than AI from damage if repeated
|
||||
if (ai.getLife() > ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)
|
||||
* ((opp.getLife() + ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) - 1)
|
||||
/ ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
|
||||
// enemy below 10 life, go for it!
|
||||
if ((opp.getLife() < 10)
|
||||
&& (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) >= 1)) {
|
||||
return 1;
|
||||
}
|
||||
// At least half enemy remaining life can be removed in one go
|
||||
// worth doing even if enemy still has high health - one more copy of spell to win!
|
||||
if (opp.getLife() <= 2 * ComputerUtilCombat.predictDamageTo(opp, dmg, source, false)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
minGain = 100; // safety for errors in evaluate creature
|
||||
}
|
||||
} else if (sa.getSubAbility() != null && ai.getGame().getPhaseHandler().isPreCombatMain() && computerList.isEmpty()
|
||||
&& opp.getCreaturesInPlay().size() > 1 && !ai.getCreaturesInPlay().isEmpty()) {
|
||||
minGain = 126; // prepare for attack
|
||||
}
|
||||
|
||||
return ComputerUtilCard.evaluateCreatureList(humanList) - ComputerUtilCard.evaluateCreatureList(computerList)
|
||||
- minGain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
final Card source = sa.getHostCard();
|
||||
String validP = "";
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
}
|
||||
|
||||
if (sa.hasParam("ValidPlayers")) {
|
||||
validP = sa.getParam("ValidPlayers");
|
||||
}
|
||||
|
||||
// Evaluate creatures getting killed
|
||||
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if (tgt != null && sa.canTarget(enemy)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(enemy);
|
||||
computerList.clear();
|
||||
}
|
||||
// Don't get yourself killed
|
||||
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we can kill human, do it
|
||||
if ((validP.equals("Player") || validP.equals("Opponent") || validP.contains("Targeted"))
|
||||
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) > ComputerUtilCard
|
||||
.evaluateCreatureList(humanList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getKillableCreatures.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param player
|
||||
* a {@link forge.game.player.Player} object.
|
||||
* @param dmg
|
||||
* a int.
|
||||
* @return a {@link forge.game.card.CardCollection} object.
|
||||
*/
|
||||
private CardCollection getKillableCreatures(final SpellAbility sa, final Player player, final int dmg) {
|
||||
final Card source = sa.getHostCard();
|
||||
String validC = sa.hasParam("ValidCards") ? sa.getParam("ValidCards") : "";
|
||||
|
||||
// TODO: X may be something different than X paid
|
||||
CardCollection list =
|
||||
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC.split(","), source.getController(), source, sa);
|
||||
|
||||
final Predicate<Card> filterKillable = new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c));
|
||||
}
|
||||
};
|
||||
|
||||
list = CardLists.getNotKeyword(list, "Indestructible");
|
||||
list = CardLists.filter(list, filterKillable);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
String validP = "";
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
}
|
||||
|
||||
if (sa.hasParam("ValidPlayers")) {
|
||||
validP = sa.getParam("ValidPlayers");
|
||||
}
|
||||
|
||||
// Evaluate creatures getting killed
|
||||
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if (tgt != null && sa.canTarget(enemy)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(enemy);
|
||||
computerList.clear();
|
||||
}
|
||||
|
||||
// If it's not mandatory check a few things
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
// Don't get yourself killed
|
||||
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we can kill human, do it
|
||||
if ((validP.equals("Player") || validP.contains("Opponent") || validP.contains("Targeted"))
|
||||
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) + 50 >= ComputerUtilCard
|
||||
.evaluateCreatureList(humanList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,54 +1,54 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
public class DamageEachAi extends DamageAiBase {
|
||||
|
||||
/* (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 TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final String logic = sa.getParam("AILogic");
|
||||
|
||||
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
Player weakestOpp = targetableOpps.min(PlayerPredicates.compareByLife());
|
||||
|
||||
if (tgt != null && weakestOpp != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(weakestOpp);
|
||||
}
|
||||
|
||||
if ("MadSarkhanUltimate".equals(logic)) {
|
||||
return SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp);
|
||||
}
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
return this.shouldTgtP(ai, sa, iDmg, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
// check AI life before playing this drawback?
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
public class DamageEachAi extends DamageAiBase {
|
||||
|
||||
/* (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 TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final String logic = sa.getParam("AILogic");
|
||||
|
||||
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
Player weakestOpp = targetableOpps.min(PlayerPredicates.compareByLife());
|
||||
|
||||
if (tgt != null && weakestOpp != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(weakestOpp);
|
||||
}
|
||||
|
||||
if ("MadSarkhanUltimate".equals(logic)) {
|
||||
return SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp);
|
||||
}
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
return this.shouldTgtP(ai, sa, iDmg, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
// check AI life before playing this drawback?
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,224 +1,224 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DamagePreventAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Card hostCard = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
boolean chance = false;
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
// temporarily disabled until better AI
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt == null) {
|
||||
// As far as I can tell these Defined Cards will only have one of
|
||||
// them
|
||||
final List<GameObject> objects = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
// react to threats on the stack
|
||||
if (!game.getStack().isEmpty()) {
|
||||
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
||||
for (final Object o : objects) {
|
||||
if (threatenedObjects.contains(o)) {
|
||||
chance = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PhaseHandler handler = game.getPhaseHandler();
|
||||
if (handler.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
boolean flag = false;
|
||||
for (final Object o : objects) {
|
||||
if (o instanceof Card) {
|
||||
flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, (Card) o, combat);
|
||||
} else if (o instanceof Player) {
|
||||
// Don't need to worry about Combat Damage during AI's turn
|
||||
final Player p = (Player) o;
|
||||
if (!handler.isPlayerTurn(p)) {
|
||||
flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, combat) && sa
|
||||
.isAbility()) || ComputerUtilCombat.lifeInDanger(ai, combat)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chance = flag;
|
||||
} else { // if nothing on the stack, and it's not declare
|
||||
// blockers. no need to prevent
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} // non-targeted
|
||||
|
||||
// react to threats on the stack
|
||||
else if (!game.getStack().isEmpty()) {
|
||||
sa.resetTargets();
|
||||
final TargetChoices tcs = sa.getTargets();
|
||||
// check stack for something on the stack will kill anything i control
|
||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
||||
|
||||
if (objects.contains(ai)) {
|
||||
tcs.add(ai);
|
||||
chance = true;
|
||||
}
|
||||
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);
|
||||
|
||||
for (final Card c : targetables) {
|
||||
if (objects.contains(c)) {
|
||||
threatenedTargets.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (!threatenedTargets.isEmpty()) {
|
||||
// Choose "best" of the remaining to save
|
||||
tcs.add(ComputerUtilCard.getBestCreatureAI(threatenedTargets));
|
||||
chance = true;
|
||||
}
|
||||
|
||||
} // Protect combatants
|
||||
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
sa.resetTargets();
|
||||
final TargetChoices tcs = sa.getTargets();
|
||||
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat)
|
||||
&& (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility() || sa.isTrigger())
|
||||
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
|
||||
tcs.add(ai);
|
||||
chance = true;
|
||||
} else {
|
||||
// filter AIs battlefield by what I can target
|
||||
CardCollectionView targetables = ai.getCardsIn(ZoneType.Battlefield);
|
||||
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard, sa);
|
||||
targetables = CardLists.getTargetableCards(targetables, sa);
|
||||
|
||||
if (targetables.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final CardCollection combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
|
||||
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||
|
||||
for (final Card c : combatants) {
|
||||
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) {
|
||||
tcs.add(c);
|
||||
chance = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
|
||||
tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
boolean chance = false;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt == null) {
|
||||
// If there's no target on the trigger, just say yes.
|
||||
chance = true;
|
||||
} else {
|
||||
chance = preventDamageMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* preventDamageMandatoryTarget.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
// filter AIs battlefield by what I can target
|
||||
final Game game = ai.getGame();
|
||||
CardCollectionView targetables = game.getCardsIn(ZoneType.Battlefield);
|
||||
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
|
||||
Card target = null;
|
||||
|
||||
if (targetables.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mandatory && compTargetables.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!compTargetables.isEmpty()) {
|
||||
final CardCollection combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
|
||||
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
Combat combat = game.getCombat();
|
||||
for (final Card c : combatants) {
|
||||
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
|
||||
target = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target == null) {
|
||||
target = combatants.get(0);
|
||||
}
|
||||
} else {
|
||||
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
|
||||
}
|
||||
sa.getTargets().add(target);
|
||||
if (sa.hasParam("DividedAsYouChoose")) {
|
||||
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DamagePreventAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Card hostCard = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
boolean chance = false;
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
// temporarily disabled until better AI
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt == null) {
|
||||
// As far as I can tell these Defined Cards will only have one of
|
||||
// them
|
||||
final List<GameObject> objects = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
// react to threats on the stack
|
||||
if (!game.getStack().isEmpty()) {
|
||||
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
||||
for (final Object o : objects) {
|
||||
if (threatenedObjects.contains(o)) {
|
||||
chance = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PhaseHandler handler = game.getPhaseHandler();
|
||||
if (handler.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
boolean flag = false;
|
||||
for (final Object o : objects) {
|
||||
if (o instanceof Card) {
|
||||
flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, (Card) o, combat);
|
||||
} else if (o instanceof Player) {
|
||||
// Don't need to worry about Combat Damage during AI's turn
|
||||
final Player p = (Player) o;
|
||||
if (!handler.isPlayerTurn(p)) {
|
||||
flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, combat) && sa
|
||||
.isAbility()) || ComputerUtilCombat.lifeInDanger(ai, combat)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chance = flag;
|
||||
} else { // if nothing on the stack, and it's not declare
|
||||
// blockers. no need to prevent
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} // non-targeted
|
||||
|
||||
// react to threats on the stack
|
||||
else if (!game.getStack().isEmpty()) {
|
||||
sa.resetTargets();
|
||||
final TargetChoices tcs = sa.getTargets();
|
||||
// check stack for something on the stack will kill anything i control
|
||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
||||
|
||||
if (objects.contains(ai)) {
|
||||
tcs.add(ai);
|
||||
chance = true;
|
||||
}
|
||||
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);
|
||||
|
||||
for (final Card c : targetables) {
|
||||
if (objects.contains(c)) {
|
||||
threatenedTargets.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (!threatenedTargets.isEmpty()) {
|
||||
// Choose "best" of the remaining to save
|
||||
tcs.add(ComputerUtilCard.getBestCreatureAI(threatenedTargets));
|
||||
chance = true;
|
||||
}
|
||||
|
||||
} // Protect combatants
|
||||
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
sa.resetTargets();
|
||||
final TargetChoices tcs = sa.getTargets();
|
||||
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat)
|
||||
&& (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility() || sa.isTrigger())
|
||||
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
|
||||
tcs.add(ai);
|
||||
chance = true;
|
||||
} else {
|
||||
// filter AIs battlefield by what I can target
|
||||
CardCollectionView targetables = ai.getCardsIn(ZoneType.Battlefield);
|
||||
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard, sa);
|
||||
targetables = CardLists.getTargetableCards(targetables, sa);
|
||||
|
||||
if (targetables.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final CardCollection combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
|
||||
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||
|
||||
for (final Card c : combatants) {
|
||||
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) {
|
||||
tcs.add(c);
|
||||
chance = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
|
||||
tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
boolean chance = false;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt == null) {
|
||||
// If there's no target on the trigger, just say yes.
|
||||
chance = true;
|
||||
} else {
|
||||
chance = preventDamageMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* preventDamageMandatoryTarget.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
// filter AIs battlefield by what I can target
|
||||
final Game game = ai.getGame();
|
||||
CardCollectionView targetables = game.getCardsIn(ZoneType.Battlefield);
|
||||
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
|
||||
Card target = null;
|
||||
|
||||
if (targetables.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mandatory && compTargetables.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!compTargetables.isEmpty()) {
|
||||
final CardCollection combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
|
||||
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
Combat combat = game.getCombat();
|
||||
for (final Card c : combatants) {
|
||||
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
|
||||
target = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target == null) {
|
||||
target = combatants.get(0);
|
||||
}
|
||||
} else {
|
||||
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
|
||||
}
|
||||
sa.getTargets().add(target);
|
||||
if (sa.hasParam("DividedAsYouChoose")) {
|
||||
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,59 +1,59 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class DamagePreventAllAi 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 Card hostCard = sa.getHostCard();
|
||||
boolean chance = false;
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
// temporarily disabled until better AI
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ai.getGame().getStack().isEmpty()) {
|
||||
// TODO check stack for something on the stack will kill anything i
|
||||
// control
|
||||
|
||||
} // Protect combatants
|
||||
else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
boolean chance = true;
|
||||
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class DamagePreventAllAi 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 Card hostCard = sa.getHostCard();
|
||||
boolean chance = false;
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
// temporarily disabled until better AI
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ai.getGame().getStack().isEmpty()) {
|
||||
// TODO check stack for something on the stack will kill anything i
|
||||
// control
|
||||
|
||||
} // Protect combatants
|
||||
else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
boolean chance = true;
|
||||
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,283 +1,283 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
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;
|
||||
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.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class DebuffAi extends SpellAbilityAi {
|
||||
// *************************************************************************
|
||||
// ***************************** Debuff ************************************
|
||||
// *************************************************************************
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||
// if there is no target and host card isn't in play, don't activate
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
// temporarily disabled until AI is improved
|
||||
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
|
||||
// Phase Restrictions
|
||||
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
|| !game.getStack().isEmpty()) {
|
||||
// Instant-speed pumps should not be cast outside of combat when the
|
||||
// stack is empty
|
||||
if (!SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
|
||||
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
|
||||
final Combat combat = game.getCombat();
|
||||
return Iterables.any(cards, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
|
||||
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
|
||||
return false;
|
||||
|
||||
if (!combat.isBlocking(c) && !combat.isAttacking(c)) {
|
||||
return false;
|
||||
}
|
||||
// don't add duplicate negative keywords
|
||||
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
|
||||
// here?
|
||||
} else {
|
||||
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // debuffDrawbackAI()
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* debuffTgtAI.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param kws
|
||||
* a {@link java.util.ArrayList} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean debuffTgtAI(final Player ai, final SpellAbility sa, final List<String> kws, final boolean mandatory) {
|
||||
// this would be for evasive things like Flying, Unblockable, etc
|
||||
if (!mandatory && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
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:
|
||||
// 1. make human creatures lose evasion when they are attacking
|
||||
// 2. make human creatures lose Flying/Horsemanship/Shadow/etc. when
|
||||
// Comp is attacking
|
||||
// 3. remove Indestructible keyword so it can be destroyed?
|
||||
// 3a. remove Persist?
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
Card t = null;
|
||||
// boolean goodt = false;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
if (mandatory) {
|
||||
return debuffMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
t = ComputerUtilCard.getBestCreatureAI(list);
|
||||
sa.getTargets().add(t);
|
||||
list.remove(t);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpTgtAI()
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getCurseCreatures.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param kws
|
||||
* a {@link java.util.ArrayList} object.
|
||||
* @return a CardCollection.
|
||||
*/
|
||||
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
||||
if (!list.isEmpty()) {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.hasAnyKeyword(kws); // don't add duplicate negative
|
||||
// keywords
|
||||
}
|
||||
});
|
||||
}
|
||||
return list;
|
||||
} // getCurseCreatures()
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* debuffMandatoryTarget.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean debuffMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(),
|
||||
sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
|
||||
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove anything that's already been targeted
|
||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||
list.remove(c);
|
||||
}
|
||||
|
||||
final CardCollection pref = CardLists.filterControlledBy(list, ComputerUtil.getOpponentFor(ai));
|
||||
final CardCollection forced = CardLists.filterControlledBy(list, ai);
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
if (pref.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Card c;
|
||||
if (CardLists.getNotType(pref, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getBestCreatureAI(pref);
|
||||
} else {
|
||||
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
|
||||
}
|
||||
|
||||
pref.remove(c);
|
||||
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (forced.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO - if forced targeting, just pick something without the given
|
||||
// keyword
|
||||
Card c;
|
||||
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
||||
} else {
|
||||
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
|
||||
}
|
||||
|
||||
forced.remove(c);
|
||||
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpMandatoryTarget()
|
||||
|
||||
@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<String>();
|
||||
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return debuffTgtAI(ai, sa, kws, mandatory);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
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;
|
||||
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.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class DebuffAi extends SpellAbilityAi {
|
||||
// *************************************************************************
|
||||
// ***************************** Debuff ************************************
|
||||
// *************************************************************************
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||
// if there is no target and host card isn't in play, don't activate
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
// temporarily disabled until AI is improved
|
||||
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
|
||||
// Phase Restrictions
|
||||
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
|| !game.getStack().isEmpty()) {
|
||||
// Instant-speed pumps should not be cast outside of combat when the
|
||||
// stack is empty
|
||||
if (!SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
|
||||
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
|
||||
final Combat combat = game.getCombat();
|
||||
return Iterables.any(cards, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
|
||||
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
|
||||
return false;
|
||||
|
||||
if (!combat.isBlocking(c) && !combat.isAttacking(c)) {
|
||||
return false;
|
||||
}
|
||||
// don't add duplicate negative keywords
|
||||
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
|
||||
// here?
|
||||
} else {
|
||||
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // debuffDrawbackAI()
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* debuffTgtAI.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param kws
|
||||
* a {@link java.util.ArrayList} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean debuffTgtAI(final Player ai, final SpellAbility sa, final List<String> kws, final boolean mandatory) {
|
||||
// this would be for evasive things like Flying, Unblockable, etc
|
||||
if (!mandatory && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
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:
|
||||
// 1. make human creatures lose evasion when they are attacking
|
||||
// 2. make human creatures lose Flying/Horsemanship/Shadow/etc. when
|
||||
// Comp is attacking
|
||||
// 3. remove Indestructible keyword so it can be destroyed?
|
||||
// 3a. remove Persist?
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
Card t = null;
|
||||
// boolean goodt = false;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
if (mandatory) {
|
||||
return debuffMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
t = ComputerUtilCard.getBestCreatureAI(list);
|
||||
sa.getTargets().add(t);
|
||||
list.remove(t);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpTgtAI()
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getCurseCreatures.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param kws
|
||||
* a {@link java.util.ArrayList} object.
|
||||
* @return a CardCollection.
|
||||
*/
|
||||
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
||||
if (!list.isEmpty()) {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.hasAnyKeyword(kws); // don't add duplicate negative
|
||||
// keywords
|
||||
}
|
||||
});
|
||||
}
|
||||
return list;
|
||||
} // getCurseCreatures()
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* debuffMandatoryTarget.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean debuffMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(),
|
||||
sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
|
||||
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove anything that's already been targeted
|
||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||
list.remove(c);
|
||||
}
|
||||
|
||||
final CardCollection pref = CardLists.filterControlledBy(list, ComputerUtil.getOpponentFor(ai));
|
||||
final CardCollection forced = CardLists.filterControlledBy(list, ai);
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
if (pref.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Card c;
|
||||
if (CardLists.getNotType(pref, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getBestCreatureAI(pref);
|
||||
} else {
|
||||
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
|
||||
}
|
||||
|
||||
pref.remove(c);
|
||||
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (forced.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO - if forced targeting, just pick something without the given
|
||||
// keyword
|
||||
Card c;
|
||||
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
||||
} else {
|
||||
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
|
||||
}
|
||||
|
||||
forced.remove(c);
|
||||
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpMandatoryTarget()
|
||||
|
||||
@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<String>();
|
||||
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return debuffTgtAI(ai, sa, kws, mandatory);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class DelayedTriggerAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
if ("Always".equals(sa.getParam("AILogic"))) {
|
||||
// TODO: improve ai
|
||||
return true;
|
||||
}
|
||||
SpellAbility trigsa = null;
|
||||
if (sa.hasAdditionalAbility("Execute")) {
|
||||
trigsa = sa.getAdditionalAbility("Execute");
|
||||
} else {
|
||||
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||
}
|
||||
trigsa.setActivatingPlayer(ai);
|
||||
|
||||
if (trigsa instanceof AbilitySub) {
|
||||
return SpellApiToAi.Converter.get(((AbilitySub) trigsa).getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
||||
} else {
|
||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
SpellAbility trigsa = null;
|
||||
if (sa.hasAdditionalAbility("Execute")) {
|
||||
trigsa = sa.getAdditionalAbility("Execute");
|
||||
} else {
|
||||
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||
}
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
trigsa.setActivatingPlayer(ai);
|
||||
|
||||
if (!sa.hasParam("OptionalDecider")) {
|
||||
return aic.doTrigger(trigsa, true);
|
||||
} else {
|
||||
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
SpellAbility trigsa = null;
|
||||
if (sa.hasAdditionalAbility("Execute")) {
|
||||
trigsa = sa.getAdditionalAbility("Execute");
|
||||
} else {
|
||||
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||
}
|
||||
trigsa.setActivatingPlayer(ai);
|
||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class DelayedTriggerAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
if ("Always".equals(sa.getParam("AILogic"))) {
|
||||
// TODO: improve ai
|
||||
return true;
|
||||
}
|
||||
SpellAbility trigsa = null;
|
||||
if (sa.hasAdditionalAbility("Execute")) {
|
||||
trigsa = sa.getAdditionalAbility("Execute");
|
||||
} else {
|
||||
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||
}
|
||||
trigsa.setActivatingPlayer(ai);
|
||||
|
||||
if (trigsa instanceof AbilitySub) {
|
||||
return SpellApiToAi.Converter.get(((AbilitySub) trigsa).getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
||||
} else {
|
||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
SpellAbility trigsa = null;
|
||||
if (sa.hasAdditionalAbility("Execute")) {
|
||||
trigsa = sa.getAdditionalAbility("Execute");
|
||||
} else {
|
||||
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||
}
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
trigsa.setActivatingPlayer(ai);
|
||||
|
||||
if (!sa.hasParam("OptionalDecider")) {
|
||||
return aic.doTrigger(trigsa, true);
|
||||
} else {
|
||||
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
SpellAbility trigsa = null;
|
||||
if (sa.hasAdditionalAbility("Execute")) {
|
||||
trigsa = sa.getAdditionalAbility("Execute");
|
||||
} else {
|
||||
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||
}
|
||||
trigsa.setActivatingPlayer(ai);
|
||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,439 +1,439 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.*;
|
||||
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.CostSacrifice;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class DestroyAi extends SpellAbilityAi {
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||
// AI needs to be expanded, since this function can be pretty complex
|
||||
// based on what the expected targets could be
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean noRegen = sa.hasParam("NoRegen");
|
||||
final String logic = sa.getParam("AILogic");
|
||||
boolean hasXCost = false;
|
||||
|
||||
CardCollection list;
|
||||
|
||||
if (abCost != null) {
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hasXCost = abCost.getCostMana() != null ? abCost.getCostMana().getAmountOfX() > 0 : false;
|
||||
}
|
||||
|
||||
if ("AtOpponentsCombatOrAfter".equals(sa.getParam("AILogic"))) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
if (ph.getPlayerTurn() == ai || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("AtEOT".equals(sa.getParam("AILogic"))) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
if (!ph.is(PhaseType.END_OF_TURN)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
if (!ph.is(PhaseType.END_OF_TURN) || ai.getAttackedWithCreatureThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Targeting
|
||||
if (abTgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.hasParam("TargetingPlayer")) {
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
sa.setTargetingPlayer(targetingPlayer);
|
||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||
}
|
||||
if ("MadSarkhanDragon".equals(logic)) {
|
||||
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
|
||||
} else if (logic != null && logic.startsWith("MinLoyalty.")) {
|
||||
int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
||||
if (source.getCounters(CounterType.LOYALTY) < minLoyalty) {
|
||||
return false;
|
||||
}
|
||||
} else if ("Polymorph".equals(logic)) {
|
||||
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (Card c : list) {
|
||||
if (c.hasKeyword("Indestructible")) {
|
||||
sa.getTargets().add(c);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Card worst = ComputerUtilCard.getWorstAI(list);
|
||||
if (worst.isCreature() && ComputerUtilCard.evaluateCreature(worst) >= 200) {
|
||||
return false;
|
||||
}
|
||||
if (!worst.isCreature() && worst.getCMC() > 1) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(worst);
|
||||
return true;
|
||||
}
|
||||
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
||||
if ("FatalPush".equals(logic)) {
|
||||
final int cmcMax = ai.hasRevolt() ? 4 : 2;
|
||||
list = CardLists.filter(list, CardPredicates.lessCMC(cmcMax));
|
||||
}
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, true);
|
||||
|
||||
list = CardLists.getNotKeyword(list, "Indestructible");
|
||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||
list = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, list, false);
|
||||
}
|
||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
//Check for cards that can be sacrificed in response
|
||||
for (final SpellAbility ability : c.getAllSpellAbilities()) {
|
||||
if (ability.isAbility()) {
|
||||
final Cost cost = ability.getPayCosts();
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (!(part instanceof CostSacrifice)) {
|
||||
continue;
|
||||
}
|
||||
CostSacrifice sacCost = (CostSacrifice) part;
|
||||
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (c.hasSVar("SacMe")) {
|
||||
return false;
|
||||
}
|
||||
//Check for undying
|
||||
return (!c.hasKeyword("Undying") || c.getCounters(CounterType.P1P1) > 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If NoRegen is not set, filter out creatures that have a
|
||||
// regeneration shield
|
||||
if (!noRegen) {
|
||||
// TODO filter out things that might be tougher?
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return (c.getShieldCount() == 0 && !ComputerUtil.canRegenerate(ai, c));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int maxTargets = abTgt.getMaxTargets(sa.getHostCard(), sa);
|
||||
|
||||
if (hasXCost) {
|
||||
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
||||
maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa));
|
||||
}
|
||||
if (sa.hasParam("AIMaxTgtsCount")) {
|
||||
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified
|
||||
// TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable?
|
||||
maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets);
|
||||
}
|
||||
|
||||
if (maxTargets == 0) {
|
||||
// can't afford X or otherwise target anything
|
||||
return false;
|
||||
}
|
||||
|
||||
// target loop
|
||||
while (sa.getTargets().getNumTargeted() < maxTargets) {
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Card choice = null;
|
||||
// If the targets are only of one type, take the best
|
||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||
if ("OppDestroyYours".equals(logic)) {
|
||||
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
||||
if (ComputerUtilCard.evaluateCreature(aiBest) > ComputerUtilCard.evaluateCreature(choice) - 40) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ("Pongify".equals(logic)) {
|
||||
final Card token = TokenAi.spawnToken(choice.getController(), sa.getSubAbility());
|
||||
if (token == null) {
|
||||
return true; // becomes Terminate
|
||||
} else {
|
||||
if (source.getGame().getPhaseHandler().getPhase()
|
||||
.isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) || // prevent surprise combatant
|
||||
ComputerUtilCard.evaluateCreature(choice) < 1.5
|
||||
* ComputerUtilCard.evaluateCreature(token)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestLandAI(list);
|
||||
|
||||
if ("LandForLand".equals(logic) || "GhostQuarter".equals(logic)) {
|
||||
// Strip Mine, Wasteland - cut short if the relevant logic fails
|
||||
if (!doLandForLandRemovalLogic(sa, ai, choice, logic)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||
}
|
||||
//option to hold removal instead only applies for single targeted removal
|
||||
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
|
||||
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Don't destroy stolen permanents when the stealing aura can be destroyed
|
||||
if (choice.getOwner() == ai) {
|
||||
for (Card aura : choice.getEnchantedBy(false)) {
|
||||
SpellAbility sp = aura.getFirstSpellAbility();
|
||||
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
|
||||
&& aura.getController() != ai && sa.canTarget(aura)) {
|
||||
choice = aura;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
} 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() < ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size()
|
||||
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|
||||
|| ai.getLife() <= 5)) {
|
||||
// Basic ai logic for Lethal Vapors
|
||||
return false;
|
||||
}
|
||||
|
||||
if (list.isEmpty()
|
||||
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|
||||
|| CardLists.getNotKeyword(list, "Indestructible").isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean noRegen = sa.hasParam("NoRegen");
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
|
||||
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
|
||||
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CardCollection preferred = CardLists.getNotKeyword(list, "Indestructible");
|
||||
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
|
||||
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
||||
preferred = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, preferred, false);
|
||||
}
|
||||
|
||||
// If NoRegen is not set, filter out creatures that have a
|
||||
// regeneration shield
|
||||
if (!noRegen) {
|
||||
// TODO filter out things that could regenerate in response?
|
||||
// might be tougher?
|
||||
preferred = CardLists.filter(preferred, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.getShieldCount() == 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
preferred = ComputerUtil.filterAITgts(sa, ai, (CardCollection)preferred, true);
|
||||
|
||||
for (final Card c : preferred) {
|
||||
list.remove(c);
|
||||
}
|
||||
|
||||
if (preferred.isEmpty() && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
if (preferred.isEmpty()) {
|
||||
if (sa.getTargets().getNumTargeted() == 0
|
||||
|| sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (!mandatory) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
Card c;
|
||||
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
||||
c = ComputerUtilCard.getBestCreatureAI(preferred);
|
||||
} else if (CardLists.getNotType(preferred, "Land").isEmpty()) {
|
||||
c = ComputerUtilCard.getBestLandAI(preferred);
|
||||
} else {
|
||||
c = ComputerUtilCard.getMostExpensivePermanentAI(preferred, sa, false);
|
||||
}
|
||||
sa.getTargets().add(c);
|
||||
preferred.remove(c);
|
||||
}
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (list.isEmpty()) {
|
||||
break;
|
||||
} else {
|
||||
Card c;
|
||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||
if (!sa.getUniqueTargets().isEmpty() && sa.getParent().getApi() == ApiType.Destroy
|
||||
&& sa.getUniqueTargets().get(0) instanceof Card) {
|
||||
// basic ai for Diaochan
|
||||
c = (Card) sa.getUniqueTargets().get(0);
|
||||
} else {
|
||||
c = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
}
|
||||
} else {
|
||||
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
|
||||
}
|
||||
sa.getTargets().add(c);
|
||||
list.remove(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!mandatory) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLand, String logic) {
|
||||
if (tgtLand == null) { return false; }
|
||||
|
||||
Player tgtPlayer = tgtLand.getController();
|
||||
int oppLandsOTB = tgtPlayer.getLandsInPlay().size();
|
||||
|
||||
// AI profile-dependent properties
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
int amountNoTempoCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK);
|
||||
int amountNoTimingCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK);
|
||||
int amountLandsInHand = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE);
|
||||
int amountLandsToManalock = aic.getIntProperty(AiProps.STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING);
|
||||
boolean highPriorityIfNoLandDrop = aic.getBooleanProperty(AiProps.STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP);
|
||||
|
||||
// if the opponent didn't play a land and has few lands OTB, might be worth mana-locking him
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
boolean oppSkippedLandDrop = (tgtPlayer.getLandsPlayedLastTurn() == 0 && ph.isPlayerTurn(ai))
|
||||
|| (tgtPlayer.getLandsPlayedThisTurn() == 0 && ph.isPlayerTurn(tgtPlayer) && ph.getPhase().isAfter(PhaseType.MAIN2));
|
||||
boolean canManaLock = oppLandsOTB <= amountLandsToManalock && oppSkippedLandDrop;
|
||||
|
||||
// Best target is a basic land, and there's only one of it, so destroying it may potentially color-lock the opponent
|
||||
// (triggers either if the opponent skipped a land drop or if there are quite a few lands already in play but only one of the given type)
|
||||
CardCollection oppLands = tgtPlayer.getLandsInPlay();
|
||||
boolean canColorLock = (oppSkippedLandDrop || oppLands.size() > 3)
|
||||
&& tgtLand.isBasicLand() && CardLists.filter(oppLands, CardPredicates.nameEquals(tgtLand.getName())).size() == 1;
|
||||
|
||||
// Non-basic lands are currently not ranked in any way in ComputerUtilCard#getBestLandAI, so if a non-basic land is best target,
|
||||
// consider killing it off unless there's too much potential tempo loss.
|
||||
// TODO: actually rank non-basics in that method and then kill off the potentially dangerous (manlands, Valakut) or lucrative
|
||||
// (dual/triple mana that opens access to a certain color) lands
|
||||
boolean nonBasicTgt = !tgtLand.isBasicLand();
|
||||
|
||||
// Try not to lose tempo too much and not to mana-screw yourself when considering this logic
|
||||
int numLandsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
||||
int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
||||
|
||||
// If the opponent skipped a land drop, consider not looking at having the extra land in hand if the profile allows it
|
||||
boolean isHighPriority = highPriorityIfNoLandDrop && oppSkippedLandDrop;
|
||||
|
||||
boolean timingCheck = canManaLock || canColorLock || nonBasicTgt;
|
||||
boolean tempoCheck = numLandsOTB >= amountNoTempoCheck
|
||||
|| ((numLandsInHand >= amountLandsInHand || isHighPriority) && ((numLandsInHand + numLandsOTB >= amountNoTimingCheck) || timingCheck));
|
||||
|
||||
// For Ghost Quarter, only use it if you have either more lands in play than your opponent
|
||||
// or the same number of lands but an extra land in hand (otherwise the AI plays too suboptimally)
|
||||
if ("GhostQuarter".equals(logic)) {
|
||||
return tempoCheck && (numLandsOTB > oppLands.size() || (numLandsOTB == oppLands.size() && numLandsInHand > 0));
|
||||
} else {
|
||||
return tempoCheck;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.*;
|
||||
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.CostSacrifice;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class DestroyAi extends SpellAbilityAi {
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||
// AI needs to be expanded, since this function can be pretty complex
|
||||
// based on what the expected targets could be
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean noRegen = sa.hasParam("NoRegen");
|
||||
final String logic = sa.getParam("AILogic");
|
||||
boolean hasXCost = false;
|
||||
|
||||
CardCollection list;
|
||||
|
||||
if (abCost != null) {
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hasXCost = abCost.getCostMana() != null ? abCost.getCostMana().getAmountOfX() > 0 : false;
|
||||
}
|
||||
|
||||
if ("AtOpponentsCombatOrAfter".equals(sa.getParam("AILogic"))) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
if (ph.getPlayerTurn() == ai || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("AtEOT".equals(sa.getParam("AILogic"))) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
if (!ph.is(PhaseType.END_OF_TURN)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
if (!ph.is(PhaseType.END_OF_TURN) || ai.getAttackedWithCreatureThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Targeting
|
||||
if (abTgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.hasParam("TargetingPlayer")) {
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
sa.setTargetingPlayer(targetingPlayer);
|
||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||
}
|
||||
if ("MadSarkhanDragon".equals(logic)) {
|
||||
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
|
||||
} else if (logic != null && logic.startsWith("MinLoyalty.")) {
|
||||
int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
||||
if (source.getCounters(CounterType.LOYALTY) < minLoyalty) {
|
||||
return false;
|
||||
}
|
||||
} else if ("Polymorph".equals(logic)) {
|
||||
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (Card c : list) {
|
||||
if (c.hasKeyword("Indestructible")) {
|
||||
sa.getTargets().add(c);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Card worst = ComputerUtilCard.getWorstAI(list);
|
||||
if (worst.isCreature() && ComputerUtilCard.evaluateCreature(worst) >= 200) {
|
||||
return false;
|
||||
}
|
||||
if (!worst.isCreature() && worst.getCMC() > 1) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(worst);
|
||||
return true;
|
||||
}
|
||||
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
||||
if ("FatalPush".equals(logic)) {
|
||||
final int cmcMax = ai.hasRevolt() ? 4 : 2;
|
||||
list = CardLists.filter(list, CardPredicates.lessCMC(cmcMax));
|
||||
}
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, true);
|
||||
|
||||
list = CardLists.getNotKeyword(list, "Indestructible");
|
||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||
list = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, list, false);
|
||||
}
|
||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
//Check for cards that can be sacrificed in response
|
||||
for (final SpellAbility ability : c.getAllSpellAbilities()) {
|
||||
if (ability.isAbility()) {
|
||||
final Cost cost = ability.getPayCosts();
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (!(part instanceof CostSacrifice)) {
|
||||
continue;
|
||||
}
|
||||
CostSacrifice sacCost = (CostSacrifice) part;
|
||||
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (c.hasSVar("SacMe")) {
|
||||
return false;
|
||||
}
|
||||
//Check for undying
|
||||
return (!c.hasKeyword("Undying") || c.getCounters(CounterType.P1P1) > 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If NoRegen is not set, filter out creatures that have a
|
||||
// regeneration shield
|
||||
if (!noRegen) {
|
||||
// TODO filter out things that might be tougher?
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return (c.getShieldCount() == 0 && !ComputerUtil.canRegenerate(ai, c));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int maxTargets = abTgt.getMaxTargets(sa.getHostCard(), sa);
|
||||
|
||||
if (hasXCost) {
|
||||
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
||||
maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa));
|
||||
}
|
||||
if (sa.hasParam("AIMaxTgtsCount")) {
|
||||
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified
|
||||
// TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable?
|
||||
maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets);
|
||||
}
|
||||
|
||||
if (maxTargets == 0) {
|
||||
// can't afford X or otherwise target anything
|
||||
return false;
|
||||
}
|
||||
|
||||
// target loop
|
||||
while (sa.getTargets().getNumTargeted() < maxTargets) {
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Card choice = null;
|
||||
// If the targets are only of one type, take the best
|
||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||
if ("OppDestroyYours".equals(logic)) {
|
||||
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
||||
if (ComputerUtilCard.evaluateCreature(aiBest) > ComputerUtilCard.evaluateCreature(choice) - 40) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ("Pongify".equals(logic)) {
|
||||
final Card token = TokenAi.spawnToken(choice.getController(), sa.getSubAbility());
|
||||
if (token == null) {
|
||||
return true; // becomes Terminate
|
||||
} else {
|
||||
if (source.getGame().getPhaseHandler().getPhase()
|
||||
.isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) || // prevent surprise combatant
|
||||
ComputerUtilCard.evaluateCreature(choice) < 1.5
|
||||
* ComputerUtilCard.evaluateCreature(token)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestLandAI(list);
|
||||
|
||||
if ("LandForLand".equals(logic) || "GhostQuarter".equals(logic)) {
|
||||
// Strip Mine, Wasteland - cut short if the relevant logic fails
|
||||
if (!doLandForLandRemovalLogic(sa, ai, choice, logic)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||
}
|
||||
//option to hold removal instead only applies for single targeted removal
|
||||
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
|
||||
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Don't destroy stolen permanents when the stealing aura can be destroyed
|
||||
if (choice.getOwner() == ai) {
|
||||
for (Card aura : choice.getEnchantedBy(false)) {
|
||||
SpellAbility sp = aura.getFirstSpellAbility();
|
||||
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
|
||||
&& aura.getController() != ai && sa.canTarget(aura)) {
|
||||
choice = aura;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
} 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() < ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size()
|
||||
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|
||||
|| ai.getLife() <= 5)) {
|
||||
// Basic ai logic for Lethal Vapors
|
||||
return false;
|
||||
}
|
||||
|
||||
if (list.isEmpty()
|
||||
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|
||||
|| CardLists.getNotKeyword(list, "Indestructible").isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean noRegen = sa.hasParam("NoRegen");
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
|
||||
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
|
||||
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CardCollection preferred = CardLists.getNotKeyword(list, "Indestructible");
|
||||
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
|
||||
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
||||
preferred = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, preferred, false);
|
||||
}
|
||||
|
||||
// If NoRegen is not set, filter out creatures that have a
|
||||
// regeneration shield
|
||||
if (!noRegen) {
|
||||
// TODO filter out things that could regenerate in response?
|
||||
// might be tougher?
|
||||
preferred = CardLists.filter(preferred, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.getShieldCount() == 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
preferred = ComputerUtil.filterAITgts(sa, ai, (CardCollection)preferred, true);
|
||||
|
||||
for (final Card c : preferred) {
|
||||
list.remove(c);
|
||||
}
|
||||
|
||||
if (preferred.isEmpty() && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
if (preferred.isEmpty()) {
|
||||
if (sa.getTargets().getNumTargeted() == 0
|
||||
|| sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (!mandatory) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
Card c;
|
||||
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
||||
c = ComputerUtilCard.getBestCreatureAI(preferred);
|
||||
} else if (CardLists.getNotType(preferred, "Land").isEmpty()) {
|
||||
c = ComputerUtilCard.getBestLandAI(preferred);
|
||||
} else {
|
||||
c = ComputerUtilCard.getMostExpensivePermanentAI(preferred, sa, false);
|
||||
}
|
||||
sa.getTargets().add(c);
|
||||
preferred.remove(c);
|
||||
}
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (list.isEmpty()) {
|
||||
break;
|
||||
} else {
|
||||
Card c;
|
||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||
if (!sa.getUniqueTargets().isEmpty() && sa.getParent().getApi() == ApiType.Destroy
|
||||
&& sa.getUniqueTargets().get(0) instanceof Card) {
|
||||
// basic ai for Diaochan
|
||||
c = (Card) sa.getUniqueTargets().get(0);
|
||||
} else {
|
||||
c = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
}
|
||||
} else {
|
||||
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
|
||||
}
|
||||
sa.getTargets().add(c);
|
||||
list.remove(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!mandatory) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLand, String logic) {
|
||||
if (tgtLand == null) { return false; }
|
||||
|
||||
Player tgtPlayer = tgtLand.getController();
|
||||
int oppLandsOTB = tgtPlayer.getLandsInPlay().size();
|
||||
|
||||
// AI profile-dependent properties
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
int amountNoTempoCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK);
|
||||
int amountNoTimingCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK);
|
||||
int amountLandsInHand = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE);
|
||||
int amountLandsToManalock = aic.getIntProperty(AiProps.STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING);
|
||||
boolean highPriorityIfNoLandDrop = aic.getBooleanProperty(AiProps.STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP);
|
||||
|
||||
// if the opponent didn't play a land and has few lands OTB, might be worth mana-locking him
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
boolean oppSkippedLandDrop = (tgtPlayer.getLandsPlayedLastTurn() == 0 && ph.isPlayerTurn(ai))
|
||||
|| (tgtPlayer.getLandsPlayedThisTurn() == 0 && ph.isPlayerTurn(tgtPlayer) && ph.getPhase().isAfter(PhaseType.MAIN2));
|
||||
boolean canManaLock = oppLandsOTB <= amountLandsToManalock && oppSkippedLandDrop;
|
||||
|
||||
// Best target is a basic land, and there's only one of it, so destroying it may potentially color-lock the opponent
|
||||
// (triggers either if the opponent skipped a land drop or if there are quite a few lands already in play but only one of the given type)
|
||||
CardCollection oppLands = tgtPlayer.getLandsInPlay();
|
||||
boolean canColorLock = (oppSkippedLandDrop || oppLands.size() > 3)
|
||||
&& tgtLand.isBasicLand() && CardLists.filter(oppLands, CardPredicates.nameEquals(tgtLand.getName())).size() == 1;
|
||||
|
||||
// Non-basic lands are currently not ranked in any way in ComputerUtilCard#getBestLandAI, so if a non-basic land is best target,
|
||||
// consider killing it off unless there's too much potential tempo loss.
|
||||
// TODO: actually rank non-basics in that method and then kill off the potentially dangerous (manlands, Valakut) or lucrative
|
||||
// (dual/triple mana that opens access to a certain color) lands
|
||||
boolean nonBasicTgt = !tgtLand.isBasicLand();
|
||||
|
||||
// Try not to lose tempo too much and not to mana-screw yourself when considering this logic
|
||||
int numLandsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
||||
int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
||||
|
||||
// If the opponent skipped a land drop, consider not looking at having the extra land in hand if the profile allows it
|
||||
boolean isHighPriority = highPriorityIfNoLandDrop && oppSkippedLandDrop;
|
||||
|
||||
boolean timingCheck = canManaLock || canColorLock || nonBasicTgt;
|
||||
boolean tempoCheck = numLandsOTB >= amountNoTempoCheck
|
||||
|| ((numLandsInHand >= amountLandsInHand || isHighPriority) && ((numLandsInHand + numLandsOTB >= amountNoTimingCheck) || timingCheck));
|
||||
|
||||
// For Ghost Quarter, only use it if you have either more lands in play than your opponent
|
||||
// or the same number of lands but an extra land in hand (otherwise the AI plays too suboptimally)
|
||||
if ("GhostQuarter".equals(logic)) {
|
||||
return tempoCheck && (numLandsOTB > oppLands.size() || (numLandsOTB == oppLands.size() && numLandsInHand > 0));
|
||||
} else {
|
||||
return tempoCheck;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,158 +1,158 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.*;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.game.combat.Combat;
|
||||
|
||||
public class DestroyAllAi extends SpellAbilityAi {
|
||||
|
||||
private static final Predicate<Card> predicate = new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !(c.hasKeyword("Indestructible") || c.getSVar("SacMe").length() > 0);
|
||||
}
|
||||
};
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return doMassRemovalLogic(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
//TODO: Check for bad outcome
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||
// AI needs to be expanded, since this function can be pretty complex
|
||||
// based on what the expected targets could be
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for some costs
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return doMassRemovalLogic(ai, sa);
|
||||
}
|
||||
|
||||
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
Player opponent = ComputerUtil.getOpponentFor(ai); // TODO: how should this AI logic work for multiplayer and getOpponents()?
|
||||
|
||||
final int CREATURE_EVAL_THRESHOLD = 200;
|
||||
|
||||
String valid = "";
|
||||
if (sa.hasParam("ValidCards")) {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
|
||||
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
valid = valid.replace("X", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
|
||||
valid.split(","), source.getController(), source, sa);
|
||||
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
||||
source.getController(), source, sa);
|
||||
|
||||
opplist = CardLists.filter(opplist, predicate);
|
||||
ailist = CardLists.filter(ailist, predicate);
|
||||
if (opplist.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(opponent)) {
|
||||
sa.getTargets().add(opponent);
|
||||
ailist.clear();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if only creatures are affected evaluate both lists and pass only if
|
||||
// human creatures are more valuable
|
||||
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// test whether the human can kill the ai next turn
|
||||
Combat combat = new Combat(opponent);
|
||||
boolean containsAttacker = false;
|
||||
for (Card att : opponent.getCreaturesInPlay()) {
|
||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
||||
combat.addAttacker(att, ai);
|
||||
containsAttacker = containsAttacker | opplist.contains(att);
|
||||
}
|
||||
}
|
||||
if (!containsAttacker) {
|
||||
return false;
|
||||
}
|
||||
AiBlockController block = new AiBlockController(ai);
|
||||
block.assignBlockersForCombat(combat);
|
||||
|
||||
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} // only lands involved
|
||||
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
||||
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||
CardCollection oppCreatures = opponent.getCreaturesInPlay();
|
||||
if (!oppCreatures.isEmpty()) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// check if the AI would lose more lands than the opponent would
|
||||
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
|
||||
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.*;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.game.combat.Combat;
|
||||
|
||||
public class DestroyAllAi extends SpellAbilityAi {
|
||||
|
||||
private static final Predicate<Card> predicate = new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !(c.hasKeyword("Indestructible") || c.getSVar("SacMe").length() > 0);
|
||||
}
|
||||
};
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return doMassRemovalLogic(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
//TODO: Check for bad outcome
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||
// AI needs to be expanded, since this function can be pretty complex
|
||||
// based on what the expected targets could be
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for some costs
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return doMassRemovalLogic(ai, sa);
|
||||
}
|
||||
|
||||
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
Player opponent = ComputerUtil.getOpponentFor(ai); // TODO: how should this AI logic work for multiplayer and getOpponents()?
|
||||
|
||||
final int CREATURE_EVAL_THRESHOLD = 200;
|
||||
|
||||
String valid = "";
|
||||
if (sa.hasParam("ValidCards")) {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
|
||||
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
valid = valid.replace("X", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
|
||||
valid.split(","), source.getController(), source, sa);
|
||||
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
||||
source.getController(), source, sa);
|
||||
|
||||
opplist = CardLists.filter(opplist, predicate);
|
||||
ailist = CardLists.filter(ailist, predicate);
|
||||
if (opplist.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(opponent)) {
|
||||
sa.getTargets().add(opponent);
|
||||
ailist.clear();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if only creatures are affected evaluate both lists and pass only if
|
||||
// human creatures are more valuable
|
||||
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// test whether the human can kill the ai next turn
|
||||
Combat combat = new Combat(opponent);
|
||||
boolean containsAttacker = false;
|
||||
for (Card att : opponent.getCreaturesInPlay()) {
|
||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
||||
combat.addAttacker(att, ai);
|
||||
containsAttacker = containsAttacker | opplist.contains(att);
|
||||
}
|
||||
}
|
||||
if (!containsAttacker) {
|
||||
return false;
|
||||
}
|
||||
AiBlockController block = new AiBlockController(ai);
|
||||
block.assignBlockersForCombat(combat);
|
||||
|
||||
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} // only lands involved
|
||||
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
||||
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||
CardCollection oppCreatures = opponent.getCreaturesInPlay();
|
||||
if (!oppCreatures.isEmpty()) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// check if the AI would lose more lands than the opponent would
|
||||
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
|
||||
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,155 +1,155 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.*;
|
||||
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.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
|
||||
public class DigAi 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 = ComputerUtil.getOpponentFor(ai);
|
||||
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"))) {
|
||||
return game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
final String num = sa.getParam("DigNum");
|
||||
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;
|
||||
|
||||
// Special logic that asks the AI to conserve a certain amount of mana when paying X
|
||||
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
|
||||
manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
|
||||
}
|
||||
|
||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
|
||||
if (numCards <= 0) {
|
||||
return false;
|
||||
}
|
||||
host.setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if ("MadSarkhanDigDmg".equals(sa.getParam("AILogic"))) {
|
||||
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
|
||||
}
|
||||
|
||||
return !ComputerUtil.preventRunAwayActivations(sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else if (mandatory && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
}
|
||||
|
||||
// Triggers that ask to pay {X} (e.g. Depala, Pilot Exemplar).
|
||||
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
|
||||
int manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
|
||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
|
||||
if (numCards <= 0) {
|
||||
return mandatory;
|
||||
}
|
||||
sa.getHostCard().setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) {
|
||||
Card chosen = ComputerUtilCard.getBestAI(valid);
|
||||
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
|
||||
return ComputerUtilCard.getWorstAI(valid);
|
||||
}
|
||||
return chosen;
|
||||
}
|
||||
|
||||
/* (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) {
|
||||
Card topc = player.getZone(ZoneType.Library).get(0);
|
||||
|
||||
// AI actions for individual cards (until this AI can be generalized)
|
||||
if (sa.getHostCard() != null) {
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Explorer's Scope")) {
|
||||
// for Explorer's Scope, always put a land on the battlefield tapped
|
||||
// (TODO: might not always be a good idea, e.g. when a land ETBing can have detrimental effects)
|
||||
return true;
|
||||
} else if ("AlwaysConfirm".equals(sa.getParam("AILogic"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// looks like perfect code for Delver of Secrets, but what about other cards?
|
||||
return topc.isInstant() || topc.isSorcery();
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.*;
|
||||
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.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
|
||||
public class DigAi 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 = ComputerUtil.getOpponentFor(ai);
|
||||
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"))) {
|
||||
return game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
final String num = sa.getParam("DigNum");
|
||||
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;
|
||||
|
||||
// Special logic that asks the AI to conserve a certain amount of mana when paying X
|
||||
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
|
||||
manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
|
||||
}
|
||||
|
||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
|
||||
if (numCards <= 0) {
|
||||
return false;
|
||||
}
|
||||
host.setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if ("MadSarkhanDigDmg".equals(sa.getParam("AILogic"))) {
|
||||
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
|
||||
}
|
||||
|
||||
return !ComputerUtil.preventRunAwayActivations(sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else if (mandatory && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
}
|
||||
|
||||
// Triggers that ask to pay {X} (e.g. Depala, Pilot Exemplar).
|
||||
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
|
||||
int manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
|
||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
|
||||
if (numCards <= 0) {
|
||||
return mandatory;
|
||||
}
|
||||
sa.getHostCard().setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) {
|
||||
Card chosen = ComputerUtilCard.getBestAI(valid);
|
||||
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
|
||||
return ComputerUtilCard.getWorstAI(valid);
|
||||
}
|
||||
return chosen;
|
||||
}
|
||||
|
||||
/* (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) {
|
||||
Card topc = player.getZone(ZoneType.Library).get(0);
|
||||
|
||||
// AI actions for individual cards (until this AI can be generalized)
|
||||
if (sa.getHostCard() != null) {
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Explorer's Scope")) {
|
||||
// for Explorer's Scope, always put a land on the battlefield tapped
|
||||
// (TODO: might not always be a good idea, e.g. when a land ETBing can have detrimental effects)
|
||||
return true;
|
||||
} else if ("AlwaysConfirm".equals(sa.getParam("AILogic"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// looks like perfect code for Delver of Secrets, but what about other cards?
|
||||
return topc.isInstant() || topc.isSorcery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,134 +1,134 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class DigUntilAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
double chance = .4; // 40 percent chance with instant speed stuff
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
chance = .667; // 66.7% chance for sorcery speed (since it will
|
||||
// never activate EOT)
|
||||
}
|
||||
final Random r = MyRandom.getRandom();
|
||||
final boolean randomReturn = r.nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
Player libraryOwner = ai;
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
if ("DontMillSelf".equals(logic)) {
|
||||
// A card that digs for specific things and puts everything revealed before it into graveyard
|
||||
// (e.g. Hermit Druid) - don't use it to mill itself and also make sure there's enough playable
|
||||
// material in the library after using it several times.
|
||||
// TODO: maybe this should happen for any DigUntil SA with RevealedDestination$ Graveyard?
|
||||
if (ai.getCardsIn(ZoneType.Library).size() < 20) {
|
||||
return false;
|
||||
}
|
||||
if ("Land.Basic".equals(sa.getParam("Valid"))
|
||||
&& !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).isEmpty()) {
|
||||
// We already have a mana-producing land in hand, so bail
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!sa.canTarget(opp)) {
|
||||
return false;
|
||||
} else {
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
libraryOwner = opp;
|
||||
} else {
|
||||
if (sa.hasParam("Valid")) {
|
||||
final String valid = sa.getParam("Valid");
|
||||
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source, sa).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final String num = sa.getParam("Amount");
|
||||
if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) {
|
||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
if (numCards <= 0) {
|
||||
return false;
|
||||
}
|
||||
source.setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
}
|
||||
|
||||
// return false if nothing to dig into
|
||||
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (sa.isCurse()) {
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mandatory && sa.getTargets().isEmpty() && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
} else {
|
||||
if (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) {
|
||||
if (sa.hasParam("AILogic")) {
|
||||
final String logic = sa.getParam("AILogic");
|
||||
if ("OathOfDruids".equals(logic)) {
|
||||
final List<Card> creaturesInLibrary =
|
||||
CardLists.filter(player.getCardsIn(ZoneType.Library), CardPredicates.Presets.CREATURES);
|
||||
final List<Card> creaturesInBattlefield =
|
||||
CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||
// if there are at least 3 creatures in library,
|
||||
// or none in play with one in library, oath
|
||||
return creaturesInLibrary.size() > 2
|
||||
|| (creaturesInBattlefield.size() == 0 && creaturesInLibrary.size() > 0);
|
||||
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class DigUntilAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
double chance = .4; // 40 percent chance with instant speed stuff
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
chance = .667; // 66.7% chance for sorcery speed (since it will
|
||||
// never activate EOT)
|
||||
}
|
||||
final Random r = MyRandom.getRandom();
|
||||
final boolean randomReturn = r.nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
Player libraryOwner = ai;
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
if ("DontMillSelf".equals(logic)) {
|
||||
// A card that digs for specific things and puts everything revealed before it into graveyard
|
||||
// (e.g. Hermit Druid) - don't use it to mill itself and also make sure there's enough playable
|
||||
// material in the library after using it several times.
|
||||
// TODO: maybe this should happen for any DigUntil SA with RevealedDestination$ Graveyard?
|
||||
if (ai.getCardsIn(ZoneType.Library).size() < 20) {
|
||||
return false;
|
||||
}
|
||||
if ("Land.Basic".equals(sa.getParam("Valid"))
|
||||
&& !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).isEmpty()) {
|
||||
// We already have a mana-producing land in hand, so bail
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!sa.canTarget(opp)) {
|
||||
return false;
|
||||
} else {
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
libraryOwner = opp;
|
||||
} else {
|
||||
if (sa.hasParam("Valid")) {
|
||||
final String valid = sa.getParam("Valid");
|
||||
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source, sa).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final String num = sa.getParam("Amount");
|
||||
if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) {
|
||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
if (numCards <= 0) {
|
||||
return false;
|
||||
}
|
||||
source.setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
}
|
||||
|
||||
// return false if nothing to dig into
|
||||
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (sa.isCurse()) {
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mandatory && sa.getTargets().isEmpty() && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
} else {
|
||||
if (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) {
|
||||
if (sa.hasParam("AILogic")) {
|
||||
final String logic = sa.getParam("AILogic");
|
||||
if ("OathOfDruids".equals(logic)) {
|
||||
final List<Card> creaturesInLibrary =
|
||||
CardLists.filter(player.getCardsIn(ZoneType.Library), CardPredicates.Presets.CREATURES);
|
||||
final List<Card> creaturesInBattlefield =
|
||||
CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||
// if there are at least 3 creatures in library,
|
||||
// or none in play with one in library, oath
|
||||
return creaturesInLibrary.size() > 2
|
||||
|| (creaturesInBattlefield.size() == 0 && creaturesInLibrary.size() > 0);
|
||||
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,224 +1,224 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.cost.Cost;
|
||||
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 forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class DiscardAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ("Chandra, Flamecaller".equals(sourceName)) {
|
||||
final int hand = ai.getCardsIn(ZoneType.Hand).size();
|
||||
return MyRandom.getRandom().nextFloat() < (1.0 / (1 + hand));
|
||||
}
|
||||
|
||||
if (aiLogic.equals("VolrathsShapeshifter")) {
|
||||
return SpecialCardAi.VolrathsShapeshifter.consider(ai, sa);
|
||||
}
|
||||
|
||||
final boolean humanHasHand = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).size() > 0;
|
||||
|
||||
if (tgt != null) {
|
||||
if (!discardTargetAI(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// TODO: Add appropriate restrictions
|
||||
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
if (players.size() == 1) {
|
||||
if (players.get(0) == ai) {
|
||||
// the ai should only be using something like this if he has
|
||||
// few cards in hand,
|
||||
// cards like this better have a good drawback to be in the
|
||||
// AIs deck
|
||||
} else {
|
||||
// defined to the human, so that's fine as long the human
|
||||
// has cards
|
||||
if (!humanHasHand) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Both players discard, any restrictions?
|
||||
}
|
||||
}
|
||||
|
||||
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), ComputerUtil.getOpponentFor(ai)
|
||||
.getCardsIn(ZoneType.Hand).size());
|
||||
if (cardsToDiscard < 1) {
|
||||
return false;
|
||||
}
|
||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
} else {
|
||||
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Improve support for Discard AI for cards with AnyNumber set to true.
|
||||
if (sa.hasParam("AnyNumber")) {
|
||||
if ("DiscardUncastableAndExcess".equals(aiLogic)) {
|
||||
final CardCollectionView inHand = ai.getCardsIn(ZoneType.Hand);
|
||||
final int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
|
||||
int numDiscard = 0;
|
||||
int numOppInHand = 0;
|
||||
for (Player p : ai.getGame().getPlayers()) {
|
||||
if (p.getCardsIn(ZoneType.Hand).size() > numOppInHand) {
|
||||
numOppInHand = p.getCardsIn(ZoneType.Hand).size();
|
||||
}
|
||||
}
|
||||
for (Card c : inHand) {
|
||||
if (c.equals(sa.getHostCard())) { continue; }
|
||||
if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) { continue; }
|
||||
if (c.isCreature() && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getSpellPermanent(), ai)) {
|
||||
numDiscard++;
|
||||
}
|
||||
if ((c.isLand() && numLandsOTB >= 5) || (c.getFirstSpellAbility() != null && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getFirstSpellAbility(), ai))) {
|
||||
if (numDiscard + 1 <= numOppInHand) {
|
||||
numDiscard++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (numDiscard == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't use draw abilities before main 2 if possible
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= Math.pow(0.9, sa.getActivationsThisTurn());
|
||||
|
||||
// some other variables here, like handsize vs. maxHandSize
|
||||
|
||||
return randomReturn;
|
||||
} // discardCanPlayAI()
|
||||
|
||||
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
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;
|
||||
} // discardTargetAI()
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (!discardTargetAI(ai, sa)) {
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else if (mandatory && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (sa.hasParam("AILogic")) {
|
||||
if ("AtLeast2".equals(sa.getParam("AILogic"))) {
|
||||
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
if (players.isEmpty() || players.get(0).getCardsIn(ZoneType.Hand).size() < 2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
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), ComputerUtil.getOpponentFor(ai)
|
||||
.getCardsIn(ZoneType.Hand).size());
|
||||
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} // discardTrigger()
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
// Drawback AI improvements
|
||||
// if parent draws cards, make sure cards in hand + cards drawn > 0
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
return discardTargetAI(ai, sa);
|
||||
}
|
||||
// TODO: check for some extra things
|
||||
return true;
|
||||
} // discardCheckDrawbackAI()
|
||||
|
||||
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
if ( mode == PlayerActionConfirmMode.Random ) { //
|
||||
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
|
||||
return true;
|
||||
}
|
||||
return super.confirmAction(player, sa, mode, message);
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.cost.Cost;
|
||||
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 forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class DiscardAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ("Chandra, Flamecaller".equals(sourceName)) {
|
||||
final int hand = ai.getCardsIn(ZoneType.Hand).size();
|
||||
return MyRandom.getRandom().nextFloat() < (1.0 / (1 + hand));
|
||||
}
|
||||
|
||||
if (aiLogic.equals("VolrathsShapeshifter")) {
|
||||
return SpecialCardAi.VolrathsShapeshifter.consider(ai, sa);
|
||||
}
|
||||
|
||||
final boolean humanHasHand = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).size() > 0;
|
||||
|
||||
if (tgt != null) {
|
||||
if (!discardTargetAI(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// TODO: Add appropriate restrictions
|
||||
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
if (players.size() == 1) {
|
||||
if (players.get(0) == ai) {
|
||||
// the ai should only be using something like this if he has
|
||||
// few cards in hand,
|
||||
// cards like this better have a good drawback to be in the
|
||||
// AIs deck
|
||||
} else {
|
||||
// defined to the human, so that's fine as long the human
|
||||
// has cards
|
||||
if (!humanHasHand) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Both players discard, any restrictions?
|
||||
}
|
||||
}
|
||||
|
||||
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), ComputerUtil.getOpponentFor(ai)
|
||||
.getCardsIn(ZoneType.Hand).size());
|
||||
if (cardsToDiscard < 1) {
|
||||
return false;
|
||||
}
|
||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
} else {
|
||||
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Improve support for Discard AI for cards with AnyNumber set to true.
|
||||
if (sa.hasParam("AnyNumber")) {
|
||||
if ("DiscardUncastableAndExcess".equals(aiLogic)) {
|
||||
final CardCollectionView inHand = ai.getCardsIn(ZoneType.Hand);
|
||||
final int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
|
||||
int numDiscard = 0;
|
||||
int numOppInHand = 0;
|
||||
for (Player p : ai.getGame().getPlayers()) {
|
||||
if (p.getCardsIn(ZoneType.Hand).size() > numOppInHand) {
|
||||
numOppInHand = p.getCardsIn(ZoneType.Hand).size();
|
||||
}
|
||||
}
|
||||
for (Card c : inHand) {
|
||||
if (c.equals(sa.getHostCard())) { continue; }
|
||||
if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) { continue; }
|
||||
if (c.isCreature() && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getSpellPermanent(), ai)) {
|
||||
numDiscard++;
|
||||
}
|
||||
if ((c.isLand() && numLandsOTB >= 5) || (c.getFirstSpellAbility() != null && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getFirstSpellAbility(), ai))) {
|
||||
if (numDiscard + 1 <= numOppInHand) {
|
||||
numDiscard++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (numDiscard == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't use draw abilities before main 2 if possible
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= Math.pow(0.9, sa.getActivationsThisTurn());
|
||||
|
||||
// some other variables here, like handsize vs. maxHandSize
|
||||
|
||||
return randomReturn;
|
||||
} // discardCanPlayAI()
|
||||
|
||||
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
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;
|
||||
} // discardTargetAI()
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (!discardTargetAI(ai, sa)) {
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else if (mandatory && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (sa.hasParam("AILogic")) {
|
||||
if ("AtLeast2".equals(sa.getParam("AILogic"))) {
|
||||
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
if (players.isEmpty() || players.get(0).getCardsIn(ZoneType.Hand).size() < 2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
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), ComputerUtil.getOpponentFor(ai)
|
||||
.getCardsIn(ZoneType.Hand).size());
|
||||
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} // discardTrigger()
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
// Drawback AI improvements
|
||||
// if parent draws cards, make sure cards in hand + cards drawn > 0
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
return discardTargetAI(ai, sa);
|
||||
}
|
||||
// TODO: check for some extra things
|
||||
return true;
|
||||
} // discardCheckDrawbackAI()
|
||||
|
||||
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
if ( mode == PlayerActionConfirmMode.Random ) { //
|
||||
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
|
||||
return true;
|
||||
}
|
||||
return super.confirmAction(player, sa, mode, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +1,92 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class DrainManaAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// AI cannot use this properly until he can use SAs during Humans turn
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
if (tgt == null) {
|
||||
// assume we are looking to tap human's stuff
|
||||
// TODO - check for things with untap abilities, and don't tap
|
||||
// those.
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!defined.contains(opp)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (null == tgt) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
} else {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!defined.contains(opp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
// AI cannot use this properly until he can use SAs during Humans turn
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
boolean randomReturn = true;
|
||||
|
||||
if (tgt == null) {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (defined.contains(ai)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class DrainManaAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// AI cannot use this properly until he can use SAs during Humans turn
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
if (tgt == null) {
|
||||
// assume we are looking to tap human's stuff
|
||||
// TODO - check for things with untap abilities, and don't tap
|
||||
// those.
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!defined.contains(opp)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (null == tgt) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
} else {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!defined.contains(opp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
// AI cannot use this properly until he can use SAs during Humans turn
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
boolean randomReturn = true;
|
||||
|
||||
if (tgt == null) {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (defined.contains(ai)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,334 +1,334 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class EffectAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai,final SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= .6667;
|
||||
String logic = "";
|
||||
|
||||
if (sa.hasParam("AILogic")) {
|
||||
logic = sa.getParam("AILogic");
|
||||
final PhaseHandler phase = game.getPhaseHandler();
|
||||
if (logic.equals("BeginningOfOppTurn")) {
|
||||
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isAfter(PhaseType.DRAW)) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("EndOfOppTurn")) {
|
||||
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("KeepOppCreatsLandsTapped")) {
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
boolean worthHolding = false;
|
||||
CardCollectionView oppCreatsLands = CardLists.filter(opp.getCardsIn(ZoneType.Battlefield),
|
||||
Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.Presets.CREATURES));
|
||||
CardCollectionView oppCreatsLandsTapped = CardLists.filter(oppCreatsLands, CardPredicates.Presets.TAPPED);
|
||||
|
||||
if (oppCreatsLandsTapped.size() >= 3 || oppCreatsLands.size() == oppCreatsLandsTapped.size()) {
|
||||
worthHolding = true;
|
||||
}
|
||||
if (!worthHolding) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
}
|
||||
} else if (logic.equals("Fog")) {
|
||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
||||
return false;
|
||||
}
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
boolean canTgt = false;
|
||||
|
||||
for (Player opp2 : ai.getOpponents()) {
|
||||
if (sa.canTarget(opp2)) {
|
||||
sa.getTargets().add(opp2);
|
||||
canTgt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!canTgt) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
List<Card> list = game.getCombat().getAttackers();
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
Card target = ComputerUtilCard.getBestCreatureAI(list);
|
||||
if (target == null) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(target);
|
||||
}
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("ChainVeil")) {
|
||||
if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2)
|
||||
|| CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Planeswalker").isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("SpellCopy")) {
|
||||
// fetch Instant or Sorcery and AI has reason to play this turn
|
||||
// does not try to get itself
|
||||
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return (c.isInstant() || c.isSorcery()) && c != sa.getHostCard() && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
|
||||
}
|
||||
});
|
||||
|
||||
if(count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("NarsetRebound")) {
|
||||
// should be done in Main2, but it might broke for other cards
|
||||
//if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
// return false;
|
||||
//}
|
||||
|
||||
// fetch Instant or Sorcery without Rebound and AI has reason to play this turn
|
||||
// only need count, not the list
|
||||
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return (c.isInstant() || c.isSorcery()) && !c.hasKeyword("Rebound") && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
|
||||
}
|
||||
});
|
||||
|
||||
if(count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("Always")) {
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("Main2")) {
|
||||
if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("Evasion")) {
|
||||
|
||||
if (!phase.isPlayerTurn(ai)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean shouldPlay = false;
|
||||
|
||||
List<Card> comp = ai.getCreaturesInPlay();
|
||||
|
||||
for (final Player opp : ai.getOpponents()) {
|
||||
List<Card> human = opp.getCreaturesInPlay();
|
||||
|
||||
// only count creatures that can attack or block
|
||||
comp = CardLists.filter(comp, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return CombatUtil.canAttack(c, opp);
|
||||
}
|
||||
});
|
||||
if (comp.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
final List<Card> attackers = comp;
|
||||
human = CardLists.filter(human, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return CombatUtil.canBlockAtLeastOne(c, attackers);
|
||||
}
|
||||
});
|
||||
if (human.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
shouldPlay = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return shouldPlay;
|
||||
} else if (logic.equals("RedirectSpellDamageFromPlayer")) {
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
boolean threatened = false;
|
||||
for (final SpellAbilityStackInstance stackInst : game.getStack()) {
|
||||
if (!stackInst.isSpell()) { continue; }
|
||||
SpellAbility stackSpellAbility = stackInst.getSpellAbility(true);
|
||||
if (stackSpellAbility.getApi() == ApiType.DealDamage) {
|
||||
final SpellAbility saTargeting = stackSpellAbility.getSATargetingPlayer();
|
||||
if (saTargeting != null && Iterables.contains(saTargeting.getTargets().getTargetPlayers(), ai)) {
|
||||
threatened = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
randomReturn = threatened;
|
||||
} else if (logic.equals("Prevent")) { // prevent burn spell from opponent
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final SpellAbility saTop = game.getStack().peekAbility();
|
||||
final Card host = saTop.getHostCard();
|
||||
if (saTop.getActivatingPlayer() != ai // from opponent
|
||||
&& !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();
|
||||
if (type == ApiType.DealDamage || type == ApiType.DamageAll) { // burn spell
|
||||
sa.getTargets().add(host);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (logic.equals("NoGain")) {
|
||||
// basic logic to cancel GainLife on stack
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final SpellAbility topStack = game.getStack().peekAbility();
|
||||
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")) {
|
||||
// for DamageDeal sub-abilities (eg. Wild Slash, Skullcrack)
|
||||
SpellAbility burn = sa.getSubAbility();
|
||||
return SpellApiToAi.Converter.get(burn.getApi()).canPlayAIWithSubs(ai, burn);
|
||||
} else if (logic.equals("YawgmothsWill")) {
|
||||
return SpecialCardAi.YawgmothsWill.consider(ai, sa);
|
||||
} else if (logic.startsWith("NeedCreatures")) {
|
||||
if (ai.getCreaturesInPlay().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (logic.contains(":")) {
|
||||
String k[] = logic.split(":");
|
||||
Integer i = Integer.valueOf(k[1]);
|
||||
if (ai.getCreaturesInPlay().size() < i) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (logic.equals("CastFromGraveThisTurn")) {
|
||||
CardCollection list = new CardCollection(game.getCardsIn(ZoneType.Graveyard));
|
||||
list = CardLists.getValidCards(list, sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else { //no AILogic
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("False".equals(sa.getParam("Stackable"))) {
|
||||
String name = sa.getParam("Name");
|
||||
if (name == null) {
|
||||
name = sa.getHostCard().getName() + "'s Effect";
|
||||
}
|
||||
if (sa.getActivatingPlayer().isCardInCommand(name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null && tgt.canTgtPlayer()) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent() || logic.equals("BeginningOfOppTurn")) {
|
||||
boolean canTgt = false;
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
canTgt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return canTgt;
|
||||
} else {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
// E.g. Nova Pentacle
|
||||
if (aiLogic.equals("RedirectFromOppToCreature")) {
|
||||
// try to target the opponent's best targetable permanent, if able
|
||||
CardCollection oppPerms = CardLists.getValidCards(aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||
if (!oppPerms.isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ComputerUtilCard.getBestAI(oppPerms));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mandatory) {
|
||||
// try to target the AI's worst targetable permanent, if able
|
||||
CardCollection aiPerms = CardLists.getValidCards(aiPlayer.getCardsIn(ZoneType.Battlefield), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||
if (!aiPerms.isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(aiPerms));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
||||
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class EffectAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai,final SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= .6667;
|
||||
String logic = "";
|
||||
|
||||
if (sa.hasParam("AILogic")) {
|
||||
logic = sa.getParam("AILogic");
|
||||
final PhaseHandler phase = game.getPhaseHandler();
|
||||
if (logic.equals("BeginningOfOppTurn")) {
|
||||
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isAfter(PhaseType.DRAW)) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("EndOfOppTurn")) {
|
||||
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("KeepOppCreatsLandsTapped")) {
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
boolean worthHolding = false;
|
||||
CardCollectionView oppCreatsLands = CardLists.filter(opp.getCardsIn(ZoneType.Battlefield),
|
||||
Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.Presets.CREATURES));
|
||||
CardCollectionView oppCreatsLandsTapped = CardLists.filter(oppCreatsLands, CardPredicates.Presets.TAPPED);
|
||||
|
||||
if (oppCreatsLandsTapped.size() >= 3 || oppCreatsLands.size() == oppCreatsLandsTapped.size()) {
|
||||
worthHolding = true;
|
||||
}
|
||||
if (!worthHolding) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
}
|
||||
} else if (logic.equals("Fog")) {
|
||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
||||
return false;
|
||||
}
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
boolean canTgt = false;
|
||||
|
||||
for (Player opp2 : ai.getOpponents()) {
|
||||
if (sa.canTarget(opp2)) {
|
||||
sa.getTargets().add(opp2);
|
||||
canTgt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!canTgt) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
List<Card> list = game.getCombat().getAttackers();
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
Card target = ComputerUtilCard.getBestCreatureAI(list);
|
||||
if (target == null) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(target);
|
||||
}
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("ChainVeil")) {
|
||||
if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2)
|
||||
|| CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Planeswalker").isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("SpellCopy")) {
|
||||
// fetch Instant or Sorcery and AI has reason to play this turn
|
||||
// does not try to get itself
|
||||
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return (c.isInstant() || c.isSorcery()) && c != sa.getHostCard() && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
|
||||
}
|
||||
});
|
||||
|
||||
if(count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("NarsetRebound")) {
|
||||
// should be done in Main2, but it might broke for other cards
|
||||
//if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
// return false;
|
||||
//}
|
||||
|
||||
// fetch Instant or Sorcery without Rebound and AI has reason to play this turn
|
||||
// only need count, not the list
|
||||
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return (c.isInstant() || c.isSorcery()) && !c.hasKeyword("Rebound") && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
|
||||
}
|
||||
});
|
||||
|
||||
if(count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("Always")) {
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("Main2")) {
|
||||
if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("Evasion")) {
|
||||
|
||||
if (!phase.isPlayerTurn(ai)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean shouldPlay = false;
|
||||
|
||||
List<Card> comp = ai.getCreaturesInPlay();
|
||||
|
||||
for (final Player opp : ai.getOpponents()) {
|
||||
List<Card> human = opp.getCreaturesInPlay();
|
||||
|
||||
// only count creatures that can attack or block
|
||||
comp = CardLists.filter(comp, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return CombatUtil.canAttack(c, opp);
|
||||
}
|
||||
});
|
||||
if (comp.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
final List<Card> attackers = comp;
|
||||
human = CardLists.filter(human, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return CombatUtil.canBlockAtLeastOne(c, attackers);
|
||||
}
|
||||
});
|
||||
if (human.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
shouldPlay = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return shouldPlay;
|
||||
} else if (logic.equals("RedirectSpellDamageFromPlayer")) {
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
boolean threatened = false;
|
||||
for (final SpellAbilityStackInstance stackInst : game.getStack()) {
|
||||
if (!stackInst.isSpell()) { continue; }
|
||||
SpellAbility stackSpellAbility = stackInst.getSpellAbility(true);
|
||||
if (stackSpellAbility.getApi() == ApiType.DealDamage) {
|
||||
final SpellAbility saTargeting = stackSpellAbility.getSATargetingPlayer();
|
||||
if (saTargeting != null && Iterables.contains(saTargeting.getTargets().getTargetPlayers(), ai)) {
|
||||
threatened = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
randomReturn = threatened;
|
||||
} else if (logic.equals("Prevent")) { // prevent burn spell from opponent
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final SpellAbility saTop = game.getStack().peekAbility();
|
||||
final Card host = saTop.getHostCard();
|
||||
if (saTop.getActivatingPlayer() != ai // from opponent
|
||||
&& !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();
|
||||
if (type == ApiType.DealDamage || type == ApiType.DamageAll) { // burn spell
|
||||
sa.getTargets().add(host);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (logic.equals("NoGain")) {
|
||||
// basic logic to cancel GainLife on stack
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final SpellAbility topStack = game.getStack().peekAbility();
|
||||
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")) {
|
||||
// for DamageDeal sub-abilities (eg. Wild Slash, Skullcrack)
|
||||
SpellAbility burn = sa.getSubAbility();
|
||||
return SpellApiToAi.Converter.get(burn.getApi()).canPlayAIWithSubs(ai, burn);
|
||||
} else if (logic.equals("YawgmothsWill")) {
|
||||
return SpecialCardAi.YawgmothsWill.consider(ai, sa);
|
||||
} else if (logic.startsWith("NeedCreatures")) {
|
||||
if (ai.getCreaturesInPlay().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (logic.contains(":")) {
|
||||
String k[] = logic.split(":");
|
||||
Integer i = Integer.valueOf(k[1]);
|
||||
if (ai.getCreaturesInPlay().size() < i) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (logic.equals("CastFromGraveThisTurn")) {
|
||||
CardCollection list = new CardCollection(game.getCardsIn(ZoneType.Graveyard));
|
||||
list = CardLists.getValidCards(list, sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else { //no AILogic
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("False".equals(sa.getParam("Stackable"))) {
|
||||
String name = sa.getParam("Name");
|
||||
if (name == null) {
|
||||
name = sa.getHostCard().getName() + "'s Effect";
|
||||
}
|
||||
if (sa.getActivatingPlayer().isCardInCommand(name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null && tgt.canTgtPlayer()) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent() || logic.equals("BeginningOfOppTurn")) {
|
||||
boolean canTgt = false;
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
canTgt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return canTgt;
|
||||
} else {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
// E.g. Nova Pentacle
|
||||
if (aiLogic.equals("RedirectFromOppToCreature")) {
|
||||
// try to target the opponent's best targetable permanent, if able
|
||||
CardCollection oppPerms = CardLists.getValidCards(aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||
if (!oppPerms.isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ComputerUtilCard.getBestAI(oppPerms));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mandatory) {
|
||||
// try to target the AI's worst targetable permanent, if able
|
||||
CardCollection aiPerms = CardLists.getValidCards(aiPlayer.getCardsIn(ZoneType.Battlefield), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||
if (!aiPerms.isEmpty()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(aiPerms));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,124 +1,124 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactoryBond class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
|
||||
*/
|
||||
public final class EncodeAi extends SpellAbilityAi {
|
||||
/**
|
||||
* <p>
|
||||
* bondCanPlayAI.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#confirmAction(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility,
|
||||
* forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
// only try to encode if there is a creature it can be used on
|
||||
return chooseCard(player, player.getCreaturesInPlay(), true) != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
|
||||
* forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
return chooseCard(ai, options, isOptional);
|
||||
}
|
||||
|
||||
private Card chooseCard(final Player ai, Iterable<Card> list, boolean isOptional) {
|
||||
Card choice = null;
|
||||
// final String logic = sa.getParam("AILogic");
|
||||
// if (logic == null) {
|
||||
final List<Card> attackers = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return ComputerUtilCombat.canAttackNextTurn(c);
|
||||
}
|
||||
});
|
||||
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
boolean canAttackOpponent = false;
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
if (CombatUtil.canAttack(c, opp) && !CombatUtil.canBeBlocked(c, opp)) {
|
||||
canAttackOpponent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return canAttackOpponent;
|
||||
}
|
||||
});
|
||||
if (!unblockables.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestAI(unblockables);
|
||||
} else if (!attackers.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestAI(attackers);
|
||||
} else if (!isOptional) {
|
||||
choice = ComputerUtilCard.getBestAI(list);
|
||||
}
|
||||
// }
|
||||
return choice;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactoryBond class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
|
||||
*/
|
||||
public final class EncodeAi extends SpellAbilityAi {
|
||||
/**
|
||||
* <p>
|
||||
* bondCanPlayAI.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#confirmAction(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility,
|
||||
* forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
// only try to encode if there is a creature it can be used on
|
||||
return chooseCard(player, player.getCreaturesInPlay(), true) != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
|
||||
* forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
return chooseCard(ai, options, isOptional);
|
||||
}
|
||||
|
||||
private Card chooseCard(final Player ai, Iterable<Card> list, boolean isOptional) {
|
||||
Card choice = null;
|
||||
// final String logic = sa.getParam("AILogic");
|
||||
// if (logic == null) {
|
||||
final List<Card> attackers = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return ComputerUtilCombat.canAttackNextTurn(c);
|
||||
}
|
||||
});
|
||||
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
boolean canAttackOpponent = false;
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
if (CombatUtil.canAttack(c, opp) && !CombatUtil.canBeBlocked(c, opp)) {
|
||||
canAttackOpponent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return canAttackOpponent;
|
||||
}
|
||||
});
|
||||
if (!unblockables.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestAI(unblockables);
|
||||
} else if (!attackers.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestAI(attackers);
|
||||
} else if (!isOptional) {
|
||||
choice = ComputerUtilCard.getBestAI(list);
|
||||
}
|
||||
// }
|
||||
return choice;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class EndTurnAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { return false; }
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class EndTurnAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { return false; }
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,263 +1,263 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
public class FightAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
if (sa.hasParam("FightWithToughness")) {
|
||||
// TODO: add ailogic
|
||||
return false;
|
||||
}
|
||||
return super.checkAiLogic(ai, sa, aiLogic);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
sa.resetTargets();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
// Get creature lists
|
||||
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
|
||||
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
|
||||
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
||||
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||
|
||||
// assumes the triggered card belongs to the ai
|
||||
if (sa.hasParam("Defined")) {
|
||||
Card fighter1 = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
|
||||
for (Card humanCreature : humCreatures) {
|
||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetPower()
|
||||
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(fighter1)) {
|
||||
// todo: check min/max targets; see if we picked the best
|
||||
// matchup
|
||||
sa.getTargets().add(humanCreature);
|
||||
return true;
|
||||
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
|
||||
sa.getTargets().add(humanCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("TargetsFromDifferentZone")) {
|
||||
if (!(humCreatures.isEmpty() && aiCreatures.isEmpty())) {
|
||||
for (Card humanCreature : humCreatures) {
|
||||
for (Card aiCreature : aiCreatures) {
|
||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
|
||||
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
|
||||
// todo: check min/max targets; see if we picked the
|
||||
// best matchup
|
||||
sa.getTargets().add(humanCreature);
|
||||
sa.getTargets().add(aiCreature);
|
||||
return true;
|
||||
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
|
||||
sa.getTargets().add(humanCreature);
|
||||
sa.getTargets().add(aiCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (Card creature1 : humCreatures) {
|
||||
for (Card creature2 : humCreatures) {
|
||||
if (creature1.equals(creature2)) {
|
||||
continue;
|
||||
}
|
||||
if (sa.hasParam("TargetsWithoutSameCreatureType") && creature1.sharesCreatureTypeWith(creature2)) {
|
||||
continue;
|
||||
}
|
||||
if (ComputerUtilCombat.getDamageToKill(creature1) <= creature2.getNetPower()
|
||||
&& creature1.getNetPower() >= ComputerUtilCombat.getDamageToKill(creature2)) {
|
||||
// todo: check min/max targets; see if we picked the best
|
||||
// matchup
|
||||
sa.getTargets().add(creature1);
|
||||
sa.getTargets().add(creature2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||
return checkApiLogic(aiPlayer, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (canPlayAI(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
if (!mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//try to make a good trade or no trade
|
||||
final Card source = sa.getHostCard();
|
||||
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||
if (humCreatures.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
//assumes the triggered card belongs to the ai
|
||||
if (sa.hasParam("Defined")) {
|
||||
Card aiCreature = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
|
||||
for (Card humanCreature : humCreatures) {
|
||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
|
||||
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
|
||||
sa.getTargets().add(humanCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (Card humanCreature : humCreatures) {
|
||||
if (ComputerUtilCombat.getDamageToKill(aiCreature) > humanCreature.getNetPower()) {
|
||||
sa.getTargets().add(humanCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
sa.getTargets().add(humCreatures.get(0));
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic for evaluating fight effects
|
||||
* @param ai controlling player
|
||||
* @param sa host SpellAbility
|
||||
* @param toughness bonus to toughness
|
||||
* @param power bonus to power
|
||||
* @return true if fight effect should be played, false otherwise
|
||||
*/
|
||||
public static boolean canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final AbilitySub tgtFight = sa.getSubAbility();
|
||||
final boolean isChandrasIgnition = "Chandra's Ignition".equals(sourceName); // TODO: generalize this for other "fake Fight" cases that do not target
|
||||
if ("Savage Punch".equals(sourceName) && !ai.hasFerocious()) {
|
||||
power = 0;
|
||||
toughness = 0;
|
||||
}
|
||||
// Get sorted creature lists
|
||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||
CardCollection humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||
if ("Time to Feed".equals(sourceName)) { // flip sa
|
||||
aiCreatures = CardLists.getTargetableCards(aiCreatures, tgtFight);
|
||||
aiCreatures = ComputerUtil.getSafeTargets(ai, tgtFight, aiCreatures);
|
||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||
} else {
|
||||
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
|
||||
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
||||
humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight);
|
||||
}
|
||||
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
|
||||
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
|
||||
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// Evaluate creature pairs
|
||||
for (Card humanCreature : humCreatures) {
|
||||
for (Card aiCreature : aiCreatures) {
|
||||
if (source.isSpell()) { // heroic triggers adding counters and prowess
|
||||
final int bonus = getSpellBonus(aiCreature);
|
||||
power += bonus;
|
||||
toughness += bonus;
|
||||
}
|
||||
if ("PowerDmg".equals(sa.getParam("AILogic"))) {
|
||||
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)) {
|
||||
if ("Time to Feed".equals(sourceName)) { // flip targets
|
||||
final Card tmp = aiCreature;
|
||||
aiCreature = humanCreature;
|
||||
humanCreature = tmp;
|
||||
}
|
||||
sa.getTargets().add(aiCreature);
|
||||
tgtFight.resetTargets();
|
||||
tgtFight.getTargets().add(humanCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the bonus from Heroic +1/+1 counters or Prowess
|
||||
*/
|
||||
private static int getSpellBonus(final Card aiCreature) {
|
||||
for (Trigger t : aiCreature.getTriggers()) {
|
||||
if (t.getMode() == TriggerType.SpellCast) {
|
||||
final Map<String, String> params = t.getMapParams();
|
||||
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))
|
||||
&& params.containsKey("Execute")) {
|
||||
SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
|
||||
if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
|
||||
return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ("ProwessPump".equals(params.get("Execute"))) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) {
|
||||
if (canKill(fighter, opponent, pumpAttack)) {
|
||||
if (!canKill(opponent, fighter, -pumpDefense)) { // can survive
|
||||
return true;
|
||||
} else {
|
||||
final Random r = MyRandom.getRandom();
|
||||
if (r.nextInt(20)<(opponent.getCMC() - fighter.getCMC())) { // trade
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static boolean canKill(Card fighter, Card opponent, int pumpAttack) {
|
||||
if (opponent.getSVar("Targeting").equals("Dies")) {
|
||||
return true;
|
||||
}
|
||||
if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed()
|
||||
|| opponent.getShieldCount() > 0 || ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
|
||||
return false;
|
||||
}
|
||||
if (fighter.hasKeyword("Deathtouch") || ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetPower() + pumpAttack) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
public class FightAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
if (sa.hasParam("FightWithToughness")) {
|
||||
// TODO: add ailogic
|
||||
return false;
|
||||
}
|
||||
return super.checkAiLogic(ai, sa, aiLogic);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
sa.resetTargets();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
// Get creature lists
|
||||
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
|
||||
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
|
||||
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
||||
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||
|
||||
// assumes the triggered card belongs to the ai
|
||||
if (sa.hasParam("Defined")) {
|
||||
Card fighter1 = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
|
||||
for (Card humanCreature : humCreatures) {
|
||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetPower()
|
||||
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(fighter1)) {
|
||||
// todo: check min/max targets; see if we picked the best
|
||||
// matchup
|
||||
sa.getTargets().add(humanCreature);
|
||||
return true;
|
||||
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
|
||||
sa.getTargets().add(humanCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("TargetsFromDifferentZone")) {
|
||||
if (!(humCreatures.isEmpty() && aiCreatures.isEmpty())) {
|
||||
for (Card humanCreature : humCreatures) {
|
||||
for (Card aiCreature : aiCreatures) {
|
||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
|
||||
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
|
||||
// todo: check min/max targets; see if we picked the
|
||||
// best matchup
|
||||
sa.getTargets().add(humanCreature);
|
||||
sa.getTargets().add(aiCreature);
|
||||
return true;
|
||||
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
|
||||
sa.getTargets().add(humanCreature);
|
||||
sa.getTargets().add(aiCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (Card creature1 : humCreatures) {
|
||||
for (Card creature2 : humCreatures) {
|
||||
if (creature1.equals(creature2)) {
|
||||
continue;
|
||||
}
|
||||
if (sa.hasParam("TargetsWithoutSameCreatureType") && creature1.sharesCreatureTypeWith(creature2)) {
|
||||
continue;
|
||||
}
|
||||
if (ComputerUtilCombat.getDamageToKill(creature1) <= creature2.getNetPower()
|
||||
&& creature1.getNetPower() >= ComputerUtilCombat.getDamageToKill(creature2)) {
|
||||
// todo: check min/max targets; see if we picked the best
|
||||
// matchup
|
||||
sa.getTargets().add(creature1);
|
||||
sa.getTargets().add(creature2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||
return checkApiLogic(aiPlayer, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (canPlayAI(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
if (!mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//try to make a good trade or no trade
|
||||
final Card source = sa.getHostCard();
|
||||
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||
if (humCreatures.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
//assumes the triggered card belongs to the ai
|
||||
if (sa.hasParam("Defined")) {
|
||||
Card aiCreature = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
|
||||
for (Card humanCreature : humCreatures) {
|
||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
|
||||
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
|
||||
sa.getTargets().add(humanCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (Card humanCreature : humCreatures) {
|
||||
if (ComputerUtilCombat.getDamageToKill(aiCreature) > humanCreature.getNetPower()) {
|
||||
sa.getTargets().add(humanCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
sa.getTargets().add(humCreatures.get(0));
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic for evaluating fight effects
|
||||
* @param ai controlling player
|
||||
* @param sa host SpellAbility
|
||||
* @param toughness bonus to toughness
|
||||
* @param power bonus to power
|
||||
* @return true if fight effect should be played, false otherwise
|
||||
*/
|
||||
public static boolean canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final AbilitySub tgtFight = sa.getSubAbility();
|
||||
final boolean isChandrasIgnition = "Chandra's Ignition".equals(sourceName); // TODO: generalize this for other "fake Fight" cases that do not target
|
||||
if ("Savage Punch".equals(sourceName) && !ai.hasFerocious()) {
|
||||
power = 0;
|
||||
toughness = 0;
|
||||
}
|
||||
// Get sorted creature lists
|
||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||
CardCollection humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||
if ("Time to Feed".equals(sourceName)) { // flip sa
|
||||
aiCreatures = CardLists.getTargetableCards(aiCreatures, tgtFight);
|
||||
aiCreatures = ComputerUtil.getSafeTargets(ai, tgtFight, aiCreatures);
|
||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||
} else {
|
||||
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
|
||||
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
||||
humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight);
|
||||
}
|
||||
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
|
||||
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
|
||||
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// Evaluate creature pairs
|
||||
for (Card humanCreature : humCreatures) {
|
||||
for (Card aiCreature : aiCreatures) {
|
||||
if (source.isSpell()) { // heroic triggers adding counters and prowess
|
||||
final int bonus = getSpellBonus(aiCreature);
|
||||
power += bonus;
|
||||
toughness += bonus;
|
||||
}
|
||||
if ("PowerDmg".equals(sa.getParam("AILogic"))) {
|
||||
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)) {
|
||||
if ("Time to Feed".equals(sourceName)) { // flip targets
|
||||
final Card tmp = aiCreature;
|
||||
aiCreature = humanCreature;
|
||||
humanCreature = tmp;
|
||||
}
|
||||
sa.getTargets().add(aiCreature);
|
||||
tgtFight.resetTargets();
|
||||
tgtFight.getTargets().add(humanCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the bonus from Heroic +1/+1 counters or Prowess
|
||||
*/
|
||||
private static int getSpellBonus(final Card aiCreature) {
|
||||
for (Trigger t : aiCreature.getTriggers()) {
|
||||
if (t.getMode() == TriggerType.SpellCast) {
|
||||
final Map<String, String> params = t.getMapParams();
|
||||
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))
|
||||
&& params.containsKey("Execute")) {
|
||||
SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
|
||||
if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
|
||||
return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ("ProwessPump".equals(params.get("Execute"))) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) {
|
||||
if (canKill(fighter, opponent, pumpAttack)) {
|
||||
if (!canKill(opponent, fighter, -pumpDefense)) { // can survive
|
||||
return true;
|
||||
} else {
|
||||
final Random r = MyRandom.getRandom();
|
||||
if (r.nextInt(20)<(opponent.getCMC() - fighter.getCMC())) { // trade
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static boolean canKill(Card fighter, Card opponent, int pumpAttack) {
|
||||
if (opponent.getSVar("Targeting").equals("Dies")) {
|
||||
return true;
|
||||
}
|
||||
if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed()
|
||||
|| opponent.getShieldCount() > 0 || ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
|
||||
return false;
|
||||
}
|
||||
if (fighter.hasKeyword("Deathtouch") || ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetPower() + pumpAttack) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class FlipACoinAi 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) {
|
||||
|
||||
if (sa.hasParam("AILogic")) {
|
||||
String AILogic = sa.getParam("AILogic");
|
||||
if (AILogic.equals("Never")) {
|
||||
return false;
|
||||
} else if (AILogic.equals("PhaseOut")) {
|
||||
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;
|
||||
}
|
||||
sa.resetTargets();
|
||||
for (Card c : ai.getOpponents().getCreaturesInPlay()) {
|
||||
if (sa.canTarget(c)) {
|
||||
sa.getTargets().add(c);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class FlipACoinAi 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) {
|
||||
|
||||
if (sa.hasParam("AILogic")) {
|
||||
String AILogic = sa.getParam("AILogic");
|
||||
if (AILogic.equals("Never")) {
|
||||
return false;
|
||||
} else if (AILogic.equals("PhaseOut")) {
|
||||
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;
|
||||
}
|
||||
sa.resetTargets();
|
||||
for (Card c : ai.getOpponents().getCreaturesInPlay()) {
|
||||
if (sa.canTarget(c)) {
|
||||
sa.getTargets().add(c);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +1,92 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
public class FogAi 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();
|
||||
// AI should only activate this during Human's Declare Blockers phase
|
||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only cast when Stack is empty, so Human uses spells/abilities first
|
||||
if (!game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't cast it, if the effect is already in place
|
||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("SeriousDamage".equals(sa.getParam("AILogic")) && game.getCombat() != null) {
|
||||
int dmg = 0;
|
||||
for (Card atk : game.getCombat().getAttackersOf(ai)) {
|
||||
if (game.getCombat().isUnblocked(atk)) {
|
||||
dmg += atk.getNetCombatDamage();
|
||||
} else if (atk.hasKeyword("Trample")) {
|
||||
dmg += atk.getNetCombatDamage() - Aggregates.sum(game.getCombat().getBlockers(atk), CardPredicates.Accessors.fnGetNetToughness);
|
||||
}
|
||||
}
|
||||
|
||||
if (dmg > ai.getLife() / 4) {
|
||||
return true;
|
||||
} else if (dmg >= 5) {
|
||||
return true;
|
||||
} else if (ai.getLife() < ai.getStartingLife() / 3) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Cast it if life is in danger
|
||||
return ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
// AI should only activate this during Human's turn
|
||||
boolean chance;
|
||||
final Game game = ai.getGame();
|
||||
|
||||
// should really check if other player is attacking this player
|
||||
if (ai.isOpponentOf(game.getPhaseHandler().getPlayerTurn())) {
|
||||
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
|
||||
} else {
|
||||
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
final Game game = aiPlayer.getGame();
|
||||
boolean chance;
|
||||
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);
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
public class FogAi 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();
|
||||
// AI should only activate this during Human's Declare Blockers phase
|
||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only cast when Stack is empty, so Human uses spells/abilities first
|
||||
if (!game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't cast it, if the effect is already in place
|
||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("SeriousDamage".equals(sa.getParam("AILogic")) && game.getCombat() != null) {
|
||||
int dmg = 0;
|
||||
for (Card atk : game.getCombat().getAttackersOf(ai)) {
|
||||
if (game.getCombat().isUnblocked(atk)) {
|
||||
dmg += atk.getNetCombatDamage();
|
||||
} else if (atk.hasKeyword("Trample")) {
|
||||
dmg += atk.getNetCombatDamage() - Aggregates.sum(game.getCombat().getBlockers(atk), CardPredicates.Accessors.fnGetNetToughness);
|
||||
}
|
||||
}
|
||||
|
||||
if (dmg > ai.getLife() / 4) {
|
||||
return true;
|
||||
} else if (dmg >= 5) {
|
||||
return true;
|
||||
} else if (ai.getLife() < ai.getStartingLife() / 3) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Cast it if life is in danger
|
||||
return ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
// AI should only activate this during Human's turn
|
||||
boolean chance;
|
||||
final Game game = ai.getGame();
|
||||
|
||||
// should really check if other player is attacking this player
|
||||
if (ai.isOpponentOf(game.getPhaseHandler().getPlayerTurn())) {
|
||||
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
|
||||
} else {
|
||||
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
final Game game = aiPlayer.getGame();
|
||||
boolean chance;
|
||||
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);
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
public class GameLossAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (opp.cantLose()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only one SA Lose the Game card right now, which is Door to
|
||||
// Nothingness
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
// In general, don't return true.
|
||||
// But this card wins the game, I can make an exception for that
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
// Phage the Untouchable
|
||||
// (Final Fortune would need to attach it's delayed trigger to a
|
||||
// specific turn, which can't be done yet)
|
||||
|
||||
if (!mandatory && ComputerUtil.getOpponentFor(ai).cantLose()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
public class GameLossAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (opp.cantLose()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only one SA Lose the Game card right now, which is Door to
|
||||
// Nothingness
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
// In general, don't return true.
|
||||
// But this card wins the game, I can make an exception for that
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
// Phage the Untouchable
|
||||
// (Final Fortune would need to attach it's delayed trigger to a
|
||||
// specific turn, which can't be done yet)
|
||||
|
||||
if (!mandatory && ComputerUtil.getOpponentFor(ai).cantLose()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class GameWinAi 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) {
|
||||
if (ai.cantWin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO Check conditions are met on card (e.g. Coalition Victory)
|
||||
|
||||
// TODO Consider likelihood of SA getting countered
|
||||
|
||||
// In general, don't return true.
|
||||
// But this card wins the game, I can make an exception for that
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class GameWinAi 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) {
|
||||
if (ai.cantWin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO Check conditions are met on card (e.g. Coalition Victory)
|
||||
|
||||
// TODO Consider likelihood of SA getting countered
|
||||
|
||||
// In general, don't return true.
|
||||
// But this card wins the game, I can make an exception for that
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class HauntAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
if (sa.usesTargeting() && !card.isToken()) {
|
||||
final List<Card> creats = CardLists.filter(game.getCardsIn(ZoneType.Battlefield),
|
||||
CardPredicates.Presets.CREATURES);
|
||||
|
||||
// nothing to haunt
|
||||
if (creats.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents());
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class HauntAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
if (sa.usesTargeting() && !card.isToken()) {
|
||||
final List<Card> creats = CardLists.filter(game.getCardsIn(ZoneType.Battlefield),
|
||||
CardPredicates.Presets.CREATURES);
|
||||
|
||||
// nothing to haunt
|
||||
if (creats.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents());
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +1,60 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class LegendaryRuleAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false; // should not get here
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
// Choose a single legendary/planeswalker card to keep
|
||||
Card firstOption = Iterables.getFirst(options, null);
|
||||
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
|
||||
|
||||
if ( choosingFromPlanewalkers ) {
|
||||
// AI decision making - should AI compare counters?
|
||||
} else {
|
||||
// AI decision making - should AI compare damage and debuffs?
|
||||
}
|
||||
|
||||
// TODO: Can this be made more generic somehow?
|
||||
if (firstOption.getName().equals("Dark Depths")) {
|
||||
Card best = firstOption;
|
||||
for (Card c : options) {
|
||||
if (c.getCounters(CounterType.ICE) < best.getCounters(CounterType.ICE)) {
|
||||
best = c;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
} else if (firstOption.getCounters(CounterType.KI) > 0) {
|
||||
// Extra Rule for KI counter
|
||||
Card best = firstOption;
|
||||
for (Card c : options) {
|
||||
if (c.getCounters(CounterType.KI) > best.getCounters(CounterType.KI)) {
|
||||
best = c;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
return firstOption;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class LegendaryRuleAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false; // should not get here
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
// Choose a single legendary/planeswalker card to keep
|
||||
Card firstOption = Iterables.getFirst(options, null);
|
||||
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
|
||||
|
||||
if ( choosingFromPlanewalkers ) {
|
||||
// AI decision making - should AI compare counters?
|
||||
} else {
|
||||
// AI decision making - should AI compare damage and debuffs?
|
||||
}
|
||||
|
||||
// TODO: Can this be made more generic somehow?
|
||||
if (firstOption.getName().equals("Dark Depths")) {
|
||||
Card best = firstOption;
|
||||
for (Card c : options) {
|
||||
if (c.getCounters(CounterType.ICE) < best.getCounters(CounterType.ICE)) {
|
||||
best = c;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
} else if (firstOption.getCounters(CounterType.KI) > 0) {
|
||||
// Extra Rule for KI counter
|
||||
Card best = firstOption;
|
||||
for (Card c : options) {
|
||||
if (c.getCounters(CounterType.KI) > best.getCounters(CounterType.KI)) {
|
||||
best = c;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
return firstOption;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,94 +1,94 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class LifeExchangeAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
|
||||
* (forge.game.player.Player, java.util.Map,
|
||||
* forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
final Random r = MyRandom.getRandom();
|
||||
final int myLife = aiPlayer.getLife();
|
||||
Player opponent = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
final int hLife = opponent.getLife();
|
||||
|
||||
if (!aiPlayer.canGainLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
/*
|
||||
* TODO - There is one card that takes two targets (Soul Conduit)
|
||||
* and one card that has a conditional (Psychic Transfer) that are
|
||||
* not currently handled
|
||||
*/
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (opponent.canBeTargetedBy(sa)) {
|
||||
// never target self, that would be silly for exchange
|
||||
sa.getTargets().add(opponent);
|
||||
if (!opponent.canLoseLife()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if life is in danger, always activate
|
||||
if ((myLife < 5) && (hLife > myLife)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// cost includes sacrifice probably, so make sure it's worth it
|
||||
chance &= (hLife > (myLife + 8));
|
||||
|
||||
return ((r.nextFloat() < .6667) && chance);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* exchangeLifeDoTriggerAINoCost.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||
final boolean mandatory) {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class LifeExchangeAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
|
||||
* (forge.game.player.Player, java.util.Map,
|
||||
* forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
final Random r = MyRandom.getRandom();
|
||||
final int myLife = aiPlayer.getLife();
|
||||
Player opponent = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
final int hLife = opponent.getLife();
|
||||
|
||||
if (!aiPlayer.canGainLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
/*
|
||||
* TODO - There is one card that takes two targets (Soul Conduit)
|
||||
* and one card that has a conditional (Psychic Transfer) that are
|
||||
* not currently handled
|
||||
*/
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (opponent.canBeTargetedBy(sa)) {
|
||||
// never target self, that would be silly for exchange
|
||||
sa.getTargets().add(opponent);
|
||||
if (!opponent.canLoseLife()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if life is in danger, always activate
|
||||
if ((myLife < 5) && (hLife > myLife)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// cost includes sacrifice probably, so make sure it's worth it
|
||||
chance &= (hLife > (myLife + 8));
|
||||
|
||||
return ((r.nextFloat() < .6667) && chance);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* exchangeLifeDoTriggerAINoCost.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||
final boolean mandatory) {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,313 +1,313 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.cost.CostSacrifice;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class LifeGainAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.cost.Cost,
|
||||
* forge.game.card.Card)
|
||||
*/
|
||||
@Override
|
||||
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
|
||||
final Game game = source.getGame();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
final int life = ai.getLife();
|
||||
|
||||
boolean lifeCritical = life <= 5;
|
||||
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
|
||||
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
||||
|
||||
if (!lifeCritical) {
|
||||
// return super.willPayCosts(ai, sa, cost, source);
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa,false)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// don't sac possible blockers
|
||||
if (!ph.getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
|| !game.getCombat().getDefenders().contains(ai)) {
|
||||
boolean skipCheck = false;
|
||||
// if it's a sac self cost and the effect source is not a
|
||||
// creature, skip this check
|
||||
// (e.g. Woodweaver's Puzzleknot)
|
||||
skipCheck |= ComputerUtilCost.isSacrificeSelfCost(cost) && !source.isCreature();
|
||||
|
||||
if (!skipCheck) {
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa,false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final Game game = ai.getGame();
|
||||
final int life = ai.getLife();
|
||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||
|
||||
boolean lifeCritical = life <= 5;
|
||||
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
|
||||
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
||||
|
||||
// When life is critical but there is no immediate danger, try to wait until declare blockers
|
||||
// before using the lifegain ability if it's an ability on a creature with a detrimental activation cost
|
||||
if (lifeCritical
|
||||
&& sa.isAbility()
|
||||
&& sa.getHostCard() != null && sa.getHostCard().isCreature()
|
||||
&& sa.getPayCosts() != null
|
||||
&& (sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class) || sa.getPayCosts().hasSpecificCostType(CostSacrifice.class))) {
|
||||
if (!game.getStack().isEmpty()) {
|
||||
SpellAbility saTop = game.getStack().peekAbility();
|
||||
if (saTop.getTargets() != null && Iterables.contains(saTop.getTargets().getTargetPlayers(), ai)) {
|
||||
return ComputerUtil.predictDamageFromSpell(saTop, ai) > 0;
|
||||
}
|
||||
}
|
||||
if (game.getCombat() == null) { return false; }
|
||||
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { return false; }
|
||||
}
|
||||
|
||||
// Don't use lifegain before main 2 if possible
|
||||
if (!lifeCritical && ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lifeCritical && !activateForCost
|
||||
&& (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
|
||||
final int life = ai.getLife();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int lifeAmount = 0;
|
||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
lifeAmount = xPay;
|
||||
} else {
|
||||
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
|
||||
// Ugin AI: always use ultimate
|
||||
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
||||
// TODO: somehow link with DamageDealAi for cases where +1 = win
|
||||
return true;
|
||||
}
|
||||
|
||||
// don't use it if no life to gain
|
||||
if (!activateForCost && lifeAmount <= 0) {
|
||||
return false;
|
||||
}
|
||||
// don't play if the conditions aren't met, unless it would trigger a
|
||||
// beneficial sub-condition
|
||||
if (!activateForCost && !sa.getConditions().areMet(sa)) {
|
||||
final AbilitySub abSub = sa.getSubAbility();
|
||||
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
|
||||
if (!abSub.getConditions().areMet(abSub)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!activateForCost && !ai.canGainLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
if (!target(ai, sa, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa)
|
||||
|| sa.getSubAbility() != null || SpellAbilityAi.playReusable(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Save instant-speed life-gain unless it is really worth it
|
||||
final float value = 0.9f * lifeAmount / life;
|
||||
if (value < 0.2f) {
|
||||
return false;
|
||||
}
|
||||
return MyRandom.getRandom().nextFloat() < value;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* gainLifeDoTriggerAINoCost.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||
final boolean mandatory) {
|
||||
|
||||
// If the Target is gaining life, target self.
|
||||
// if the Target is modifying how much life is gained, this needs to be
|
||||
// handled better
|
||||
if (sa.usesTargeting()) {
|
||||
if (!target(ai, sa, mandatory)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return doTriggerAINoCost(ai, sa, true);
|
||||
}
|
||||
|
||||
private boolean target(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
Card source = sa.getHostCard();
|
||||
sa.resetTargets();
|
||||
// TODO : add add even more logic into it
|
||||
// try to target opponents first if that would kill them
|
||||
|
||||
PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
|
||||
if (sa.canTarget(ai) && ComputerUtil.lifegainPositive(ai, source)) {
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
boolean hasTgt = false;
|
||||
// check for Lifegain negative on opponents
|
||||
for (Player opp : opps) {
|
||||
if (ComputerUtil.lifegainNegative(opp, source)) {
|
||||
sa.getTargets().add(opp);
|
||||
hasTgt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasTgt) {
|
||||
// lifegain on ally
|
||||
for (Player ally : allies) {
|
||||
if (ComputerUtil.lifegainPositive(ally, source)) {
|
||||
sa.getTargets().add(ally);
|
||||
hasTgt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasTgt && mandatory) {
|
||||
// need to target something but its neither negative against
|
||||
// opponents,
|
||||
// nor posive against allies
|
||||
|
||||
// hurting ally is probably better than healing opponent
|
||||
// look for Lifegain not Negative (case of lifegain negated)
|
||||
for (Player ally : allies) {
|
||||
if (!ComputerUtil.lifegainNegative(ally, source)) {
|
||||
sa.getTargets().add(ally);
|
||||
hasTgt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasTgt) {
|
||||
// same for opponent lifegain not positive
|
||||
for (Player opp : opps) {
|
||||
if (!ComputerUtil.lifegainPositive(opp, source)) {
|
||||
sa.getTargets().add(opp);
|
||||
hasTgt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// still no luck, try to target ally with most life
|
||||
if (!allies.isEmpty()) {
|
||||
Player ally = allies.max(PlayerPredicates.compareByLife());
|
||||
sa.getTargets().add(ally);
|
||||
hasTgt = true;
|
||||
}
|
||||
// better heal opponent which most life then the one with the
|
||||
// lowest
|
||||
if (!hasTgt) {
|
||||
Player opp = opps.max(PlayerPredicates.compareByLife());
|
||||
sa.getTargets().add(opp);
|
||||
hasTgt = true;
|
||||
}
|
||||
}
|
||||
if (!hasTgt) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.cost.CostSacrifice;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class LifeGainAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.cost.Cost,
|
||||
* forge.game.card.Card)
|
||||
*/
|
||||
@Override
|
||||
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
|
||||
final Game game = source.getGame();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
final int life = ai.getLife();
|
||||
|
||||
boolean lifeCritical = life <= 5;
|
||||
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
|
||||
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
||||
|
||||
if (!lifeCritical) {
|
||||
// return super.willPayCosts(ai, sa, cost, source);
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa,false)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// don't sac possible blockers
|
||||
if (!ph.getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
|| !game.getCombat().getDefenders().contains(ai)) {
|
||||
boolean skipCheck = false;
|
||||
// if it's a sac self cost and the effect source is not a
|
||||
// creature, skip this check
|
||||
// (e.g. Woodweaver's Puzzleknot)
|
||||
skipCheck |= ComputerUtilCost.isSacrificeSelfCost(cost) && !source.isCreature();
|
||||
|
||||
if (!skipCheck) {
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa,false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final Game game = ai.getGame();
|
||||
final int life = ai.getLife();
|
||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||
|
||||
boolean lifeCritical = life <= 5;
|
||||
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
|
||||
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
||||
|
||||
// When life is critical but there is no immediate danger, try to wait until declare blockers
|
||||
// before using the lifegain ability if it's an ability on a creature with a detrimental activation cost
|
||||
if (lifeCritical
|
||||
&& sa.isAbility()
|
||||
&& sa.getHostCard() != null && sa.getHostCard().isCreature()
|
||||
&& sa.getPayCosts() != null
|
||||
&& (sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class) || sa.getPayCosts().hasSpecificCostType(CostSacrifice.class))) {
|
||||
if (!game.getStack().isEmpty()) {
|
||||
SpellAbility saTop = game.getStack().peekAbility();
|
||||
if (saTop.getTargets() != null && Iterables.contains(saTop.getTargets().getTargetPlayers(), ai)) {
|
||||
return ComputerUtil.predictDamageFromSpell(saTop, ai) > 0;
|
||||
}
|
||||
}
|
||||
if (game.getCombat() == null) { return false; }
|
||||
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { return false; }
|
||||
}
|
||||
|
||||
// Don't use lifegain before main 2 if possible
|
||||
if (!lifeCritical && ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lifeCritical && !activateForCost
|
||||
&& (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
|
||||
final int life = ai.getLife();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int lifeAmount = 0;
|
||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
lifeAmount = xPay;
|
||||
} else {
|
||||
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
|
||||
// Ugin AI: always use ultimate
|
||||
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
||||
// TODO: somehow link with DamageDealAi for cases where +1 = win
|
||||
return true;
|
||||
}
|
||||
|
||||
// don't use it if no life to gain
|
||||
if (!activateForCost && lifeAmount <= 0) {
|
||||
return false;
|
||||
}
|
||||
// don't play if the conditions aren't met, unless it would trigger a
|
||||
// beneficial sub-condition
|
||||
if (!activateForCost && !sa.getConditions().areMet(sa)) {
|
||||
final AbilitySub abSub = sa.getSubAbility();
|
||||
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
|
||||
if (!abSub.getConditions().areMet(abSub)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!activateForCost && !ai.canGainLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
if (!target(ai, sa, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa)
|
||||
|| sa.getSubAbility() != null || SpellAbilityAi.playReusable(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Save instant-speed life-gain unless it is really worth it
|
||||
final float value = 0.9f * lifeAmount / life;
|
||||
if (value < 0.2f) {
|
||||
return false;
|
||||
}
|
||||
return MyRandom.getRandom().nextFloat() < value;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* gainLifeDoTriggerAINoCost.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||
final boolean mandatory) {
|
||||
|
||||
// If the Target is gaining life, target self.
|
||||
// if the Target is modifying how much life is gained, this needs to be
|
||||
// handled better
|
||||
if (sa.usesTargeting()) {
|
||||
if (!target(ai, sa, mandatory)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return doTriggerAINoCost(ai, sa, true);
|
||||
}
|
||||
|
||||
private boolean target(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
Card source = sa.getHostCard();
|
||||
sa.resetTargets();
|
||||
// TODO : add add even more logic into it
|
||||
// try to target opponents first if that would kill them
|
||||
|
||||
PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
|
||||
if (sa.canTarget(ai) && ComputerUtil.lifegainPositive(ai, source)) {
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
boolean hasTgt = false;
|
||||
// check for Lifegain negative on opponents
|
||||
for (Player opp : opps) {
|
||||
if (ComputerUtil.lifegainNegative(opp, source)) {
|
||||
sa.getTargets().add(opp);
|
||||
hasTgt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasTgt) {
|
||||
// lifegain on ally
|
||||
for (Player ally : allies) {
|
||||
if (ComputerUtil.lifegainPositive(ally, source)) {
|
||||
sa.getTargets().add(ally);
|
||||
hasTgt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasTgt && mandatory) {
|
||||
// need to target something but its neither negative against
|
||||
// opponents,
|
||||
// nor posive against allies
|
||||
|
||||
// hurting ally is probably better than healing opponent
|
||||
// look for Lifegain not Negative (case of lifegain negated)
|
||||
for (Player ally : allies) {
|
||||
if (!ComputerUtil.lifegainNegative(ally, source)) {
|
||||
sa.getTargets().add(ally);
|
||||
hasTgt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasTgt) {
|
||||
// same for opponent lifegain not positive
|
||||
for (Player opp : opps) {
|
||||
if (!ComputerUtil.lifegainPositive(opp, source)) {
|
||||
sa.getTargets().add(opp);
|
||||
hasTgt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// still no luck, try to target ally with most life
|
||||
if (!allies.isEmpty()) {
|
||||
Player ally = allies.max(PlayerPredicates.compareByLife());
|
||||
sa.getTargets().add(ally);
|
||||
hasTgt = true;
|
||||
}
|
||||
// better heal opponent which most life then the one with the
|
||||
// lowest
|
||||
if (!hasTgt) {
|
||||
Player opp = opps.max(PlayerPredicates.compareByLife());
|
||||
sa.getTargets().add(opp);
|
||||
hasTgt = true;
|
||||
}
|
||||
}
|
||||
if (!hasTgt) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,236 +1,236 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
public class LifeLoseAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chkAIDrawback(forge.game.spellability.
|
||||
* SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
|
||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// something already set PayX
|
||||
if (source.hasSVar("PayX")) {
|
||||
amount = Integer.parseInt(source.getSVar("PayX"));
|
||||
} else {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
}
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
if (tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.cost.Cost,
|
||||
* forge.game.card.Card)
|
||||
*/
|
||||
@Override
|
||||
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
// source.setSVar("PayX", Integer.toString(amount));
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
// special logic for checkLifeCost
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, amount, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// other cost as the same
|
||||
return super.willPayCosts(ai, sa, cost, source);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(amount));
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
if (amount <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
if (!doTgt(ai, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PlayerCollection filteredPlayer = tgtPlayers
|
||||
.filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount)));
|
||||
// killing opponents asap
|
||||
if (!filteredPlayer.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't use loselife before main 2 if possible
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa) || sa.hasParam("ActivationPhases") || SpellAbilityAi.playReusable(ai, sa)
|
||||
|| ComputerUtil.activateForCost(sa, ai)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#doTriggerAINoCost(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||
final boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
if (!doTgt(ai, sa, mandatory)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
final List<Player> tgtPlayers = sa.usesTargeting() && !sa.hasParam("Defined")
|
||||
? new FCollection<Player>(sa.getTargets().getTargetPlayers())
|
||||
: AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
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) {
|
||||
sa.resetTargets();
|
||||
PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
// try first to find Opponent that can lose life and lose the game
|
||||
if (!opps.isEmpty()) {
|
||||
for (Player opp : opps) {
|
||||
if (opp.canLoseLife() && !opp.cantLose()) {
|
||||
sa.getTargets().add(opp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// do that only if needed
|
||||
if (mandatory) {
|
||||
if (!opps.isEmpty()) {
|
||||
// try another opponent even if it can't lose life
|
||||
sa.getTargets().add(opps.getFirst());
|
||||
return true;
|
||||
}
|
||||
// try hit ally instead of itself
|
||||
for (Player ally : ai.getAllies()) {
|
||||
if (sa.canTarget(ally)) {
|
||||
sa.getTargets().add(ally);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// need to hit itself
|
||||
if (sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected PlayerCollection getPlayers(Player ai, SpellAbility sa) {
|
||||
Iterable<Player> it;
|
||||
if (sa.usesTargeting() && !sa.hasParam("Defined")) {
|
||||
it = sa.getTargets().getTargetPlayers();
|
||||
} else {
|
||||
it = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
}
|
||||
return new PlayerCollection(it);
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
public class LifeLoseAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#chkAIDrawback(forge.game.spellability.
|
||||
* SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
|
||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// something already set PayX
|
||||
if (source.hasSVar("PayX")) {
|
||||
amount = Integer.parseInt(source.getSVar("PayX"));
|
||||
} else {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
}
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
if (tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.cost.Cost,
|
||||
* forge.game.card.Card)
|
||||
*/
|
||||
@Override
|
||||
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
// source.setSVar("PayX", Integer.toString(amount));
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
// special logic for checkLifeCost
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, amount, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// other cost as the same
|
||||
return super.willPayCosts(ai, sa, cost, source);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(amount));
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
if (amount <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
if (!doTgt(ai, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PlayerCollection filteredPlayer = tgtPlayers
|
||||
.filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount)));
|
||||
// killing opponents asap
|
||||
if (!filteredPlayer.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't use loselife before main 2 if possible
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa) || sa.hasParam("ActivationPhases") || SpellAbilityAi.playReusable(ai, sa)
|
||||
|| ComputerUtil.activateForCost(sa, ai)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#doTriggerAINoCost(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||
final boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
if (!doTgt(ai, sa, mandatory)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
final List<Player> tgtPlayers = sa.usesTargeting() && !sa.hasParam("Defined")
|
||||
? new FCollection<Player>(sa.getTargets().getTargetPlayers())
|
||||
: AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
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) {
|
||||
sa.resetTargets();
|
||||
PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
// try first to find Opponent that can lose life and lose the game
|
||||
if (!opps.isEmpty()) {
|
||||
for (Player opp : opps) {
|
||||
if (opp.canLoseLife() && !opp.cantLose()) {
|
||||
sa.getTargets().add(opp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// do that only if needed
|
||||
if (mandatory) {
|
||||
if (!opps.isEmpty()) {
|
||||
// try another opponent even if it can't lose life
|
||||
sa.getTargets().add(opps.getFirst());
|
||||
return true;
|
||||
}
|
||||
// try hit ally instead of itself
|
||||
for (Player ally : ai.getAllies()) {
|
||||
if (sa.canTarget(ally)) {
|
||||
sa.getTargets().add(ally);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// need to hit itself
|
||||
if (sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected PlayerCollection getPlayers(Player ai, SpellAbility sa) {
|
||||
Iterable<Player> it;
|
||||
if (sa.usesTargeting() && !sa.hasParam("Defined")) {
|
||||
it = sa.getTargets().getTargetPlayers();
|
||||
} else {
|
||||
it = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
}
|
||||
return new PlayerCollection(it);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,160 +1,160 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class LifeSetAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Random r = MyRandom.getRandom();
|
||||
// Ability_Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
final int hlife = opponent.getLife();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
|
||||
if (!ai.canGainLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't use setLife before main 2 if possible
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO add AI logic for that
|
||||
if (sa.hasParam("Redistribute")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO handle proper calculation of X values based on Cost and what
|
||||
// would be paid
|
||||
int amount;
|
||||
// we shouldn't have to worry too much about PayX for SetLife
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(opponent);
|
||||
// if we can only target the human, and the Human's life
|
||||
// would
|
||||
// go up, don't play it.
|
||||
// possibly add a combo here for Magister Sphinx and
|
||||
// Higedetsu's
|
||||
// (sp?) Second Rite
|
||||
if ((amount > hlife) || !opponent.canLoseLife()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if ((amount > myLife) && (myLife <= 10)) {
|
||||
sa.getTargets().add(ai);
|
||||
} else if (hlife > amount) {
|
||||
sa.getTargets().add(opponent);
|
||||
} else if (amount > myLife) {
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (sa.getParam("Defined").equals("Player")) {
|
||||
if (amount == 0) {
|
||||
return false;
|
||||
} else if (myLife > amount) { // will decrease computer's
|
||||
// life
|
||||
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (amount <= myLife) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if life is in danger, always activate
|
||||
if ((myLife < 3) && (amount > myLife)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ((r.nextFloat() < .6667) && chance);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
final int hlife = opponent.getLife();
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
|
||||
int amount;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
|
||||
if (sourceName.equals("Eternity Vessel")
|
||||
&& (opponent.isCardInPlay("Vampire Hexmage") || (source.getCounters(CounterType.CHARGE) == 0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the Target is gaining life, target self.
|
||||
// if the Target is modifying how much life is gained, this needs to
|
||||
// be
|
||||
// handled better
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(opponent);
|
||||
} else {
|
||||
if ((amount > myLife) && (myLife <= 10)) {
|
||||
sa.getTargets().add(ai);
|
||||
} else if (hlife > amount) {
|
||||
sa.getTargets().add(opponent);
|
||||
} else if (amount > myLife) {
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class LifeSetAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Random r = MyRandom.getRandom();
|
||||
// Ability_Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
final int hlife = opponent.getLife();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
|
||||
if (!ai.canGainLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't use setLife before main 2 if possible
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO add AI logic for that
|
||||
if (sa.hasParam("Redistribute")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO handle proper calculation of X values based on Cost and what
|
||||
// would be paid
|
||||
int amount;
|
||||
// we shouldn't have to worry too much about PayX for SetLife
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(opponent);
|
||||
// if we can only target the human, and the Human's life
|
||||
// would
|
||||
// go up, don't play it.
|
||||
// possibly add a combo here for Magister Sphinx and
|
||||
// Higedetsu's
|
||||
// (sp?) Second Rite
|
||||
if ((amount > hlife) || !opponent.canLoseLife()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if ((amount > myLife) && (myLife <= 10)) {
|
||||
sa.getTargets().add(ai);
|
||||
} else if (hlife > amount) {
|
||||
sa.getTargets().add(opponent);
|
||||
} else if (amount > myLife) {
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (sa.getParam("Defined").equals("Player")) {
|
||||
if (amount == 0) {
|
||||
return false;
|
||||
} else if (myLife > amount) { // will decrease computer's
|
||||
// life
|
||||
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (amount <= myLife) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if life is in danger, always activate
|
||||
if ((myLife < 3) && (amount > myLife)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ((r.nextFloat() < .6667) && chance);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
final int hlife = opponent.getLife();
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
|
||||
int amount;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
|
||||
if (sourceName.equals("Eternity Vessel")
|
||||
&& (opponent.isCardInPlay("Vampire Hexmage") || (source.getCounters(CounterType.CHARGE) == 0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the Target is gaining life, target self.
|
||||
// if the Target is modifying how much life is gained, this needs to
|
||||
// be
|
||||
// handled better
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(opponent);
|
||||
} else {
|
||||
if ((amount > myLife) && (myLife <= 10)) {
|
||||
sa.getTargets().add(ai);
|
||||
} else if (hlife > amount) {
|
||||
sa.getTargets().add(opponent);
|
||||
} else if (amount > myLife) {
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,199 +1,199 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import forge.ai.*;
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ManaEffectAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkAiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkAiLogic(Player ai, SpellAbility sa, String aiLogic) {
|
||||
if ("ManaRitual".equals(aiLogic)) {
|
||||
return doManaRitualLogic(ai, sa);
|
||||
}
|
||||
return super.checkAiLogic(ai, sa, aiLogic);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
||||
if (!ph.is(PhaseType.MAIN2) || !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
}
|
||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler,
|
||||
* java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
|
||||
if (logic.startsWith("ManaRitual")) {
|
||||
return ph.is(PhaseType.MAIN2, ai) || ph.is(PhaseType.MAIN1, ai);
|
||||
}
|
||||
return super.checkPhaseRestrictions(ai, sa, ph, logic);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
if (sa.hasParam("AILogic")) {
|
||||
return true; // handled elsewhere, does not meet the standard requirements
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param aiPlayer
|
||||
* the AI player.
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Dark Ritual and other similar instants/sorceries that add mana to mana pool
|
||||
private boolean doManaRitualLogic(Player ai, SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
CardCollection manaSources = ComputerUtilMana.getAvailableManaSources(ai, true);
|
||||
int numManaSrcs = manaSources.size();
|
||||
int manaReceived = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa) : 1;
|
||||
manaReceived *= sa.getParam("Produced").split(" ").length;
|
||||
|
||||
int selfCost = sa.getPayCosts().getCostMana() != null ? sa.getPayCosts().getCostMana().getMana().getCMC() : 0;
|
||||
|
||||
String produced = sa.getParam("Produced");
|
||||
byte producedColor = produced.equals("Any") ? MagicColor.ALL_COLORS : MagicColor.fromName(produced);
|
||||
|
||||
if ("ChosenX".equals(sa.getParam("Amount"))
|
||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
||||
CounterType ctrType = CounterType.KI; // Petalmane Baku
|
||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
ctrType = ((CostRemoveCounter)part).counter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
manaReceived = host.getCounters(ctrType);
|
||||
}
|
||||
|
||||
int searchCMC = numManaSrcs - selfCost + manaReceived;
|
||||
|
||||
if ("X".equals(sa.getParam("Produced"))) {
|
||||
String x = host.getSVar("X");
|
||||
if ("Count$CardsInYourHand".equals(x) && host.isInZone(ZoneType.Hand)) {
|
||||
searchCMC--; // the spell in hand will be used
|
||||
} else if (x.startsWith("Count$NamedInAllYards") && host.isInZone(ZoneType.Graveyard)) {
|
||||
searchCMC--; // the spell in graveyard will be used
|
||||
}
|
||||
}
|
||||
|
||||
if (searchCMC <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String restrictValid = sa.hasParam("RestrictValid") ? sa.getParam("RestrictValid") : "Card";
|
||||
|
||||
CardCollection cardList = new CardCollection();
|
||||
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(ai.getCardsIn(ZoneType.Hand), ai);
|
||||
for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) {
|
||||
ManaCost cost = testSa.getPayCosts().getTotalMana();
|
||||
boolean canPayWithAvailableColors = cost.canBePaidWithAvaliable(ColorSet.fromNames(
|
||||
ComputerUtilCost.getAvailableManaColors(ai, (List<Card>)null)).getColor());
|
||||
|
||||
if (cost.getCMC() == 0 && cost.countX() == 0) {
|
||||
// no mana cost, no need to activate this SA then (additional mana not needed)
|
||||
continue;
|
||||
} else if (cost.getColorProfile() != 0 && !canPayWithAvailableColors) {
|
||||
// don't have one of each shard represented, may not be able to pay the cost
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ComputerUtilAbility.getAbilitySourceName(testSa).equals(ComputerUtilAbility.getAbilitySourceName(sa))
|
||||
|| testSa.hasParam("AINoRecursiveCheck")) {
|
||||
// prevent infinitely recursing mana ritual and other abilities with reentry
|
||||
continue;
|
||||
}
|
||||
|
||||
SpellAbility testSaNoCost = testSa.copyWithNoManaCost();
|
||||
if (testSaNoCost == null) {
|
||||
continue;
|
||||
}
|
||||
testSaNoCost.setActivatingPlayer(ai);
|
||||
if (((PlayerControllerAi)ai.getController()).getAi().canPlaySa(testSaNoCost) == AiPlayDecision.WillPlay) {
|
||||
if (testSa.getHostCard().isPermanent() && !testSa.getHostCard().hasKeyword("Haste")
|
||||
&& !ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
|
||||
// AI will waste a ritual in Main 1 unless the casted permanent is a haste creature
|
||||
continue;
|
||||
}
|
||||
if (testSa.getHostCard().isInstant()) {
|
||||
// AI is bad at choosing which instants are worth a Ritual
|
||||
continue;
|
||||
}
|
||||
|
||||
// the AI is willing to play the spell
|
||||
if (!cardList.contains(testSa.getHostCard())) {
|
||||
cardList.add(testSa.getHostCard());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CardCollection castableSpells = CardLists.filter(cardList,
|
||||
Arrays.asList(
|
||||
CardPredicates.restriction(restrictValid.split(","), ai, host, sa),
|
||||
CardPredicates.lessCMC(searchCMC),
|
||||
Predicates.or(CardPredicates.isColorless(), CardPredicates.isColor(producedColor))));
|
||||
|
||||
// TODO: this will probably still waste the card from time to time. Somehow improve detection of castable material.
|
||||
return castableSpells.size() > 0;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import forge.ai.*;
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ManaEffectAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkAiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkAiLogic(Player ai, SpellAbility sa, String aiLogic) {
|
||||
if ("ManaRitual".equals(aiLogic)) {
|
||||
return doManaRitualLogic(ai, sa);
|
||||
}
|
||||
return super.checkAiLogic(ai, sa, aiLogic);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
||||
if (!ph.is(PhaseType.MAIN2) || !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
}
|
||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler,
|
||||
* java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
|
||||
if (logic.startsWith("ManaRitual")) {
|
||||
return ph.is(PhaseType.MAIN2, ai) || ph.is(PhaseType.MAIN1, ai);
|
||||
}
|
||||
return super.checkPhaseRestrictions(ai, sa, ph, logic);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
if (sa.hasParam("AILogic")) {
|
||||
return true; // handled elsewhere, does not meet the standard requirements
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param aiPlayer
|
||||
* the AI player.
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Dark Ritual and other similar instants/sorceries that add mana to mana pool
|
||||
private boolean doManaRitualLogic(Player ai, SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
CardCollection manaSources = ComputerUtilMana.getAvailableManaSources(ai, true);
|
||||
int numManaSrcs = manaSources.size();
|
||||
int manaReceived = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa) : 1;
|
||||
manaReceived *= sa.getParam("Produced").split(" ").length;
|
||||
|
||||
int selfCost = sa.getPayCosts().getCostMana() != null ? sa.getPayCosts().getCostMana().getMana().getCMC() : 0;
|
||||
|
||||
String produced = sa.getParam("Produced");
|
||||
byte producedColor = produced.equals("Any") ? MagicColor.ALL_COLORS : MagicColor.fromName(produced);
|
||||
|
||||
if ("ChosenX".equals(sa.getParam("Amount"))
|
||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
||||
CounterType ctrType = CounterType.KI; // Petalmane Baku
|
||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
ctrType = ((CostRemoveCounter)part).counter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
manaReceived = host.getCounters(ctrType);
|
||||
}
|
||||
|
||||
int searchCMC = numManaSrcs - selfCost + manaReceived;
|
||||
|
||||
if ("X".equals(sa.getParam("Produced"))) {
|
||||
String x = host.getSVar("X");
|
||||
if ("Count$CardsInYourHand".equals(x) && host.isInZone(ZoneType.Hand)) {
|
||||
searchCMC--; // the spell in hand will be used
|
||||
} else if (x.startsWith("Count$NamedInAllYards") && host.isInZone(ZoneType.Graveyard)) {
|
||||
searchCMC--; // the spell in graveyard will be used
|
||||
}
|
||||
}
|
||||
|
||||
if (searchCMC <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String restrictValid = sa.hasParam("RestrictValid") ? sa.getParam("RestrictValid") : "Card";
|
||||
|
||||
CardCollection cardList = new CardCollection();
|
||||
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(ai.getCardsIn(ZoneType.Hand), ai);
|
||||
for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) {
|
||||
ManaCost cost = testSa.getPayCosts().getTotalMana();
|
||||
boolean canPayWithAvailableColors = cost.canBePaidWithAvaliable(ColorSet.fromNames(
|
||||
ComputerUtilCost.getAvailableManaColors(ai, (List<Card>)null)).getColor());
|
||||
|
||||
if (cost.getCMC() == 0 && cost.countX() == 0) {
|
||||
// no mana cost, no need to activate this SA then (additional mana not needed)
|
||||
continue;
|
||||
} else if (cost.getColorProfile() != 0 && !canPayWithAvailableColors) {
|
||||
// don't have one of each shard represented, may not be able to pay the cost
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ComputerUtilAbility.getAbilitySourceName(testSa).equals(ComputerUtilAbility.getAbilitySourceName(sa))
|
||||
|| testSa.hasParam("AINoRecursiveCheck")) {
|
||||
// prevent infinitely recursing mana ritual and other abilities with reentry
|
||||
continue;
|
||||
}
|
||||
|
||||
SpellAbility testSaNoCost = testSa.copyWithNoManaCost();
|
||||
if (testSaNoCost == null) {
|
||||
continue;
|
||||
}
|
||||
testSaNoCost.setActivatingPlayer(ai);
|
||||
if (((PlayerControllerAi)ai.getController()).getAi().canPlaySa(testSaNoCost) == AiPlayDecision.WillPlay) {
|
||||
if (testSa.getHostCard().isPermanent() && !testSa.getHostCard().hasKeyword("Haste")
|
||||
&& !ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
|
||||
// AI will waste a ritual in Main 1 unless the casted permanent is a haste creature
|
||||
continue;
|
||||
}
|
||||
if (testSa.getHostCard().isInstant()) {
|
||||
// AI is bad at choosing which instants are worth a Ritual
|
||||
continue;
|
||||
}
|
||||
|
||||
// the AI is willing to play the spell
|
||||
if (!cardList.contains(testSa.getHostCard())) {
|
||||
cardList.add(testSa.getHostCard());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CardCollection castableSpells = CardLists.filter(cardList,
|
||||
Arrays.asList(
|
||||
CardPredicates.restriction(restrictValid.split(","), ai, host, sa),
|
||||
CardPredicates.lessCMC(searchCMC),
|
||||
Predicates.or(CardPredicates.isColorless(), CardPredicates.isColor(producedColor))));
|
||||
|
||||
// TODO: this will probably still waste the card from time to time. Somehow improve detection of castable material.
|
||||
return castableSpells.size() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,156 +1,156 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementLayer;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by friarsol on 1/23/15.
|
||||
*/
|
||||
public class ManifestAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
// Manifest doesn't have any "Pay X to manifest X triggers"
|
||||
|
||||
return true;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility based on its phase restrictions
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final Card source = sa.getHostCard();
|
||||
// Only manifest things on your turn if sorcery speed, or would pump one of my creatures
|
||||
if (ph.isPlayerTurn(ai)) {
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases")
|
||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
boolean buff = false;
|
||||
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
|
||||
if ("Creature".equals(c.getSVar("BuffedBy"))) {
|
||||
buff = true;
|
||||
}
|
||||
}
|
||||
if (!buff) {
|
||||
return false;
|
||||
}
|
||||
} else if (!SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// try to ambush attackers
|
||||
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Handle either Manifest X cards, or Manifest 1 card and give it X P1P1s
|
||||
// Set PayX here to maximum value.
|
||||
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(x));
|
||||
if (x <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// do not manifest a card with X in its cost
|
||||
if (topCard.getManaCost().countX() > 0)
|
||||
return false;
|
||||
|
||||
// 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;
|
||||
}
|
||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
// Add blockers?
|
||||
return true;
|
||||
}
|
||||
if (sa.isAbility()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return MyRandom.getRandom().nextFloat() < .8;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementLayer;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by friarsol on 1/23/15.
|
||||
*/
|
||||
public class ManifestAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
// Manifest doesn't have any "Pay X to manifest X triggers"
|
||||
|
||||
return true;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility based on its phase restrictions
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final Card source = sa.getHostCard();
|
||||
// Only manifest things on your turn if sorcery speed, or would pump one of my creatures
|
||||
if (ph.isPlayerTurn(ai)) {
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases")
|
||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
boolean buff = false;
|
||||
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
|
||||
if ("Creature".equals(c.getSVar("BuffedBy"))) {
|
||||
buff = true;
|
||||
}
|
||||
}
|
||||
if (!buff) {
|
||||
return false;
|
||||
}
|
||||
} else if (!SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// try to ambush attackers
|
||||
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Handle either Manifest X cards, or Manifest 1 card and give it X P1P1s
|
||||
// Set PayX here to maximum value.
|
||||
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(x));
|
||||
if (x <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// do not manifest a card with X in its cost
|
||||
if (topCard.getManaCost().countX() > 0)
|
||||
return false;
|
||||
|
||||
// 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;
|
||||
}
|
||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
// Add blockers?
|
||||
return true;
|
||||
}
|
||||
if (sa.isAbility()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return MyRandom.getRandom().nextFloat() < .8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,238 +1,238 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
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;
|
||||
|
||||
public class MillAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
|
||||
if (aiLogic.equals("Main1")) {
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("EndOfOppTurn")) {
|
||||
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
|
||||
if (CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES).size() < 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
if ("ExileAndPlayUntilEOT".equals(sa.getParam("AILogic"))) {
|
||||
return ph.is(PhaseType.MAIN1) && ph.isPlayerTurn(ai); // try to maximize the chance of being able to play the card this turn
|
||||
} else if ("ExileAndPlayOrDealDamage".equals(sa.getParam("AILogic"))) {
|
||||
return (ph.is(PhaseType.MAIN1) || ph.is(PhaseType.MAIN2)) && ph.isPlayerTurn(ai); // Chandra, Torch of Defiance and similar
|
||||
}
|
||||
if ("You".equals(sa.getParam("Defined")) && !(!SpellAbilityAi.isSorcerySpeed(sa) && ph.is(PhaseType.END_OF_TURN)
|
||||
&& ph.getNextTurn().equals(ai))) {
|
||||
return false; // only self-mill at opponent EOT
|
||||
}
|
||||
if (sa.getHostCard().isCreature() && sa.getPayCosts().hasTapCost()) {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
/*
|
||||
* TODO:
|
||||
* - logic in targetAI looks dodgy
|
||||
* - decide whether to self-mill (eg. delirium, dredge, bad top card)
|
||||
* - interrupt opponent's top card (eg. Brainstorm, good top card)
|
||||
* - check for Laboratory Maniac effect (needs to check for actual
|
||||
* effect due to possibility of "lose abilities" effect)
|
||||
*/
|
||||
final Card source = sa.getHostCard();
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false; // prevents mill 0 infinite loop?
|
||||
}
|
||||
|
||||
if (("You".equals(sa.getParam("Defined")) || "Player".equals(sa.getParam("Defined")))
|
||||
&& ai.getCardsIn(ZoneType.Library).size() < 10) {
|
||||
return false; // prevent self and each player mill when library is small
|
||||
}
|
||||
|
||||
if (sa.getTargetRestrictions() != null && !targetAI(ai, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z"))
|
||||
&& source.getSVar("X").startsWith("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int cardsToDiscard = getNumToDiscard(ai, sa);
|
||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
if (cardsToDiscard <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
final Map<Player, Integer> list = Maps.newHashMap();
|
||||
for (final Player o : ai.getOpponents()) {
|
||||
if (!sa.canTarget(o)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// need to set as target for some calcuate
|
||||
sa.getTargets().add(o);
|
||||
final int numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
|
||||
sa.getTargets().remove(o);
|
||||
|
||||
// if it would mill none, try other one
|
||||
if (numCards <= 0) {
|
||||
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z")))
|
||||
{
|
||||
if (source.getSVar("X").startsWith("Count$xPaid")) {
|
||||
// Spell is PayX based
|
||||
} else if (source.getSVar("X").startsWith("Remembered$ChromaSource")) {
|
||||
// Cards like Sanity Grinding
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
final CardCollectionView pLibrary = o.getCardsIn(ZoneType.Library);
|
||||
if (pLibrary.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if that player can be miled, select this one.
|
||||
if (numCards >= pLibrary.size()) {
|
||||
sa.getTargets().add(o);
|
||||
return true;
|
||||
}
|
||||
|
||||
list.put(o, numCards);
|
||||
}
|
||||
|
||||
// can't target opponent?
|
||||
if (list.isEmpty()) {
|
||||
if (mandatory && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
return true;
|
||||
}
|
||||
// TODO Obscure case when you know what your top card is so you might?
|
||||
// want to mill yourself here
|
||||
return false;
|
||||
}
|
||||
|
||||
// select Player which would cause the most damage
|
||||
// JAVA 1.8 use Map.Entry.comparingByValue()
|
||||
Map.Entry<Player, Integer> max = Collections.max(list.entrySet(), new Comparator<Map.Entry<Player,Integer>>(){
|
||||
@Override
|
||||
public int compare(Map.Entry<Player, Integer> o1, Map.Entry<Player, Integer> o2) {
|
||||
return o1.getValue() - o2.getValue();
|
||||
}
|
||||
});
|
||||
|
||||
sa.getTargets().add(max.getKey());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return targetAI(aiPlayer, sa, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
if (!targetAI(aiPlayer, sa, mandatory)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int cardsToDiscard = getNumToDiscard(aiPlayer, sa);
|
||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* return num of cards to discard
|
||||
*/
|
||||
private int getNumToDiscard(final Player ai, final SpellAbility sa) {
|
||||
// need list of affected players
|
||||
List<Player> list = Lists.newArrayList();
|
||||
if (sa.usesTargeting()) {
|
||||
list.addAll(Lists.newArrayList(sa.getTargets().getTargetPlayers()));
|
||||
} else {
|
||||
list.addAll(AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa));
|
||||
}
|
||||
|
||||
// get targeted or defined Player with largest library
|
||||
// TODO in Java 8 find better way
|
||||
final Player m = Collections.max(list, new Comparator<Player>() {
|
||||
@Override
|
||||
public int compare(Player arg0, Player arg1) {
|
||||
return arg0.getCardsIn(ZoneType.Library).size() - arg1.getCardsIn(ZoneType.Library).size();
|
||||
}
|
||||
});
|
||||
|
||||
int cardsToDiscard = m.getCardsIn(ZoneType.Library).size();
|
||||
|
||||
// if ai is in affected list too, try to not mill himself
|
||||
if (list.contains(ai)) {
|
||||
cardsToDiscard = Math.min(ai.getCardsIn(ZoneType.Library).size() - 5, cardsToDiscard);
|
||||
}
|
||||
|
||||
return Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), cardsToDiscard);
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
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;
|
||||
|
||||
public class MillAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
|
||||
if (aiLogic.equals("Main1")) {
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic.equals("EndOfOppTurn")) {
|
||||
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
|
||||
if (CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES).size() < 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
if ("ExileAndPlayUntilEOT".equals(sa.getParam("AILogic"))) {
|
||||
return ph.is(PhaseType.MAIN1) && ph.isPlayerTurn(ai); // try to maximize the chance of being able to play the card this turn
|
||||
} else if ("ExileAndPlayOrDealDamage".equals(sa.getParam("AILogic"))) {
|
||||
return (ph.is(PhaseType.MAIN1) || ph.is(PhaseType.MAIN2)) && ph.isPlayerTurn(ai); // Chandra, Torch of Defiance and similar
|
||||
}
|
||||
if ("You".equals(sa.getParam("Defined")) && !(!SpellAbilityAi.isSorcerySpeed(sa) && ph.is(PhaseType.END_OF_TURN)
|
||||
&& ph.getNextTurn().equals(ai))) {
|
||||
return false; // only self-mill at opponent EOT
|
||||
}
|
||||
if (sa.getHostCard().isCreature() && sa.getPayCosts().hasTapCost()) {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
/*
|
||||
* TODO:
|
||||
* - logic in targetAI looks dodgy
|
||||
* - decide whether to self-mill (eg. delirium, dredge, bad top card)
|
||||
* - interrupt opponent's top card (eg. Brainstorm, good top card)
|
||||
* - check for Laboratory Maniac effect (needs to check for actual
|
||||
* effect due to possibility of "lose abilities" effect)
|
||||
*/
|
||||
final Card source = sa.getHostCard();
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false; // prevents mill 0 infinite loop?
|
||||
}
|
||||
|
||||
if (("You".equals(sa.getParam("Defined")) || "Player".equals(sa.getParam("Defined")))
|
||||
&& ai.getCardsIn(ZoneType.Library).size() < 10) {
|
||||
return false; // prevent self and each player mill when library is small
|
||||
}
|
||||
|
||||
if (sa.getTargetRestrictions() != null && !targetAI(ai, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z"))
|
||||
&& source.getSVar("X").startsWith("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int cardsToDiscard = getNumToDiscard(ai, sa);
|
||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
if (cardsToDiscard <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
final Map<Player, Integer> list = Maps.newHashMap();
|
||||
for (final Player o : ai.getOpponents()) {
|
||||
if (!sa.canTarget(o)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// need to set as target for some calcuate
|
||||
sa.getTargets().add(o);
|
||||
final int numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
|
||||
sa.getTargets().remove(o);
|
||||
|
||||
// if it would mill none, try other one
|
||||
if (numCards <= 0) {
|
||||
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z")))
|
||||
{
|
||||
if (source.getSVar("X").startsWith("Count$xPaid")) {
|
||||
// Spell is PayX based
|
||||
} else if (source.getSVar("X").startsWith("Remembered$ChromaSource")) {
|
||||
// Cards like Sanity Grinding
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
final CardCollectionView pLibrary = o.getCardsIn(ZoneType.Library);
|
||||
if (pLibrary.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if that player can be miled, select this one.
|
||||
if (numCards >= pLibrary.size()) {
|
||||
sa.getTargets().add(o);
|
||||
return true;
|
||||
}
|
||||
|
||||
list.put(o, numCards);
|
||||
}
|
||||
|
||||
// can't target opponent?
|
||||
if (list.isEmpty()) {
|
||||
if (mandatory && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
return true;
|
||||
}
|
||||
// TODO Obscure case when you know what your top card is so you might?
|
||||
// want to mill yourself here
|
||||
return false;
|
||||
}
|
||||
|
||||
// select Player which would cause the most damage
|
||||
// JAVA 1.8 use Map.Entry.comparingByValue()
|
||||
Map.Entry<Player, Integer> max = Collections.max(list.entrySet(), new Comparator<Map.Entry<Player,Integer>>(){
|
||||
@Override
|
||||
public int compare(Map.Entry<Player, Integer> o1, Map.Entry<Player, Integer> o2) {
|
||||
return o1.getValue() - o2.getValue();
|
||||
}
|
||||
});
|
||||
|
||||
sa.getTargets().add(max.getKey());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return targetAI(aiPlayer, sa, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
if (!targetAI(aiPlayer, sa, mandatory)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int cardsToDiscard = getNumToDiscard(aiPlayer, sa);
|
||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* return num of cards to discard
|
||||
*/
|
||||
private int getNumToDiscard(final Player ai, final SpellAbility sa) {
|
||||
// need list of affected players
|
||||
List<Player> list = Lists.newArrayList();
|
||||
if (sa.usesTargeting()) {
|
||||
list.addAll(Lists.newArrayList(sa.getTargets().getTargetPlayers()));
|
||||
} else {
|
||||
list.addAll(AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa));
|
||||
}
|
||||
|
||||
// get targeted or defined Player with largest library
|
||||
// TODO in Java 8 find better way
|
||||
final Player m = Collections.max(list, new Comparator<Player>() {
|
||||
@Override
|
||||
public int compare(Player arg0, Player arg1) {
|
||||
return arg0.getCardsIn(ZoneType.Library).size() - arg1.getCardsIn(ZoneType.Library).size();
|
||||
}
|
||||
});
|
||||
|
||||
int cardsToDiscard = m.getCardsIn(ZoneType.Library).size();
|
||||
|
||||
// if ai is in affected list too, try to not mill himself
|
||||
if (list.contains(ai)) {
|
||||
cardsToDiscard = Math.min(ai.getCardsIn(ZoneType.Library).size() - 5, cardsToDiscard);
|
||||
}
|
||||
|
||||
return Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), cardsToDiscard);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class MustAttackAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
// disabled for the AI for now. Only for Gideon Jura at this time.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
// AI should only activate this during Human's turn
|
||||
// TODO - implement AI
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
boolean chance;
|
||||
|
||||
// TODO - implement AI
|
||||
chance = false;
|
||||
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class MustAttackAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
// disabled for the AI for now. Only for Gideon Jura at this time.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
// AI should only activate this during Human's turn
|
||||
// TODO - implement AI
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
boolean chance;
|
||||
|
||||
// TODO - implement AI
|
||||
chance = false;
|
||||
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,101 +1,101 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MustBlockAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
// disabled for the AI until he/she can make decisions about who to make
|
||||
// block
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
|
||||
// only use on creatures that can attack
|
||||
if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Card attacker = null;
|
||||
if (sa.hasParam("DefinedAttacker")) {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa);
|
||||
if (cards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
attacker = cards.get(0);
|
||||
}
|
||||
|
||||
if (attacker == null) {
|
||||
attacker = source;
|
||||
}
|
||||
|
||||
final Card definedAttacker = attacker;
|
||||
|
||||
boolean chance = false;
|
||||
|
||||
if (abTgt != null) {
|
||||
List<Card> list = CardLists.filter(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source, sa);
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
boolean tapped = c.isTapped();
|
||||
c.setTapped(false);
|
||||
if (!CombatUtil.canBlock(definedAttacker, c)) {
|
||||
return false;
|
||||
}
|
||||
if (ComputerUtilCombat.canDestroyAttacker(ai, definedAttacker, c, null, false)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCombat.canDestroyBlocker(ai, c, definedAttacker, null, false)) {
|
||||
return false;
|
||||
}
|
||||
c.setTapped(tapped);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
||||
if (blocker == null) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(blocker);
|
||||
chance = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MustBlockAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
// disabled for the AI until he/she can make decisions about who to make
|
||||
// block
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
|
||||
// only use on creatures that can attack
|
||||
if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Card attacker = null;
|
||||
if (sa.hasParam("DefinedAttacker")) {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa);
|
||||
if (cards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
attacker = cards.get(0);
|
||||
}
|
||||
|
||||
if (attacker == null) {
|
||||
attacker = source;
|
||||
}
|
||||
|
||||
final Card definedAttacker = attacker;
|
||||
|
||||
boolean chance = false;
|
||||
|
||||
if (abTgt != null) {
|
||||
List<Card> list = CardLists.filter(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source, sa);
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
boolean tapped = c.isTapped();
|
||||
c.setTapped(false);
|
||||
if (!CombatUtil.canBlock(definedAttacker, c)) {
|
||||
return false;
|
||||
}
|
||||
if (ComputerUtilCombat.canDestroyAttacker(ai, definedAttacker, c, null, false)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCombat.canDestroyBlocker(ai, c, definedAttacker, null, false)) {
|
||||
return false;
|
||||
}
|
||||
c.setTapped(tapped);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
||||
if (blocker == null) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(blocker);
|
||||
chance = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilityStatic;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class PeekAndRevealAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
if (sa instanceof AbilityStatic) {
|
||||
return false;
|
||||
}
|
||||
if ("Main2".equals(sa.getParam("AILogic"))) {
|
||||
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;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
AbilitySub subAb = sa.getSubAbility();
|
||||
return subAb != null && SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(player, subAb);
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilityStatic;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class PeekAndRevealAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
if (sa instanceof AbilityStatic) {
|
||||
return false;
|
||||
}
|
||||
if ("Main2".equals(sa.getParam("AILogic"))) {
|
||||
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;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
AbilitySub subAb = sa.getSubAbility();
|
||||
return subAb != null && SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(player, subAb);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,132 +1,132 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
/**
|
||||
* AbilityFactory for Creature Spells.
|
||||
*
|
||||
*/
|
||||
public class PermanentCreatureAi extends PermanentAi {
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility with the specified AiLogic
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
final Game game = ai.getGame();
|
||||
|
||||
if ("Never".equals(aiLogic)) {
|
||||
return false;
|
||||
} else if ("ZeroToughness".equals(aiLogic)) {
|
||||
// If Creature has Zero Toughness, make sure some static ability is in play
|
||||
// That will grant a toughness bonus
|
||||
|
||||
final Card copy = CardUtil.getLKICopy(sa.getHostCard());
|
||||
|
||||
ComputerUtilCard.applyStaticContPT(game, copy, null);
|
||||
|
||||
if (copy.getNetToughness() <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility based on its phase restrictions
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
// FRF Dash Keyword
|
||||
if (sa.isDash()) {
|
||||
//only checks that the dashed creature will attack
|
||||
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
if (ai.hasKeyword("Skip your next combat phase."))
|
||||
return false;
|
||||
if (ComputerUtilCost.canPayCost(sa.getHostCard().getSpellPermanent(), ai)) {
|
||||
//do not dash if creature can be played normally
|
||||
return false;
|
||||
}
|
||||
Card dashed = CardUtil.getLKICopy(sa.getHostCard());
|
||||
dashed.setSickness(false);
|
||||
return ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, dashed);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent the computer from summoning Ball Lightning type creatures
|
||||
// after attacking
|
||||
if (card.hasSVar("EndOfTurnLeavePlay")
|
||||
&& (!ph.isPlayerTurn(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
|| ai.hasKeyword("Skip your next combat phase."))) {
|
||||
// AiPlayDecision.AnotherTime
|
||||
return false;
|
||||
}
|
||||
|
||||
// save cards with flash for surprise blocking
|
||||
if (card.hasKeyword("Flash")
|
||||
&& (ai.isUnlimitedHandSize() || ai.getCardsIn(ZoneType.Hand).size() <= ai.getMaxHandSize()
|
||||
|| ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& ai.getManaPool().totalMana() <= 0
|
||||
&& (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS))
|
||||
&& (!card.hasETBTrigger(true) || card.hasSVar("AmbushAI")) && game.getStack().isEmpty()
|
||||
&& !ComputerUtil.castPermanentInMain1(ai, sa)) {
|
||||
// AiPlayDecision.AnotherTime;
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
|
||||
if (!super.checkApiLogic(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card card = sa.getHostCard();
|
||||
final ManaCost mana = card.getManaCost();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
/*
|
||||
* Checks if the creature will have non-positive toughness after
|
||||
* applying static effects. Exceptions: 1. has "etbCounter" keyword (eg.
|
||||
* Endless One) 2. paid non-zero for X cost 3. has ETB trigger 4. has
|
||||
* ETB replacement 5. has NoZeroToughnessAI svar (eg. Veteran Warleader)
|
||||
*
|
||||
* 1. and 2. should probably be merged and applied on the card after
|
||||
* checking for effects like Doubling Season for getNetToughness to see
|
||||
* the true value. 3. currently allows the AI to suicide creatures as
|
||||
* long as it has an ETB. Maybe it should check if said ETB is actually
|
||||
* worth it. Not sure what 4. is for. 5. needs to be updated to ensure
|
||||
* that the net toughness is still positive after static effects.
|
||||
*/
|
||||
final Card copy = CardUtil.getLKICopy(sa.getHostCard());
|
||||
ComputerUtilCard.applyStaticContPT(game, copy, null);
|
||||
if (copy.getNetToughness() <= 0 && !copy.hasStartOfKeyword("etbCounter") && mana.countX() == 0
|
||||
&& !copy.hasETBTrigger(false) && !copy.hasETBReplacement() && !copy.hasSVar("NoZeroToughnessAI")) {
|
||||
// AiPlayDecision.WouldBecomeZeroToughnessCreature
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
/**
|
||||
* AbilityFactory for Creature Spells.
|
||||
*
|
||||
*/
|
||||
public class PermanentCreatureAi extends PermanentAi {
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility with the specified AiLogic
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
final Game game = ai.getGame();
|
||||
|
||||
if ("Never".equals(aiLogic)) {
|
||||
return false;
|
||||
} else if ("ZeroToughness".equals(aiLogic)) {
|
||||
// If Creature has Zero Toughness, make sure some static ability is in play
|
||||
// That will grant a toughness bonus
|
||||
|
||||
final Card copy = CardUtil.getLKICopy(sa.getHostCard());
|
||||
|
||||
ComputerUtilCard.applyStaticContPT(game, copy, null);
|
||||
|
||||
if (copy.getNetToughness() <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility based on its phase restrictions
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
// FRF Dash Keyword
|
||||
if (sa.isDash()) {
|
||||
//only checks that the dashed creature will attack
|
||||
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
if (ai.hasKeyword("Skip your next combat phase."))
|
||||
return false;
|
||||
if (ComputerUtilCost.canPayCost(sa.getHostCard().getSpellPermanent(), ai)) {
|
||||
//do not dash if creature can be played normally
|
||||
return false;
|
||||
}
|
||||
Card dashed = CardUtil.getLKICopy(sa.getHostCard());
|
||||
dashed.setSickness(false);
|
||||
return ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, dashed);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent the computer from summoning Ball Lightning type creatures
|
||||
// after attacking
|
||||
if (card.hasSVar("EndOfTurnLeavePlay")
|
||||
&& (!ph.isPlayerTurn(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
|| ai.hasKeyword("Skip your next combat phase."))) {
|
||||
// AiPlayDecision.AnotherTime
|
||||
return false;
|
||||
}
|
||||
|
||||
// save cards with flash for surprise blocking
|
||||
if (card.hasKeyword("Flash")
|
||||
&& (ai.isUnlimitedHandSize() || ai.getCardsIn(ZoneType.Hand).size() <= ai.getMaxHandSize()
|
||||
|| ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& ai.getManaPool().totalMana() <= 0
|
||||
&& (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS))
|
||||
&& (!card.hasETBTrigger(true) || card.hasSVar("AmbushAI")) && game.getStack().isEmpty()
|
||||
&& !ComputerUtil.castPermanentInMain1(ai, sa)) {
|
||||
// AiPlayDecision.AnotherTime;
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
|
||||
if (!super.checkApiLogic(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card card = sa.getHostCard();
|
||||
final ManaCost mana = card.getManaCost();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
/*
|
||||
* Checks if the creature will have non-positive toughness after
|
||||
* applying static effects. Exceptions: 1. has "etbCounter" keyword (eg.
|
||||
* Endless One) 2. paid non-zero for X cost 3. has ETB trigger 4. has
|
||||
* ETB replacement 5. has NoZeroToughnessAI svar (eg. Veteran Warleader)
|
||||
*
|
||||
* 1. and 2. should probably be merged and applied on the card after
|
||||
* checking for effects like Doubling Season for getNetToughness to see
|
||||
* the true value. 3. currently allows the AI to suicide creatures as
|
||||
* long as it has an ETB. Maybe it should check if said ETB is actually
|
||||
* worth it. Not sure what 4. is for. 5. needs to be updated to ensure
|
||||
* that the net toughness is still positive after static effects.
|
||||
*/
|
||||
final Card copy = CardUtil.getLKICopy(sa.getHostCard());
|
||||
ComputerUtilCard.applyStaticContPT(game, copy, null);
|
||||
if (copy.getNetToughness() <= 0 && !copy.hasStartOfKeyword("etbCounter") && mana.countX() == 0
|
||||
&& !copy.hasETBTrigger(false) && !copy.hasETBReplacement() && !copy.hasSVar("NoZeroToughnessAI")) {
|
||||
// AiPlayDecision.WouldBecomeZeroToughnessCreature
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
/**
|
||||
* AbilityFactory for Creature Spells.
|
||||
*
|
||||
*/
|
||||
public class PermanentNoncreatureAi extends PermanentAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
if ("Never".equals(aiLogic) || "DontCast".equals(aiLogic)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rest of the logic not covered by the canPlayAI template is defined
|
||||
* here
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
|
||||
if (!super.checkApiLogic(ai, sa))
|
||||
return false;
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final Game game = ai.getGame();
|
||||
|
||||
// Check for valid targets before casting
|
||||
if (host.hasSVar("OblivionRing")) {
|
||||
SpellAbility effectExile = AbilityFactory.getAbility(host.getSVar("TrigExile"), host);
|
||||
final ZoneType origin = ZoneType.listValueOf(effectExile.getParam("Origin")).get(0);
|
||||
final TargetRestrictions tgt = effectExile.getTargetRestrictions();
|
||||
final CardCollection list = CardLists.getValidCards(game.getCardsIn(origin), tgt.getValidTgts(), ai, host,
|
||||
effectExile);
|
||||
CardCollection targets = CardLists.getTargetableCards(list, sa);
|
||||
if (sourceName.equals("Suspension Field")
|
||||
|| sourceName.equals("Detention Sphere")) {
|
||||
// existing "exile until leaves" enchantments only target
|
||||
// opponent's permanents
|
||||
// TODO: consider replacing the condition with host.hasSVar("OblivionRing")
|
||||
targets = CardLists.filterControlledBy(targets, ai.getOpponents());
|
||||
}
|
||||
if (targets.isEmpty()) {
|
||||
// AiPlayDecision.AnotherTime
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
/**
|
||||
* AbilityFactory for Creature Spells.
|
||||
*
|
||||
*/
|
||||
public class PermanentNoncreatureAi extends PermanentAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
if ("Never".equals(aiLogic) || "DontCast".equals(aiLogic)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rest of the logic not covered by the canPlayAI template is defined
|
||||
* here
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
|
||||
if (!super.checkApiLogic(ai, sa))
|
||||
return false;
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final Game game = ai.getGame();
|
||||
|
||||
// Check for valid targets before casting
|
||||
if (host.hasSVar("OblivionRing")) {
|
||||
SpellAbility effectExile = AbilityFactory.getAbility(host.getSVar("TrigExile"), host);
|
||||
final ZoneType origin = ZoneType.listValueOf(effectExile.getParam("Origin")).get(0);
|
||||
final TargetRestrictions tgt = effectExile.getTargetRestrictions();
|
||||
final CardCollection list = CardLists.getValidCards(game.getCardsIn(origin), tgt.getValidTgts(), ai, host,
|
||||
effectExile);
|
||||
CardCollection targets = CardLists.getTargetableCards(list, sa);
|
||||
if (sourceName.equals("Suspension Field")
|
||||
|| sourceName.equals("Detention Sphere")) {
|
||||
// existing "exile until leaves" enchantments only target
|
||||
// opponent's permanents
|
||||
// TODO: consider replacing the condition with host.hasSVar("OblivionRing")
|
||||
targets = CardLists.filterControlledBy(targets, ai.getOpponents());
|
||||
}
|
||||
if (targets.isEmpty()) {
|
||||
// AiPlayDecision.AnotherTime
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,132 +1,132 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class PhasesAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
// This still needs to be fleshed out
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
List<Card> tgtCards;
|
||||
if (tgt == null) {
|
||||
tgtCards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||
if (tgtCards.contains(source)) {
|
||||
// Protect it from something
|
||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(aiPlayer, null, true).contains(source);
|
||||
if (isThreatened) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Card def = tgtCards.get(0);
|
||||
// Phase this out if it might attack me, or before it can be
|
||||
// declared as a blocker
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
if (!phasesPrefTargeting(tgt, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if (tgt == null) {
|
||||
return mandatory;
|
||||
}
|
||||
|
||||
if (phasesPrefTargeting(tgt, sa, mandatory)) {
|
||||
return true;
|
||||
} else if (mandatory) {
|
||||
// not enough preferred targets, but mandatory so keep going:
|
||||
return phasesUnpreferredTargeting(aiPlayer.getGame(), sa, mandatory);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
boolean randomReturn = true;
|
||||
|
||||
if (tgt == null) {
|
||||
|
||||
} else {
|
||||
if (!phasesPrefTargeting(tgt, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
private boolean phasesPrefTargeting(final TargetRestrictions tgt, final SpellAbility sa,
|
||||
final boolean mandatory) {
|
||||
// Card source = sa.getHostCard();
|
||||
|
||||
// List<Card> phaseList =
|
||||
// AllZoneUtil.getCardsIn(Zone.Battlefield).getTargetableCards(source)
|
||||
// .getValidCards(tgt.getValidTgts(), source.getController(), source);
|
||||
|
||||
// List<Card> aiPhaseList =
|
||||
// phaseList.getController(AllZone.getComputerPlayer());
|
||||
|
||||
// If Something in the Phase List might die from a bad combat, or a
|
||||
// spell on the stack save it
|
||||
|
||||
// List<Card> humanPhaseList =
|
||||
// phaseList.getController(AllZone.getHumanPlayer());
|
||||
|
||||
// If something in the Human List is causing issues, phase it out
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean phasesUnpreferredTargeting(final Game game, final SpellAbility sa, final boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.getTargetableCards(CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa), sa);
|
||||
|
||||
// in general, if it's our own creature, choose the weakest one, if it's the opponent's creature,
|
||||
// choose the strongest one
|
||||
if (!list.isEmpty()) {
|
||||
CardCollectionView oppList = CardLists.filter(list, Predicates.not(CardPredicates.isController(source.getController())));
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(!oppList.isEmpty() ? ComputerUtilCard.getBestAI(oppList) : ComputerUtilCard.getWorstAI(list));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class PhasesAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
// This still needs to be fleshed out
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
List<Card> tgtCards;
|
||||
if (tgt == null) {
|
||||
tgtCards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||
if (tgtCards.contains(source)) {
|
||||
// Protect it from something
|
||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(aiPlayer, null, true).contains(source);
|
||||
if (isThreatened) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Card def = tgtCards.get(0);
|
||||
// Phase this out if it might attack me, or before it can be
|
||||
// declared as a blocker
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
if (!phasesPrefTargeting(tgt, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if (tgt == null) {
|
||||
return mandatory;
|
||||
}
|
||||
|
||||
if (phasesPrefTargeting(tgt, sa, mandatory)) {
|
||||
return true;
|
||||
} else if (mandatory) {
|
||||
// not enough preferred targets, but mandatory so keep going:
|
||||
return phasesUnpreferredTargeting(aiPlayer.getGame(), sa, mandatory);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
boolean randomReturn = true;
|
||||
|
||||
if (tgt == null) {
|
||||
|
||||
} else {
|
||||
if (!phasesPrefTargeting(tgt, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
private boolean phasesPrefTargeting(final TargetRestrictions tgt, final SpellAbility sa,
|
||||
final boolean mandatory) {
|
||||
// Card source = sa.getHostCard();
|
||||
|
||||
// List<Card> phaseList =
|
||||
// AllZoneUtil.getCardsIn(Zone.Battlefield).getTargetableCards(source)
|
||||
// .getValidCards(tgt.getValidTgts(), source.getController(), source);
|
||||
|
||||
// List<Card> aiPhaseList =
|
||||
// phaseList.getController(AllZone.getComputerPlayer());
|
||||
|
||||
// If Something in the Phase List might die from a bad combat, or a
|
||||
// spell on the stack save it
|
||||
|
||||
// List<Card> humanPhaseList =
|
||||
// phaseList.getController(AllZone.getHumanPlayer());
|
||||
|
||||
// If something in the Human List is causing issues, phase it out
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean phasesUnpreferredTargeting(final Game game, final SpellAbility sa, final boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.getTargetableCards(CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa), sa);
|
||||
|
||||
// in general, if it's our own creature, choose the weakest one, if it's the opponent's creature,
|
||||
// choose the strongest one
|
||||
if (!list.isEmpty()) {
|
||||
CardCollectionView oppList = CardLists.filter(list, Predicates.not(CardPredicates.isController(source.getController())));
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(!oppList.isEmpty() ? ComputerUtilCard.getBestAI(oppList) : ComputerUtilCard.getWorstAI(list));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,142 +1,142 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.*;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardTypeView;
|
||||
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.cost.Cost;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.Spell;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PlayAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
final String logic = sa.hasParam("AILogic") ? sa.getParam("AILogic") : "";
|
||||
|
||||
final Game game = ai.getGame();
|
||||
final Card source = sa.getHostCard();
|
||||
// don't use this as a response (ReplaySpell logic is an exception, might be called from a subability
|
||||
// while the trigger is on stack)
|
||||
if (!game.getStack().isEmpty() && !"ReplaySpell".equals(logic)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false; // prevent infinite loop
|
||||
}
|
||||
|
||||
CardCollection cards = null;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
ZoneType zone = tgt.getZone().get(0);
|
||||
cards = CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), ai, source, sa);
|
||||
if (cards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (!sa.hasParam("Valid")) {
|
||||
cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
if (cards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ("ReplaySpell".equals(logic)) {
|
||||
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
|
||||
}
|
||||
|
||||
if (source != null && source.hasKeyword("Hideaway") && source.hasRemembered()) {
|
||||
// AI is not very good at playing non-permanent spells this way, at least yet
|
||||
// (might be possible to enable it for Sorceries in Main1/Main2 if target is available,
|
||||
// but definitely not for most Instants)
|
||||
Card rem = (Card) source.getFirstRemembered();
|
||||
CardTypeView t = rem.getState(CardStateName.Original).getType();
|
||||
|
||||
return t.isPermanent() && !t.isLand();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* doTriggerAINoCost
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
// as called from PlayEffect:173
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options,
|
||||
final boolean isOptional,
|
||||
Player targetedPlayer) {
|
||||
List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
for (SpellAbility s : c.getBasicSpells(c.getState(CardStateName.Original))) {
|
||||
Spell spell = (Spell) s;
|
||||
s.setActivatingPlayer(ai);
|
||||
// timing restrictions still apply
|
||||
if (!s.getRestrictions().checkTimingRestrictions(c, s))
|
||||
continue;
|
||||
if (sa.hasParam("WithoutManaCost")) {
|
||||
spell = (Spell) spell.copyWithNoManaCost();
|
||||
} else if (sa.hasParam("PlayCost")) {
|
||||
Cost abCost;
|
||||
if ("ManaCost".equals(sa.getParam("PlayCost"))) {
|
||||
abCost = new Cost(c.getManaCost(), false);
|
||||
} else {
|
||||
abCost = new Cost(sa.getParam("PlayCost"), false);
|
||||
}
|
||||
|
||||
spell = (Spell) spell.copyWithDefinedCost(abCost);
|
||||
}
|
||||
if( AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !isOptional, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return ComputerUtilCard.getBestAI(tgtCards);
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.*;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardTypeView;
|
||||
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.cost.Cost;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.Spell;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PlayAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
final String logic = sa.hasParam("AILogic") ? sa.getParam("AILogic") : "";
|
||||
|
||||
final Game game = ai.getGame();
|
||||
final Card source = sa.getHostCard();
|
||||
// don't use this as a response (ReplaySpell logic is an exception, might be called from a subability
|
||||
// while the trigger is on stack)
|
||||
if (!game.getStack().isEmpty() && !"ReplaySpell".equals(logic)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false; // prevent infinite loop
|
||||
}
|
||||
|
||||
CardCollection cards = null;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
ZoneType zone = tgt.getZone().get(0);
|
||||
cards = CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), ai, source, sa);
|
||||
if (cards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (!sa.hasParam("Valid")) {
|
||||
cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
if (cards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ("ReplaySpell".equals(logic)) {
|
||||
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
|
||||
}
|
||||
|
||||
if (source != null && source.hasKeyword("Hideaway") && source.hasRemembered()) {
|
||||
// AI is not very good at playing non-permanent spells this way, at least yet
|
||||
// (might be possible to enable it for Sorceries in Main1/Main2 if target is available,
|
||||
// but definitely not for most Instants)
|
||||
Card rem = (Card) source.getFirstRemembered();
|
||||
CardTypeView t = rem.getState(CardStateName.Original).getType();
|
||||
|
||||
return t.isPermanent() && !t.isLand();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* doTriggerAINoCost
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
// as called from PlayEffect:173
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options,
|
||||
final boolean isOptional,
|
||||
Player targetedPlayer) {
|
||||
List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
for (SpellAbility s : c.getBasicSpells(c.getState(CardStateName.Original))) {
|
||||
Spell spell = (Spell) s;
|
||||
s.setActivatingPlayer(ai);
|
||||
// timing restrictions still apply
|
||||
if (!s.getRestrictions().checkTimingRestrictions(c, s))
|
||||
continue;
|
||||
if (sa.hasParam("WithoutManaCost")) {
|
||||
spell = (Spell) spell.copyWithNoManaCost();
|
||||
} else if (sa.hasParam("PlayCost")) {
|
||||
Cost abCost;
|
||||
if ("ManaCost".equals(sa.getParam("PlayCost"))) {
|
||||
abCost = new Cost(c.getManaCost(), false);
|
||||
} else {
|
||||
abCost = new Cost(sa.getParam("PlayCost"), false);
|
||||
}
|
||||
|
||||
spell = (Spell) spell.copyWithDefinedCost(abCost);
|
||||
}
|
||||
if( AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !isOptional, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return ComputerUtilCard.getBestAI(tgtCards);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,163 +1,163 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class PoisonAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
return tgtPlayer(ai, sa, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#doTriggerAINoCost(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
return tgtPlayer(ai, sa, mandatory);
|
||||
} else if (mandatory || !ai.canReceiveCounters(CounterType.POISON)) {
|
||||
// mandatory or ai is uneffected
|
||||
return true;
|
||||
} else {
|
||||
// currently there are no optional Trigger
|
||||
final PlayerCollection players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"),
|
||||
sa);
|
||||
if (players.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// not affected, don't care
|
||||
if (!players.contains(ai)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Player max = players.max(PlayerPredicates.compareByPoison());
|
||||
if (ai.getPoisonCounters() == max.getPoisonCounters()) {
|
||||
// ai is one of the max
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean tgtPlayer(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
PlayerCollection tgts = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
if (!tgts.isEmpty()) {
|
||||
// try to select a opponent that can lose through poison counters
|
||||
PlayerCollection betterTgts = tgts.filter(new Predicate<Player>() {
|
||||
@Override
|
||||
public boolean apply(Player input) {
|
||||
if (input.cantLose()) {
|
||||
return false;
|
||||
} else if (!input.canReceiveCounters(CounterType.POISON)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (!betterTgts.isEmpty()) {
|
||||
tgts = betterTgts;
|
||||
} else if (mandatory) {
|
||||
// no better choice but better than hiting himself
|
||||
sa.getTargets().add(tgts.getFirst());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// no opponent can be killed with that
|
||||
if (tgts.isEmpty()) {
|
||||
if (mandatory) {
|
||||
// AI is uneffected
|
||||
if (ai.canBeTargetedBy(sa) && ai.canReceiveCounters(CounterType.POISON)) {
|
||||
sa.getTargets().add(ai);
|
||||
return true;
|
||||
}
|
||||
// need to target something, try to target allies
|
||||
PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
if (!allies.isEmpty()) {
|
||||
// some ally would be uneffected
|
||||
PlayerCollection betterAllies = allies.filter(new Predicate<Player>() {
|
||||
@Override
|
||||
public boolean apply(Player input) {
|
||||
if (input.cantLose()) {
|
||||
return true;
|
||||
}
|
||||
if (!input.canReceiveCounters(CounterType.POISON)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
if (!betterAllies.isEmpty()) {
|
||||
allies = betterAllies;
|
||||
}
|
||||
|
||||
Player min = allies.min(PlayerPredicates.compareByPoison());
|
||||
sa.getTargets().add(min);
|
||||
return true;
|
||||
} else if (ai.canBeTargetedBy(sa)) {
|
||||
// need to target himself
|
||||
sa.getTargets().add(ai);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// find player with max poison to kill
|
||||
Player max = tgts.max(PlayerPredicates.compareByPoison());
|
||||
sa.getTargets().add(max);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class PoisonAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
return tgtPlayer(ai, sa, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.ai.SpellAbilityAi#doTriggerAINoCost(forge.game.player.Player,
|
||||
* forge.game.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
return tgtPlayer(ai, sa, mandatory);
|
||||
} else if (mandatory || !ai.canReceiveCounters(CounterType.POISON)) {
|
||||
// mandatory or ai is uneffected
|
||||
return true;
|
||||
} else {
|
||||
// currently there are no optional Trigger
|
||||
final PlayerCollection players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"),
|
||||
sa);
|
||||
if (players.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// not affected, don't care
|
||||
if (!players.contains(ai)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Player max = players.max(PlayerPredicates.compareByPoison());
|
||||
if (ai.getPoisonCounters() == max.getPoisonCounters()) {
|
||||
// ai is one of the max
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean tgtPlayer(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
PlayerCollection tgts = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
if (!tgts.isEmpty()) {
|
||||
// try to select a opponent that can lose through poison counters
|
||||
PlayerCollection betterTgts = tgts.filter(new Predicate<Player>() {
|
||||
@Override
|
||||
public boolean apply(Player input) {
|
||||
if (input.cantLose()) {
|
||||
return false;
|
||||
} else if (!input.canReceiveCounters(CounterType.POISON)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (!betterTgts.isEmpty()) {
|
||||
tgts = betterTgts;
|
||||
} else if (mandatory) {
|
||||
// no better choice but better than hiting himself
|
||||
sa.getTargets().add(tgts.getFirst());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// no opponent can be killed with that
|
||||
if (tgts.isEmpty()) {
|
||||
if (mandatory) {
|
||||
// AI is uneffected
|
||||
if (ai.canBeTargetedBy(sa) && ai.canReceiveCounters(CounterType.POISON)) {
|
||||
sa.getTargets().add(ai);
|
||||
return true;
|
||||
}
|
||||
// need to target something, try to target allies
|
||||
PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
if (!allies.isEmpty()) {
|
||||
// some ally would be uneffected
|
||||
PlayerCollection betterAllies = allies.filter(new Predicate<Player>() {
|
||||
@Override
|
||||
public boolean apply(Player input) {
|
||||
if (input.cantLose()) {
|
||||
return true;
|
||||
}
|
||||
if (!input.canReceiveCounters(CounterType.POISON)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
if (!betterAllies.isEmpty()) {
|
||||
allies = betterAllies;
|
||||
}
|
||||
|
||||
Player min = allies.min(PlayerPredicates.compareByPoison());
|
||||
sa.getTargets().add(min);
|
||||
return true;
|
||||
} else if (ai.canBeTargetedBy(sa)) {
|
||||
// need to target himself
|
||||
sa.getTargets().add(ai);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// find player with max poison to kill
|
||||
Player max = tgts.max(PlayerPredicates.compareByPoison());
|
||||
sa.getTargets().add(max);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +1,81 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PowerExchangeAi 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, final SpellAbility sa) {
|
||||
Card c1 = null;
|
||||
Card c2 = null;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
|
||||
List<Card> list =
|
||||
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>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
final Map<String, String> vars = c.getSVars();
|
||||
return !vars.containsKey("RemAIDeck") && c.canBeTargetedBy(sa);
|
||||
}
|
||||
});
|
||||
CardLists.sortByPowerAsc(list);
|
||||
c1 = list.isEmpty() ? null : list.get(0);
|
||||
if (sa.hasParam("Defined")) {
|
||||
c2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
|
||||
}
|
||||
else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
|
||||
CardCollection list2 = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
CardLists.sortByPowerAsc(list2);
|
||||
Collections.reverse(list2);
|
||||
c2 = list2.isEmpty() ? null : list2.get(0);
|
||||
sa.getTargets().add(c2);
|
||||
}
|
||||
if (c1 == null || c2 == null) {
|
||||
return false;
|
||||
}
|
||||
if (ComputerUtilCard.evaluateCreature(c1) > ComputerUtilCard.evaluateCreature(c2) + 40) {
|
||||
sa.getTargets().add(c1);
|
||||
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PowerExchangeAi 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, final SpellAbility sa) {
|
||||
Card c1 = null;
|
||||
Card c2 = null;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
|
||||
List<Card> list =
|
||||
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>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
final Map<String, String> vars = c.getSVars();
|
||||
return !vars.containsKey("RemAIDeck") && c.canBeTargetedBy(sa);
|
||||
}
|
||||
});
|
||||
CardLists.sortByPowerAsc(list);
|
||||
c1 = list.isEmpty() ? null : list.get(0);
|
||||
if (sa.hasParam("Defined")) {
|
||||
c2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
|
||||
}
|
||||
else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
|
||||
CardCollection list2 = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
CardLists.sortByPowerAsc(list2);
|
||||
Collections.reverse(list2);
|
||||
c2 = list2.isEmpty() ? null : list2.get(0);
|
||||
sa.getTargets().add(c2);
|
||||
}
|
||||
if (c1 == null || c2 == null) {
|
||||
return false;
|
||||
}
|
||||
if (ComputerUtilCard.evaluateCreature(c1) > ComputerUtilCard.evaluateCreature(c2) + 40) {
|
||||
sa.getTargets().add(c1);
|
||||
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,375 +1,375 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.ProtectEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class ProtectAi extends SpellAbilityAi {
|
||||
private static boolean hasProtectionFrom(final Card card, final String color) {
|
||||
final List<String> onlyColors = new ArrayList<String>(MagicColor.Constant.ONLY_COLORS);
|
||||
|
||||
// make sure we have a valid color
|
||||
if (!onlyColors.contains(color)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String protection = "Protection from " + color;
|
||||
|
||||
return card.hasKeyword(protection);
|
||||
}
|
||||
|
||||
private static boolean hasProtectionFromAny(final Card card, final Iterable<String> colors) {
|
||||
boolean protect = false;
|
||||
for (final String color : colors) {
|
||||
protect |= hasProtectionFrom(card, color);
|
||||
}
|
||||
return protect;
|
||||
}
|
||||
|
||||
private static boolean hasProtectionFromAll(final Card card, final Iterable<String> colors) {
|
||||
boolean protect = true;
|
||||
boolean isEmpty = true;
|
||||
for (final String color : colors) {
|
||||
protect &= hasProtectionFrom(card, color);
|
||||
isEmpty = false;
|
||||
}
|
||||
return protect && !isEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Find a choice for a Protect SpellAbility that protects from a specific threat card.
|
||||
* @param threat Card to protect against
|
||||
* @param sa Protect SpellAbility
|
||||
* @return choice that can protect against the given threat, null if no such choice exists
|
||||
*/
|
||||
public static String toProtectFrom(final Card threat, SpellAbility sa) {
|
||||
if (sa.getApi() != ApiType.Protection) {
|
||||
return null;
|
||||
}
|
||||
final List<String> choices = ProtectEffect.getProtectionList(sa);
|
||||
if (threat.isArtifact() && choices.contains("artifacts")) {
|
||||
return "artifacts";
|
||||
}
|
||||
if (threat.isBlack() && choices.contains("black")) {
|
||||
return "black";
|
||||
}
|
||||
if (threat.isBlue() && choices.contains("blue")) {
|
||||
return "blue";
|
||||
}
|
||||
if (threat.isGreen() && choices.contains("green")) {
|
||||
return "green";
|
||||
}
|
||||
if (threat.isRed() && choices.contains("red")) {
|
||||
return "red";
|
||||
}
|
||||
if (threat.isWhite() && choices.contains("white")) {
|
||||
return "white";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getProtectCreatures.
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public static CardCollection getProtectCreatures(final Player ai, final SpellAbility sa) {
|
||||
final List<String> gains = ProtectEffect.getProtectionList(sa);
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
|
||||
CardCollection list = ai.getCreaturesInPlay();
|
||||
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (!c.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't add duplicate protections
|
||||
if (hasProtectionFromAll(c, gains)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (threatenedObjects.contains(c)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (combat != null) {
|
||||
//creature is blocking and would be destroyed itself
|
||||
if (combat.isBlocking(c) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) {
|
||||
List<Card> threats = combat.getAttackersBlockedBy(c);
|
||||
return threats != null && !threats.isEmpty() && ProtectAi.toProtectFrom(threats.get(0), sa) != null;
|
||||
}
|
||||
|
||||
//creature is attacking and would be destroyed itself
|
||||
if (combat.isAttacking(c) && combat.isBlocked(c) && ComputerUtilCombat.attackerWouldBeDestroyed(ai, c, combat)) {
|
||||
CardCollection threats = combat.getBlockers(c);
|
||||
if (threats != null && !threats.isEmpty()) {
|
||||
ComputerUtilCard.sortByEvaluateCreature(threats);
|
||||
return ProtectAi.toProtectFrom(threats.get(0), sa) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//make unblockable
|
||||
if (ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1) {
|
||||
AiAttackController aiAtk = new AiAttackController(ai, c);
|
||||
String s = aiAtk.toProtectAttacker(sa);
|
||||
if (s==null) {
|
||||
return false;
|
||||
} else {
|
||||
Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
Combat combat = ai.getGame().getCombat();
|
||||
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opponent, combat, true);
|
||||
float ratio = 1.0f * dmg / opponent.getLife();
|
||||
Random r = MyRandom.getRandom();
|
||||
return r.nextFloat() < ratio;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return list;
|
||||
} // getProtectCreatures()
|
||||
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final boolean notAiMain1 = !(ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1);
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa) && notAiMain1) {
|
||||
// sorceries can only give protection in order to create an unblockable attacker
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
if (cards.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
* when this happens we need to expand AI to consider if its ok
|
||||
* for everything? for (Card card : cards) { // TODO if AI doesn't
|
||||
* control Card and Pump is a Curse, than maybe use?
|
||||
* }
|
||||
*/
|
||||
} else {
|
||||
return protectTgtAI(ai, sa, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean protectTgtAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final Game game = ai.getGame();
|
||||
if (!mandatory && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
CardCollection list = getProtectCreatures(ai, sa);
|
||||
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
|
||||
if (game.getStack().isEmpty()) {
|
||||
// If the cost is tapping, don't activate before declare
|
||||
// attack/block
|
||||
if ((sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {
|
||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& game.getPhaseHandler().isPlayerTurn(ai)) {
|
||||
list.remove(sa.getHostCard());
|
||||
}
|
||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& game.getPhaseHandler().isPlayerTurn(ai)) {
|
||||
list.remove(sa.getHostCard());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't target cards that will die.
|
||||
list = ComputerUtil.getSafeTargets(ai, sa, list);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return mandatory && protectMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
Card t = null;
|
||||
// boolean goodt = false;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) || sa.getTargets().getNumTargeted() == 0) {
|
||||
if (mandatory) {
|
||||
return protectMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
t = ComputerUtilCard.getBestCreatureAI(list);
|
||||
sa.getTargets().add(t);
|
||||
list.remove(t);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // protectTgtAI()
|
||||
|
||||
private static boolean protectMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final Game game = ai.getGame();
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
|
||||
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove anything that's already been targeted
|
||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||
list.remove(c);
|
||||
}
|
||||
|
||||
CardCollection pref = CardLists.filterControlledBy(list, ai);
|
||||
pref = CardLists.filter(pref, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !hasProtectionFromAll(c, ProtectEffect.getProtectionList(sa));
|
||||
}
|
||||
});
|
||||
final CardCollection pref2 = CardLists.filterControlledBy(list, ai);
|
||||
pref = CardLists.filter(pref, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !hasProtectionFromAny(c, ProtectEffect.getProtectionList(sa));
|
||||
}
|
||||
});
|
||||
final List<Card> forced = CardLists.filterControlledBy(list, ai);
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
if (pref.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Card c;
|
||||
if (CardLists.getNotType(pref, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getBestCreatureAI(pref);
|
||||
} else {
|
||||
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
|
||||
}
|
||||
|
||||
pref.remove(c);
|
||||
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
if (pref2.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Card c;
|
||||
if (CardLists.getNotType(pref2, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getBestCreatureAI(pref2);
|
||||
} else {
|
||||
c = ComputerUtilCard.getMostExpensivePermanentAI(pref2, sa, true);
|
||||
}
|
||||
|
||||
pref2.remove(c);
|
||||
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) {
|
||||
if (forced.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Card c;
|
||||
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
||||
} else {
|
||||
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
|
||||
}
|
||||
|
||||
forced.remove(c);
|
||||
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} // protectMandatoryTarget()
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return protectTgtAI(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // protectTriggerAI
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
final Card host = sa.getHostCard();
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
if (host.isCreature()) {
|
||||
// TODO
|
||||
}
|
||||
} else {
|
||||
return protectTgtAI(ai, sa, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // protectDrawbackAI()
|
||||
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.ProtectEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class ProtectAi extends SpellAbilityAi {
|
||||
private static boolean hasProtectionFrom(final Card card, final String color) {
|
||||
final List<String> onlyColors = new ArrayList<String>(MagicColor.Constant.ONLY_COLORS);
|
||||
|
||||
// make sure we have a valid color
|
||||
if (!onlyColors.contains(color)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String protection = "Protection from " + color;
|
||||
|
||||
return card.hasKeyword(protection);
|
||||
}
|
||||
|
||||
private static boolean hasProtectionFromAny(final Card card, final Iterable<String> colors) {
|
||||
boolean protect = false;
|
||||
for (final String color : colors) {
|
||||
protect |= hasProtectionFrom(card, color);
|
||||
}
|
||||
return protect;
|
||||
}
|
||||
|
||||
private static boolean hasProtectionFromAll(final Card card, final Iterable<String> colors) {
|
||||
boolean protect = true;
|
||||
boolean isEmpty = true;
|
||||
for (final String color : colors) {
|
||||
protect &= hasProtectionFrom(card, color);
|
||||
isEmpty = false;
|
||||
}
|
||||
return protect && !isEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Find a choice for a Protect SpellAbility that protects from a specific threat card.
|
||||
* @param threat Card to protect against
|
||||
* @param sa Protect SpellAbility
|
||||
* @return choice that can protect against the given threat, null if no such choice exists
|
||||
*/
|
||||
public static String toProtectFrom(final Card threat, SpellAbility sa) {
|
||||
if (sa.getApi() != ApiType.Protection) {
|
||||
return null;
|
||||
}
|
||||
final List<String> choices = ProtectEffect.getProtectionList(sa);
|
||||
if (threat.isArtifact() && choices.contains("artifacts")) {
|
||||
return "artifacts";
|
||||
}
|
||||
if (threat.isBlack() && choices.contains("black")) {
|
||||
return "black";
|
||||
}
|
||||
if (threat.isBlue() && choices.contains("blue")) {
|
||||
return "blue";
|
||||
}
|
||||
if (threat.isGreen() && choices.contains("green")) {
|
||||
return "green";
|
||||
}
|
||||
if (threat.isRed() && choices.contains("red")) {
|
||||
return "red";
|
||||
}
|
||||
if (threat.isWhite() && choices.contains("white")) {
|
||||
return "white";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getProtectCreatures.
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public static CardCollection getProtectCreatures(final Player ai, final SpellAbility sa) {
|
||||
final List<String> gains = ProtectEffect.getProtectionList(sa);
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
|
||||
CardCollection list = ai.getCreaturesInPlay();
|
||||
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (!c.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't add duplicate protections
|
||||
if (hasProtectionFromAll(c, gains)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (threatenedObjects.contains(c)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (combat != null) {
|
||||
//creature is blocking and would be destroyed itself
|
||||
if (combat.isBlocking(c) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) {
|
||||
List<Card> threats = combat.getAttackersBlockedBy(c);
|
||||
return threats != null && !threats.isEmpty() && ProtectAi.toProtectFrom(threats.get(0), sa) != null;
|
||||
}
|
||||
|
||||
//creature is attacking and would be destroyed itself
|
||||
if (combat.isAttacking(c) && combat.isBlocked(c) && ComputerUtilCombat.attackerWouldBeDestroyed(ai, c, combat)) {
|
||||
CardCollection threats = combat.getBlockers(c);
|
||||
if (threats != null && !threats.isEmpty()) {
|
||||
ComputerUtilCard.sortByEvaluateCreature(threats);
|
||||
return ProtectAi.toProtectFrom(threats.get(0), sa) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//make unblockable
|
||||
if (ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1) {
|
||||
AiAttackController aiAtk = new AiAttackController(ai, c);
|
||||
String s = aiAtk.toProtectAttacker(sa);
|
||||
if (s==null) {
|
||||
return false;
|
||||
} else {
|
||||
Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
Combat combat = ai.getGame().getCombat();
|
||||
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opponent, combat, true);
|
||||
float ratio = 1.0f * dmg / opponent.getLife();
|
||||
Random r = MyRandom.getRandom();
|
||||
return r.nextFloat() < ratio;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return list;
|
||||
} // getProtectCreatures()
|
||||
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final boolean notAiMain1 = !(ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1);
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa) && notAiMain1) {
|
||||
// sorceries can only give protection in order to create an unblockable attacker
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
if (cards.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
* when this happens we need to expand AI to consider if its ok
|
||||
* for everything? for (Card card : cards) { // TODO if AI doesn't
|
||||
* control Card and Pump is a Curse, than maybe use?
|
||||
* }
|
||||
*/
|
||||
} else {
|
||||
return protectTgtAI(ai, sa, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean protectTgtAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final Game game = ai.getGame();
|
||||
if (!mandatory && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
CardCollection list = getProtectCreatures(ai, sa);
|
||||
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
|
||||
if (game.getStack().isEmpty()) {
|
||||
// If the cost is tapping, don't activate before declare
|
||||
// attack/block
|
||||
if ((sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {
|
||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& game.getPhaseHandler().isPlayerTurn(ai)) {
|
||||
list.remove(sa.getHostCard());
|
||||
}
|
||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& game.getPhaseHandler().isPlayerTurn(ai)) {
|
||||
list.remove(sa.getHostCard());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't target cards that will die.
|
||||
list = ComputerUtil.getSafeTargets(ai, sa, list);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return mandatory && protectMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
Card t = null;
|
||||
// boolean goodt = false;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) || sa.getTargets().getNumTargeted() == 0) {
|
||||
if (mandatory) {
|
||||
return protectMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
t = ComputerUtilCard.getBestCreatureAI(list);
|
||||
sa.getTargets().add(t);
|
||||
list.remove(t);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // protectTgtAI()
|
||||
|
||||
private static boolean protectMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final Game game = ai.getGame();
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
|
||||
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove anything that's already been targeted
|
||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||
list.remove(c);
|
||||
}
|
||||
|
||||
CardCollection pref = CardLists.filterControlledBy(list, ai);
|
||||
pref = CardLists.filter(pref, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !hasProtectionFromAll(c, ProtectEffect.getProtectionList(sa));
|
||||
}
|
||||
});
|
||||
final CardCollection pref2 = CardLists.filterControlledBy(list, ai);
|
||||
pref = CardLists.filter(pref, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !hasProtectionFromAny(c, ProtectEffect.getProtectionList(sa));
|
||||
}
|
||||
});
|
||||
final List<Card> forced = CardLists.filterControlledBy(list, ai);
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
if (pref.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Card c;
|
||||
if (CardLists.getNotType(pref, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getBestCreatureAI(pref);
|
||||
} else {
|
||||
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
|
||||
}
|
||||
|
||||
pref.remove(c);
|
||||
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
if (pref2.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Card c;
|
||||
if (CardLists.getNotType(pref2, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getBestCreatureAI(pref2);
|
||||
} else {
|
||||
c = ComputerUtilCard.getMostExpensivePermanentAI(pref2, sa, true);
|
||||
}
|
||||
|
||||
pref2.remove(c);
|
||||
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) {
|
||||
if (forced.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Card c;
|
||||
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
||||
} else {
|
||||
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
|
||||
}
|
||||
|
||||
forced.remove(c);
|
||||
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} // protectMandatoryTarget()
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return protectTgtAI(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // protectTriggerAI
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
final Card host = sa.getHostCard();
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
if (host.isCreature()) {
|
||||
// TODO
|
||||
}
|
||||
} else {
|
||||
return protectTgtAI(ai, sa, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // protectDrawbackAI()
|
||||
|
||||
}
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ProtectAllAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Card hostCard = sa.getHostCard();
|
||||
// if there is no target and host card isn't in play, don't activate
|
||||
if ((sa.getTargetRestrictions() == null) && !hostCard.isInPlay()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
// temporarily disabled until better AI
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
} // protectAllCanPlayAI()
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ProtectAllAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Card hostCard = sa.getHostCard();
|
||||
// if there is no target and host card isn't in play, don't activate
|
||||
if ((sa.getTargetRestrictions() == null) && !hostCard.isInPlay()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
// temporarily disabled until better AI
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
} // protectAllCanPlayAI()
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user