diff --git a/.gitattributes b/.gitattributes index 8fd62b6aa56..31542bc92b4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -84,7 +84,6 @@ forge-core/src/main/java/forge/util/Aggregates.java -text forge-core/src/main/java/forge/util/BinaryUtil.java -text forge-core/src/main/java/forge/util/CollectionSuppliers.java -text forge-core/src/main/java/forge/util/ComparableOp.java -text -forge-core/src/main/java/forge/util/Expressions.java -text forge-core/src/main/java/forge/util/FileSection.java -text forge-core/src/main/java/forge/util/FileSectionManual.java -text forge-core/src/main/java/forge/util/FileUtil.java svneol=native#text/plain @@ -96,7 +95,6 @@ forge-core/src/main/java/forge/util/ItemPoolSorter.java -text forge-core/src/main/java/forge/util/Lang.java -text forge-core/src/main/java/forge/util/MyRandom.java svneol=native#text/plain forge-core/src/main/java/forge/util/PredicateString.java -text -forge-core/src/main/java/forge/util/ReflectionUtil.java -text forge-core/src/main/java/forge/util/TextUtil.java -text forge-core/src/main/java/forge/util/ThreadUtil.java -text forge-core/src/main/java/forge/util/package-info.java -text @@ -115,506 +113,10 @@ forge-game/.settings/org.eclipse.core.resources.prefs -text forge-game/.settings/org.eclipse.jdt.core.prefs -text forge-game/.settings/org.eclipse.m2e.core.prefs -text forge-game/pom.xml -text -forge-game/src/main/java/forge/Command.java -text -forge-game/src/main/java/forge/Constant.java -text -forge-game/src/main/java/forge/ImageCacheBridge.java -text -forge-game/src/main/java/forge/PreferencesBridge.java -text -forge-game/src/main/java/forge/ai/AiAttackController.java -text -forge-game/src/main/java/forge/ai/AiBlockController.java -text -forge-game/src/main/java/forge/ai/AiController.java -text -forge-game/src/main/java/forge/ai/AiCostDecision.java -text -forge-game/src/main/java/forge/ai/AiProfileUtil.java -text -forge-game/src/main/java/forge/ai/ComputerUtil.java -text -forge-game/src/main/java/forge/ai/ComputerUtilCard.java -text -forge-game/src/main/java/forge/ai/ComputerUtilCombat.java -text -forge-game/src/main/java/forge/ai/ComputerUtilCost.java -text -forge-game/src/main/java/forge/ai/ComputerUtilMana.java -text -forge-game/src/main/java/forge/ai/SpellAbilityAi.java -text -forge-game/src/main/java/forge/ai/ability/AddPhaseAi.java -text -forge-game/src/main/java/forge/ai/ability/AddTurnAi.java -text -forge-game/src/main/java/forge/ai/ability/AlwaysPlayAi.java -text -forge-game/src/main/java/forge/ai/ability/AnimateAi.java -text -forge-game/src/main/java/forge/ai/ability/AnimateAllAi.java -text -forge-game/src/main/java/forge/ai/ability/AttachAi.java -text -forge-game/src/main/java/forge/ai/ability/BalanceAi.java -text -forge-game/src/main/java/forge/ai/ability/BecomesBlockedAi.java -text -forge-game/src/main/java/forge/ai/ability/BondAi.java -text -forge-game/src/main/java/forge/ai/ability/CanPlayAsDrawbackAi.java -text -forge-game/src/main/java/forge/ai/ability/CannotPlayAi.java -text -forge-game/src/main/java/forge/ai/ability/ChangeTargetsAi.java -text -forge-game/src/main/java/forge/ai/ability/ChangeZoneAi.java -text -forge-game/src/main/java/forge/ai/ability/ChangeZoneAllAi.java -text -forge-game/src/main/java/forge/ai/ability/CharmAi.java -text -forge-game/src/main/java/forge/ai/ability/ChooseCardAi.java -text -forge-game/src/main/java/forge/ai/ability/ChooseCardNameAi.java -text -forge-game/src/main/java/forge/ai/ability/ChooseColorAi.java -text -forge-game/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java -text -forge-game/src/main/java/forge/ai/ability/ChoosePlayerAi.java -text -forge-game/src/main/java/forge/ai/ability/ChooseSourceAi.java -text -forge-game/src/main/java/forge/ai/ability/ChooseTypeAi.java -text -forge-game/src/main/java/forge/ai/ability/ClashAi.java -text -forge-game/src/main/java/forge/ai/ability/CloneAi.java -text -forge-game/src/main/java/forge/ai/ability/ControlExchangeAi.java -text -forge-game/src/main/java/forge/ai/ability/ControlGainAi.java -text -forge-game/src/main/java/forge/ai/ability/CopyPermanentAi.java -text -forge-game/src/main/java/forge/ai/ability/CounterAi.java -text -forge-game/src/main/java/forge/ai/ability/CountersAi.java -text -forge-game/src/main/java/forge/ai/ability/CountersMoveAi.java -text -forge-game/src/main/java/forge/ai/ability/CountersProliferateAi.java -text -forge-game/src/main/java/forge/ai/ability/CountersPutAi.java -text -forge-game/src/main/java/forge/ai/ability/CountersPutAllAi.java -text -forge-game/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java -text -forge-game/src/main/java/forge/ai/ability/CountersRemoveAi.java -text -forge-game/src/main/java/forge/ai/ability/DamageAiBase.java -text -forge-game/src/main/java/forge/ai/ability/DamageAllAi.java -text -forge-game/src/main/java/forge/ai/ability/DamageDealAi.java -text -forge-game/src/main/java/forge/ai/ability/DamageEachAi.java -text -forge-game/src/main/java/forge/ai/ability/DamagePreventAi.java -text -forge-game/src/main/java/forge/ai/ability/DamagePreventAllAi.java -text -forge-game/src/main/java/forge/ai/ability/DebuffAi.java -text -forge-game/src/main/java/forge/ai/ability/DebuffAllAi.java -text -forge-game/src/main/java/forge/ai/ability/DelayedTriggerAi.java -text -forge-game/src/main/java/forge/ai/ability/DestroyAi.java -text -forge-game/src/main/java/forge/ai/ability/DestroyAllAi.java -text -forge-game/src/main/java/forge/ai/ability/DigAi.java -text -forge-game/src/main/java/forge/ai/ability/DigUntilAi.java -text -forge-game/src/main/java/forge/ai/ability/DiscardAi.java -text -forge-game/src/main/java/forge/ai/ability/DrainManaAi.java -text -forge-game/src/main/java/forge/ai/ability/DrawAi.java -text -forge-game/src/main/java/forge/ai/ability/EffectAi.java -text -forge-game/src/main/java/forge/ai/ability/EncodeAi.java -text -forge-game/src/main/java/forge/ai/ability/EndTurnAi.java -text -forge-game/src/main/java/forge/ai/ability/FightAi.java -text -forge-game/src/main/java/forge/ai/ability/FlipACoinAi.java -text -forge-game/src/main/java/forge/ai/ability/FogAi.java -text -forge-game/src/main/java/forge/ai/ability/GameLossAi.java -text -forge-game/src/main/java/forge/ai/ability/GameWinAi.java -text -forge-game/src/main/java/forge/ai/ability/HauntAi.java -text -forge-game/src/main/java/forge/ai/ability/LegendaryRuleAi.java -text -forge-game/src/main/java/forge/ai/ability/LifeExchangeAi.java -text -forge-game/src/main/java/forge/ai/ability/LifeGainAi.java -text -forge-game/src/main/java/forge/ai/ability/LifeLoseAi.java -text -forge-game/src/main/java/forge/ai/ability/LifeSetAi.java -text -forge-game/src/main/java/forge/ai/ability/ManaEffectAi.java -text -forge-game/src/main/java/forge/ai/ability/MillAi.java -text -forge-game/src/main/java/forge/ai/ability/MustAttackAi.java -text -forge-game/src/main/java/forge/ai/ability/MustBlockAi.java -text -forge-game/src/main/java/forge/ai/ability/PeekAndRevealAi.java -text -forge-game/src/main/java/forge/ai/ability/PermanentCreatureAi.java -text -forge-game/src/main/java/forge/ai/ability/PermanentNoncreatureAi.java -text -forge-game/src/main/java/forge/ai/ability/PhasesAi.java -text -forge-game/src/main/java/forge/ai/ability/PlayAi.java -text -forge-game/src/main/java/forge/ai/ability/PoisonAi.java -text -forge-game/src/main/java/forge/ai/ability/PowerExchangeAi.java -text -forge-game/src/main/java/forge/ai/ability/ProtectAi.java -text -forge-game/src/main/java/forge/ai/ability/ProtectAllAi.java -text -forge-game/src/main/java/forge/ai/ability/PumpAi.java -text -forge-game/src/main/java/forge/ai/ability/PumpAiBase.java -text -forge-game/src/main/java/forge/ai/ability/PumpAllAi.java -text -forge-game/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java -text -forge-game/src/main/java/forge/ai/ability/RegenerateAi.java -text -forge-game/src/main/java/forge/ai/ability/RegenerateAllAi.java -text -forge-game/src/main/java/forge/ai/ability/RemoveFromCombatAi.java -text -forge-game/src/main/java/forge/ai/ability/RepeatAi.java -text -forge-game/src/main/java/forge/ai/ability/RepeatEachAi.java -text -forge-game/src/main/java/forge/ai/ability/RestartGameAi.java -text -forge-game/src/main/java/forge/ai/ability/RevealAi.java -text -forge-game/src/main/java/forge/ai/ability/RevealAiBase.java -text -forge-game/src/main/java/forge/ai/ability/RevealHandAi.java -text -forge-game/src/main/java/forge/ai/ability/RollPlanarDiceAi.java -text -forge-game/src/main/java/forge/ai/ability/SacrificeAi.java -text -forge-game/src/main/java/forge/ai/ability/SacrificeAllAi.java -text -forge-game/src/main/java/forge/ai/ability/ScryAi.java -text -forge-game/src/main/java/forge/ai/ability/SetStateAi.java -text -forge-game/src/main/java/forge/ai/ability/ShuffleAi.java -text -forge-game/src/main/java/forge/ai/ability/StoreSVarAi.java -text -forge-game/src/main/java/forge/ai/ability/TapAi.java -text -forge-game/src/main/java/forge/ai/ability/TapAiBase.java -text -forge-game/src/main/java/forge/ai/ability/TapAllAi.java -text -forge-game/src/main/java/forge/ai/ability/TapOrUntapAi.java -text -forge-game/src/main/java/forge/ai/ability/TapOrUntapAllAi.java -text -forge-game/src/main/java/forge/ai/ability/TokenAi.java -text -forge-game/src/main/java/forge/ai/ability/TwoPilesAi.java -text -forge-game/src/main/java/forge/ai/ability/UnattachAllAi.java -text -forge-game/src/main/java/forge/ai/ability/UntapAi.java -text -forge-game/src/main/java/forge/ai/ability/UntapAllAi.java -text -forge-game/src/main/java/forge/ai/ability/ZoneExchangeAi.java -text -forge-game/src/main/java/forge/game/Game.java -text -forge-game/src/main/java/forge/game/GameAction.java -text -forge-game/src/main/java/forge/game/GameActionUtil.java -text -forge-game/src/main/java/forge/game/GameEndReason.java -text -forge-game/src/main/java/forge/game/GameEntity.java -text forge-game/src/main/java/forge/game/GameFormat.java -text -forge-game/src/main/java/forge/game/GameLog.java -text -forge-game/src/main/java/forge/game/GameLogEntry.java -text -forge-game/src/main/java/forge/game/GameLogEntryType.java -text -forge-game/src/main/java/forge/game/GameLogFormatter.java -text -forge-game/src/main/java/forge/game/GameObject.java -text -forge-game/src/main/java/forge/game/GameOutcome.java -text -forge-game/src/main/java/forge/game/GameStage.java -text -forge-game/src/main/java/forge/game/GameType.java -text -forge-game/src/main/java/forge/game/GlobalRuleChange.java -text -forge-game/src/main/java/forge/game/Match.java -text -forge-game/src/main/java/forge/game/PlanarDice.java -text -forge-game/src/main/java/forge/game/StaticEffect.java -text -forge-game/src/main/java/forge/game/StaticEffects.java -text -forge-game/src/main/java/forge/game/TriggerReplacementBase.java -text -forge-game/src/main/java/forge/game/ability/AbilityApiBased.java -text -forge-game/src/main/java/forge/game/ability/AbilityFactory.java -text -forge-game/src/main/java/forge/game/ability/AbilityUtils.java -text -forge-game/src/main/java/forge/game/ability/ApiType.java -text -forge-game/src/main/java/forge/game/ability/SaTargetRoutines.java -text -forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java -text -forge-game/src/main/java/forge/game/ability/SpellApiBased.java -text -forge-game/src/main/java/forge/game/ability/StaticAbilityApiBased.java -text -forge-game/src/main/java/forge/game/ability/effects/AbandonEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/AddPhaseEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java -text -forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/BondEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ChooseColorEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ChoosePlayerEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ChooseSourceEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ChooseTypeEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ClashEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/CleanUpEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/CountersRemoveAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DamagePreventAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DebuffAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DeclareCombatantsEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DestroyAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DestroyEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DigEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DrainManaEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/DrawEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ETBReplacementEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/EncodeEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/EndTurnEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/FightEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/FogEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/GameLossEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/GameWinEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/LifeExchangeEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/LifeGainEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/LifeLoseEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/LifeSetEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/MillEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/MustAttackEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/MustBlockEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/OwnershipGainEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/PeekAndRevealEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/PermanentCreatureEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/PhasesEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/PlaneswalkEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/PoisonEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/PowerExchangeEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ProtectEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/RearrangeTopOfLibraryEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/RegenerateEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/RevealEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/RevealHandEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/RollPlanarDiceEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/RunSVarAbilityEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ScryEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/SetInMotionEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ShuffleEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/StoreSVarEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/TapAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/TapEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/TapOrUntapAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/TapOrUntapEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/UnattachAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/UntapAllEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/UntapEffect.java -text -forge-game/src/main/java/forge/game/ability/effects/ZoneExchangeEffect.java -text -forge-game/src/main/java/forge/game/ability/package-info.java -text -forge-game/src/main/java/forge/game/card/Card.java -text -forge-game/src/main/java/forge/game/card/CardCharacteristics.java -text -forge-game/src/main/java/forge/game/card/CardColor.java -text -forge-game/src/main/java/forge/game/card/CardDamageHistory.java -text -forge-game/src/main/java/forge/game/card/CardFactory.java -text -forge-game/src/main/java/forge/game/card/CardFactoryUtil.java -text -forge-game/src/main/java/forge/game/card/CardKeywords.java -text -forge-game/src/main/java/forge/game/card/CardLists.java -text -forge-game/src/main/java/forge/game/card/CardPowerToughness.java -text -forge-game/src/main/java/forge/game/card/CardPredicates.java -text -forge-game/src/main/java/forge/game/card/CardShields.java -text -forge-game/src/main/java/forge/game/card/CardType.java -text -forge-game/src/main/java/forge/game/card/CardUtil.java -text -forge-game/src/main/java/forge/game/card/CounterType.java -text forge-game/src/main/java/forge/game/card/package-info.java -text -forge-game/src/main/java/forge/game/combat/AttackingBand.java -text -forge-game/src/main/java/forge/game/combat/Combat.java -text -forge-game/src/main/java/forge/game/combat/CombatLki.java -text -forge-game/src/main/java/forge/game/combat/CombatUtil.java -text -forge-game/src/main/java/forge/game/cost/Cost.java -text -forge-game/src/main/java/forge/game/cost/CostAddMana.java -text -forge-game/src/main/java/forge/game/cost/CostChooseCreatureType.java -text -forge-game/src/main/java/forge/game/cost/CostDamage.java -text -forge-game/src/main/java/forge/game/cost/CostDecisionMakerBase.java -text -forge-game/src/main/java/forge/game/cost/CostDiscard.java -text -forge-game/src/main/java/forge/game/cost/CostDraw.java -text -forge-game/src/main/java/forge/game/cost/CostExile.java -text -forge-game/src/main/java/forge/game/cost/CostExiledMoveToGrave.java -text -forge-game/src/main/java/forge/game/cost/CostFlipCoin.java -text -forge-game/src/main/java/forge/game/cost/CostGainControl.java -text -forge-game/src/main/java/forge/game/cost/CostGainLife.java -text -forge-game/src/main/java/forge/game/cost/CostMill.java -text -forge-game/src/main/java/forge/game/cost/CostPart.java -text -forge-game/src/main/java/forge/game/cost/CostPartMana.java -text -forge-game/src/main/java/forge/game/cost/CostPartWithList.java -text -forge-game/src/main/java/forge/game/cost/CostPayLife.java -text -forge-game/src/main/java/forge/game/cost/CostPayment.java -text -forge-game/src/main/java/forge/game/cost/CostPutCardToLib.java -text -forge-game/src/main/java/forge/game/cost/CostPutCounter.java -text -forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java -text -forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java -text -forge-game/src/main/java/forge/game/cost/CostReturn.java -text -forge-game/src/main/java/forge/game/cost/CostReveal.java -text -forge-game/src/main/java/forge/game/cost/CostSacrifice.java -text -forge-game/src/main/java/forge/game/cost/CostTap.java -text -forge-game/src/main/java/forge/game/cost/CostTapType.java -text -forge-game/src/main/java/forge/game/cost/CostUnattach.java -text -forge-game/src/main/java/forge/game/cost/CostUntap.java -text -forge-game/src/main/java/forge/game/cost/CostUntapType.java -text -forge-game/src/main/java/forge/game/cost/ICostVisitor.java -text -forge-game/src/main/java/forge/game/cost/PaymentDecision.java -text -forge-game/src/main/java/forge/game/cost/package-info.java -text -forge-game/src/main/java/forge/game/event/EventValueChangeType.java -text -forge-game/src/main/java/forge/game/event/GameEvent.java -text -forge-game/src/main/java/forge/game/event/GameEventAnteCardsSelected.java -text -forge-game/src/main/java/forge/game/event/GameEventAttackersDeclared.java -text -forge-game/src/main/java/forge/game/event/GameEventBlockersDeclared.java -text -forge-game/src/main/java/forge/game/event/GameEventCardAttachment.java -text -forge-game/src/main/java/forge/game/event/GameEventCardChangeZone.java -text -forge-game/src/main/java/forge/game/event/GameEventCardCounters.java -text -forge-game/src/main/java/forge/game/event/GameEventCardDamaged.java -text -forge-game/src/main/java/forge/game/event/GameEventCardDestroyed.java -text -forge-game/src/main/java/forge/game/event/GameEventCardPhased.java -text -forge-game/src/main/java/forge/game/event/GameEventCardRegenerated.java -text -forge-game/src/main/java/forge/game/event/GameEventCardSacrificed.java -text -forge-game/src/main/java/forge/game/event/GameEventCardStatsChanged.java -text -forge-game/src/main/java/forge/game/event/GameEventCardTapped.java -text -forge-game/src/main/java/forge/game/event/GameEventCombatEnded.java -text -forge-game/src/main/java/forge/game/event/GameEventFlipCoin.java -text -forge-game/src/main/java/forge/game/event/GameEventGameFinished.java -text -forge-game/src/main/java/forge/game/event/GameEventGameOutcome.java -text -forge-game/src/main/java/forge/game/event/GameEventGameRestarted.java -text -forge-game/src/main/java/forge/game/event/GameEventGameStarted.java -text -forge-game/src/main/java/forge/game/event/GameEventLandPlayed.java -text -forge-game/src/main/java/forge/game/event/GameEventManaBurn.java -text -forge-game/src/main/java/forge/game/event/GameEventManaPool.java -text -forge-game/src/main/java/forge/game/event/GameEventMulligan.java -text -forge-game/src/main/java/forge/game/event/GameEventPlayerControl.java -text -forge-game/src/main/java/forge/game/event/GameEventPlayerDamaged.java -text -forge-game/src/main/java/forge/game/event/GameEventPlayerLivesChanged.java -text -forge-game/src/main/java/forge/game/event/GameEventPlayerPoisoned.java -text -forge-game/src/main/java/forge/game/event/GameEventPlayerPriority.java -text -forge-game/src/main/java/forge/game/event/GameEventShuffle.java -text -forge-game/src/main/java/forge/game/event/GameEventSpellAbilityCast.java -text -forge-game/src/main/java/forge/game/event/GameEventSpellRemovedFromStack.java -text -forge-game/src/main/java/forge/game/event/GameEventSpellResolved.java -text -forge-game/src/main/java/forge/game/event/GameEventTokenCreated.java -text -forge-game/src/main/java/forge/game/event/GameEventTurnBegan.java -text -forge-game/src/main/java/forge/game/event/GameEventTurnEnded.java -text -forge-game/src/main/java/forge/game/event/GameEventTurnPhase.java -text -forge-game/src/main/java/forge/game/event/GameEventZone.java -text -forge-game/src/main/java/forge/game/event/IGameEventVisitor.java -text -forge-game/src/main/java/forge/game/event/package-info.java -text -forge-game/src/main/java/forge/game/mana/Mana.java -text -forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java -text -forge-game/src/main/java/forge/game/mana/ManaPool.java -text -forge-game/src/main/java/forge/game/mana/package-info.java -text forge-game/src/main/java/forge/game/package-info.java -text -forge-game/src/main/java/forge/game/phase/EndOfTurn.java -text -forge-game/src/main/java/forge/game/phase/ExtraTurn.java -text -forge-game/src/main/java/forge/game/phase/Phase.java -text -forge-game/src/main/java/forge/game/phase/PhaseHandler.java -text -forge-game/src/main/java/forge/game/phase/PhaseType.java -text -forge-game/src/main/java/forge/game/phase/Untap.java -text -forge-game/src/main/java/forge/game/phase/Upkeep.java -text -forge-game/src/main/java/forge/game/phase/package-info.java -text -forge-game/src/main/java/forge/game/player/GameLossReason.java -text -forge-game/src/main/java/forge/game/player/IHasIcon.java -text -forge-game/src/main/java/forge/game/player/LobbyPlayer.java -text -forge-game/src/main/java/forge/game/player/LobbyPlayerAi.java -text -forge-game/src/main/java/forge/game/player/Player.java -text -forge-game/src/main/java/forge/game/player/PlayerActionConfirmMode.java -text -forge-game/src/main/java/forge/game/player/PlayerController.java -text -forge-game/src/main/java/forge/game/player/PlayerControllerAi.java -text -forge-game/src/main/java/forge/game/player/PlayerOutcome.java -text -forge-game/src/main/java/forge/game/player/PlayerStatistics.java -text -forge-game/src/main/java/forge/game/player/RegisteredPlayer.java -text -forge-game/src/main/java/forge/game/player/package-info.java -text -forge-game/src/main/java/forge/game/replacement/ReplaceAddCounter.java -text -forge-game/src/main/java/forge/game/replacement/ReplaceCounter.java -text -forge-game/src/main/java/forge/game/replacement/ReplaceDamage.java -text -forge-game/src/main/java/forge/game/replacement/ReplaceDestroy.java -text -forge-game/src/main/java/forge/game/replacement/ReplaceDiscard.java -text -forge-game/src/main/java/forge/game/replacement/ReplaceDraw.java -text -forge-game/src/main/java/forge/game/replacement/ReplaceGainLife.java -text -forge-game/src/main/java/forge/game/replacement/ReplaceGameLoss.java -text -forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java -text -forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java -text -forge-game/src/main/java/forge/game/replacement/ReplaceSetInMotion.java -text -forge-game/src/main/java/forge/game/replacement/ReplaceTurnFaceUp.java -text -forge-game/src/main/java/forge/game/replacement/ReplaceUntap.java -text -forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java -text -forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java -text -forge-game/src/main/java/forge/game/replacement/ReplacementLayer.java -text -forge-game/src/main/java/forge/game/replacement/ReplacementResult.java -text -forge-game/src/main/java/forge/game/replacement/ReplacementType.java -text -forge-game/src/main/java/forge/game/replacement/package-info.java -text -forge-game/src/main/java/forge/game/spellability/Ability.java -text -forge-game/src/main/java/forge/game/spellability/AbilityActivated.java -text -forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java -text -forge-game/src/main/java/forge/game/spellability/AbilityStatic.java -text -forge-game/src/main/java/forge/game/spellability/AbilitySub.java -text -forge-game/src/main/java/forge/game/spellability/AbilityTriggered.java -text -forge-game/src/main/java/forge/game/spellability/ISpellAbility.java -text -forge-game/src/main/java/forge/game/spellability/OptionalCost.java -text -forge-game/src/main/java/forge/game/spellability/Spell.java -text -forge-game/src/main/java/forge/game/spellability/SpellAbility.java -text -forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java -text -forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java -text -forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java -text -forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java -text -forge-game/src/main/java/forge/game/spellability/SpellPermanent.java -text -forge-game/src/main/java/forge/game/spellability/TargetChoices.java -text -forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java -text -forge-game/src/main/java/forge/game/spellability/package-info.java -text -forge-game/src/main/java/forge/game/staticability/StaticAbility.java -text -forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java -text -forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java -text -forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java -text -forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java -text -forge-game/src/main/java/forge/game/staticability/StaticAbilityCostChange.java -text -forge-game/src/main/java/forge/game/staticability/StaticAbilityETBTapped.java -text -forge-game/src/main/java/forge/game/staticability/StaticAbilityMayLookAt.java -text -forge-game/src/main/java/forge/game/staticability/StaticAbilityPreventDamage.java -text -forge-game/src/main/java/forge/game/staticability/package-info.java -text -forge-game/src/main/java/forge/game/trigger/Trigger.java -text -forge-game/src/main/java/forge/game/trigger/TriggerAlways.java -text -forge-game/src/main/java/forge/game/trigger/TriggerAttached.java -text -forge-game/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java -text -forge-game/src/main/java/forge/game/trigger/TriggerAttackerUnblocked.java -text -forge-game/src/main/java/forge/game/trigger/TriggerAttackersDeclared.java -text -forge-game/src/main/java/forge/game/trigger/TriggerAttacks.java -text -forge-game/src/main/java/forge/game/trigger/TriggerBecomeMonstrous.java -text -forge-game/src/main/java/forge/game/trigger/TriggerBecomesTarget.java -text -forge-game/src/main/java/forge/game/trigger/TriggerBlockersDeclared.java -text -forge-game/src/main/java/forge/game/trigger/TriggerBlocks.java -text -forge-game/src/main/java/forge/game/trigger/TriggerChampioned.java -text -forge-game/src/main/java/forge/game/trigger/TriggerChangesController.java -text -forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java -text -forge-game/src/main/java/forge/game/trigger/TriggerClashed.java -text -forge-game/src/main/java/forge/game/trigger/TriggerCombatDamageDoneOnce.java -text -forge-game/src/main/java/forge/game/trigger/TriggerCounterAdded.java -text -forge-game/src/main/java/forge/game/trigger/TriggerCounterRemoved.java -text -forge-game/src/main/java/forge/game/trigger/TriggerCountered.java -text -forge-game/src/main/java/forge/game/trigger/TriggerCycled.java -text -forge-game/src/main/java/forge/game/trigger/TriggerDamageDone.java -text -forge-game/src/main/java/forge/game/trigger/TriggerDestroyed.java -text -forge-game/src/main/java/forge/game/trigger/TriggerDevoured.java -text -forge-game/src/main/java/forge/game/trigger/TriggerDiscarded.java -text -forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java -text -forge-game/src/main/java/forge/game/trigger/TriggerEvolved.java -text -forge-game/src/main/java/forge/game/trigger/TriggerFlippedCoin.java -text -forge-game/src/main/java/forge/game/trigger/TriggerHandler.java -text -forge-game/src/main/java/forge/game/trigger/TriggerLandPlayed.java -text -forge-game/src/main/java/forge/game/trigger/TriggerLifeGained.java -text -forge-game/src/main/java/forge/game/trigger/TriggerLifeLost.java -text -forge-game/src/main/java/forge/game/trigger/TriggerLosesGame.java -text -forge-game/src/main/java/forge/game/trigger/TriggerNewGame.java -text -forge-game/src/main/java/forge/game/trigger/TriggerPayCumulativeUpkeep.java -text -forge-game/src/main/java/forge/game/trigger/TriggerPhase.java -text -forge-game/src/main/java/forge/game/trigger/TriggerPlanarDice.java -text -forge-game/src/main/java/forge/game/trigger/TriggerPlaneswalkedFrom.java -text -forge-game/src/main/java/forge/game/trigger/TriggerPlaneswalkedTo.java -text -forge-game/src/main/java/forge/game/trigger/TriggerSacrificed.java -text -forge-game/src/main/java/forge/game/trigger/TriggerScry.java -text -forge-game/src/main/java/forge/game/trigger/TriggerSetInMotion.java -text -forge-game/src/main/java/forge/game/trigger/TriggerShuffled.java -text -forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCast.java -text -forge-game/src/main/java/forge/game/trigger/TriggerTaps.java -text -forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java -text -forge-game/src/main/java/forge/game/trigger/TriggerTransformed.java -text -forge-game/src/main/java/forge/game/trigger/TriggerTurnFaceUp.java -text -forge-game/src/main/java/forge/game/trigger/TriggerType.java -text -forge-game/src/main/java/forge/game/trigger/TriggerUnequip.java -text -forge-game/src/main/java/forge/game/trigger/TriggerUntaps.java -text -forge-game/src/main/java/forge/game/trigger/TriggerWaiting.java -text -forge-game/src/main/java/forge/game/trigger/WrappedAbility.java -text -forge-game/src/main/java/forge/game/trigger/ZCTrigger.java -text -forge-game/src/main/java/forge/game/trigger/package-info.java -text -forge-game/src/main/java/forge/game/zone/MagicStack.java -text -forge-game/src/main/java/forge/game/zone/PlayerZone.java -text -forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java -text -forge-game/src/main/java/forge/game/zone/Zone.java -text -forge-game/src/main/java/forge/game/zone/ZoneType.java -text -forge-game/src/main/java/forge/game/zone/package-info.java -text forge-game/src/main/java/forge/package-info.java -text -forge-game/src/main/java/forge/util/maps/EnumMapOfLists.java -text -forge-game/src/main/java/forge/util/maps/EnumMapToAmount.java -text -forge-game/src/main/java/forge/util/maps/HashMapOfLists.java -text -forge-game/src/main/java/forge/util/maps/MapOfLists.java -text -forge-game/src/main/java/forge/util/maps/MapToAmount.java -text -forge-game/src/main/java/forge/util/maps/package-info.java -text forge-gui/.classpath -text forge-gui/.project -text forge-gui/.settings/org.eclipse.core.resources.prefs -text @@ -15204,12 +14706,132 @@ forge-gui/src/main/html/js/jquery/jquery-1.9.1.js -text forge-gui/src/main/html/js/jquery/jquery-1.9.1.min.js -text forge-gui/src/main/html/js/observable.js -text forge-gui/src/main/html/js/socket.js -text +forge-gui/src/main/java/forge/Command.java svneol=native#text/plain +forge-gui/src/main/java/forge/Constant.java svneol=native#text/plain forge-gui/src/main/java/forge/FThreads.java -text forge-gui/src/main/java/forge/ImageCache.java svneol=native#text/plain -forge-gui/src/main/java/forge/ImageCacheProvider.java -text forge-gui/src/main/java/forge/ImageLoader.java -text -forge-gui/src/main/java/forge/PreferencesProvider.java -text forge-gui/src/main/java/forge/Singletons.java svneol=native#text/plain +forge-gui/src/main/java/forge/ai/AiAttackController.java svneol=native#text/plain +forge-gui/src/main/java/forge/ai/AiBlockController.java svneol=native#text/plain +forge-gui/src/main/java/forge/ai/AiController.java svneol=native#text/plain +forge-gui/src/main/java/forge/ai/AiCostDecision.java -text +forge-gui/src/main/java/forge/ai/AiProfileUtil.java -text +forge-gui/src/main/java/forge/ai/ComputerUtil.java svneol=native#text/plain +forge-gui/src/main/java/forge/ai/ComputerUtilCard.java -text +forge-gui/src/main/java/forge/ai/ComputerUtilCombat.java -text +forge-gui/src/main/java/forge/ai/ComputerUtilCost.java -text +forge-gui/src/main/java/forge/ai/ComputerUtilMana.java -text +forge-gui/src/main/java/forge/ai/SpellAbilityAi.java -text +forge-gui/src/main/java/forge/ai/ability/AddPhaseAi.java -text +forge-gui/src/main/java/forge/ai/ability/AddTurnAi.java svneol=native#text/plain +forge-gui/src/main/java/forge/ai/ability/AlwaysPlayAi.java -text +forge-gui/src/main/java/forge/ai/ability/AnimateAi.java -text +forge-gui/src/main/java/forge/ai/ability/AnimateAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/AttachAi.java -text +forge-gui/src/main/java/forge/ai/ability/BalanceAi.java -text +forge-gui/src/main/java/forge/ai/ability/BecomesBlockedAi.java -text +forge-gui/src/main/java/forge/ai/ability/BondAi.java -text +forge-gui/src/main/java/forge/ai/ability/CanPlayAsDrawbackAi.java -text +forge-gui/src/main/java/forge/ai/ability/CannotPlayAi.java -text +forge-gui/src/main/java/forge/ai/ability/ChangeTargetsAi.java -text +forge-gui/src/main/java/forge/ai/ability/ChangeZoneAi.java -text +forge-gui/src/main/java/forge/ai/ability/ChangeZoneAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/CharmAi.java -text +forge-gui/src/main/java/forge/ai/ability/ChooseCardAi.java -text +forge-gui/src/main/java/forge/ai/ability/ChooseCardNameAi.java -text +forge-gui/src/main/java/forge/ai/ability/ChooseColorAi.java -text +forge-gui/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java -text +forge-gui/src/main/java/forge/ai/ability/ChoosePlayerAi.java -text +forge-gui/src/main/java/forge/ai/ability/ChooseSourceAi.java -text +forge-gui/src/main/java/forge/ai/ability/ChooseTypeAi.java -text +forge-gui/src/main/java/forge/ai/ability/ClashAi.java -text +forge-gui/src/main/java/forge/ai/ability/CloneAi.java -text +forge-gui/src/main/java/forge/ai/ability/ControlExchangeAi.java -text +forge-gui/src/main/java/forge/ai/ability/ControlGainAi.java -text +forge-gui/src/main/java/forge/ai/ability/CopyPermanentAi.java -text +forge-gui/src/main/java/forge/ai/ability/CounterAi.java -text +forge-gui/src/main/java/forge/ai/ability/CountersAi.java svneol=native#text/plain +forge-gui/src/main/java/forge/ai/ability/CountersMoveAi.java -text +forge-gui/src/main/java/forge/ai/ability/CountersProliferateAi.java -text +forge-gui/src/main/java/forge/ai/ability/CountersPutAi.java -text +forge-gui/src/main/java/forge/ai/ability/CountersPutAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java -text +forge-gui/src/main/java/forge/ai/ability/CountersRemoveAi.java -text +forge-gui/src/main/java/forge/ai/ability/DamageAiBase.java -text +forge-gui/src/main/java/forge/ai/ability/DamageAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/DamageDealAi.java -text +forge-gui/src/main/java/forge/ai/ability/DamageEachAi.java -text +forge-gui/src/main/java/forge/ai/ability/DamagePreventAi.java -text +forge-gui/src/main/java/forge/ai/ability/DamagePreventAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/DebuffAi.java -text +forge-gui/src/main/java/forge/ai/ability/DebuffAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/DelayedTriggerAi.java -text +forge-gui/src/main/java/forge/ai/ability/DestroyAi.java -text +forge-gui/src/main/java/forge/ai/ability/DestroyAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/DigAi.java -text +forge-gui/src/main/java/forge/ai/ability/DigUntilAi.java -text +forge-gui/src/main/java/forge/ai/ability/DiscardAi.java -text +forge-gui/src/main/java/forge/ai/ability/DrainManaAi.java -text +forge-gui/src/main/java/forge/ai/ability/DrawAi.java svneol=native#text/plain +forge-gui/src/main/java/forge/ai/ability/EffectAi.java -text +forge-gui/src/main/java/forge/ai/ability/EncodeAi.java -text +forge-gui/src/main/java/forge/ai/ability/EndTurnAi.java -text +forge-gui/src/main/java/forge/ai/ability/FightAi.java -text +forge-gui/src/main/java/forge/ai/ability/FlipACoinAi.java -text +forge-gui/src/main/java/forge/ai/ability/FogAi.java -text +forge-gui/src/main/java/forge/ai/ability/GameLossAi.java -text +forge-gui/src/main/java/forge/ai/ability/GameWinAi.java -text +forge-gui/src/main/java/forge/ai/ability/HauntAi.java -text +forge-gui/src/main/java/forge/ai/ability/LegendaryRuleAi.java -text +forge-gui/src/main/java/forge/ai/ability/LifeExchangeAi.java -text +forge-gui/src/main/java/forge/ai/ability/LifeGainAi.java -text +forge-gui/src/main/java/forge/ai/ability/LifeLoseAi.java -text +forge-gui/src/main/java/forge/ai/ability/LifeSetAi.java -text +forge-gui/src/main/java/forge/ai/ability/ManaEffectAi.java -text +forge-gui/src/main/java/forge/ai/ability/MillAi.java -text +forge-gui/src/main/java/forge/ai/ability/MustAttackAi.java -text +forge-gui/src/main/java/forge/ai/ability/MustBlockAi.java -text +forge-gui/src/main/java/forge/ai/ability/PeekAndRevealAi.java -text +forge-gui/src/main/java/forge/ai/ability/PermanentCreatureAi.java -text +forge-gui/src/main/java/forge/ai/ability/PermanentNoncreatureAi.java -text +forge-gui/src/main/java/forge/ai/ability/PhasesAi.java -text +forge-gui/src/main/java/forge/ai/ability/PlayAi.java -text +forge-gui/src/main/java/forge/ai/ability/PoisonAi.java -text +forge-gui/src/main/java/forge/ai/ability/PowerExchangeAi.java -text +forge-gui/src/main/java/forge/ai/ability/ProtectAi.java -text +forge-gui/src/main/java/forge/ai/ability/ProtectAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/PumpAi.java -text +forge-gui/src/main/java/forge/ai/ability/PumpAiBase.java -text +forge-gui/src/main/java/forge/ai/ability/PumpAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java -text +forge-gui/src/main/java/forge/ai/ability/RegenerateAi.java svneol=native#text/plain +forge-gui/src/main/java/forge/ai/ability/RegenerateAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/RemoveFromCombatAi.java -text +forge-gui/src/main/java/forge/ai/ability/RepeatAi.java -text +forge-gui/src/main/java/forge/ai/ability/RepeatEachAi.java -text +forge-gui/src/main/java/forge/ai/ability/RestartGameAi.java -text +forge-gui/src/main/java/forge/ai/ability/RevealAi.java -text +forge-gui/src/main/java/forge/ai/ability/RevealAiBase.java -text +forge-gui/src/main/java/forge/ai/ability/RevealHandAi.java -text +forge-gui/src/main/java/forge/ai/ability/RollPlanarDiceAi.java -text +forge-gui/src/main/java/forge/ai/ability/SacrificeAi.java -text +forge-gui/src/main/java/forge/ai/ability/SacrificeAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/ScryAi.java -text +forge-gui/src/main/java/forge/ai/ability/SetStateAi.java -text +forge-gui/src/main/java/forge/ai/ability/ShuffleAi.java -text +forge-gui/src/main/java/forge/ai/ability/StoreSVarAi.java -text +forge-gui/src/main/java/forge/ai/ability/TapAi.java -text +forge-gui/src/main/java/forge/ai/ability/TapAiBase.java -text +forge-gui/src/main/java/forge/ai/ability/TapAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/TapOrUntapAi.java -text +forge-gui/src/main/java/forge/ai/ability/TapOrUntapAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/TokenAi.java -text +forge-gui/src/main/java/forge/ai/ability/TwoPilesAi.java -text +forge-gui/src/main/java/forge/ai/ability/UnattachAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/UntapAi.java -text +forge-gui/src/main/java/forge/ai/ability/UntapAllAi.java -text +forge-gui/src/main/java/forge/ai/ability/ZoneExchangeAi.java -text forge-gui/src/main/java/forge/control/ChatArea.java -text forge-gui/src/main/java/forge/control/FControl.java -text forge-gui/src/main/java/forge/control/FControlGameEventHandler.java -text @@ -15224,6 +14846,374 @@ forge-gui/src/main/java/forge/deck/io/OldDeckParser.java -text forge-gui/src/main/java/forge/deck/io/package-info.java svneol=native#text/plain forge-gui/src/main/java/forge/error/ExceptionHandler.java svneol=native#text/plain forge-gui/src/main/java/forge/error/package-info.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/Game.java -text +forge-gui/src/main/java/forge/game/GameAction.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/GameActionUtil.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/GameEndReason.java -text +forge-gui/src/main/java/forge/game/GameEntity.java -text +forge-gui/src/main/java/forge/game/GameLog.java -text +forge-gui/src/main/java/forge/game/GameLogEntry.java -text +forge-gui/src/main/java/forge/game/GameLogEntryType.java -text +forge-gui/src/main/java/forge/game/GameLogFormatter.java -text +forge-gui/src/main/java/forge/game/GameObject.java -text +forge-gui/src/main/java/forge/game/GameOutcome.java -text +forge-gui/src/main/java/forge/game/GameStage.java -text +forge-gui/src/main/java/forge/game/GameType.java -text +forge-gui/src/main/java/forge/game/GlobalRuleChange.java -text +forge-gui/src/main/java/forge/game/Match.java -text +forge-gui/src/main/java/forge/game/PlanarDice.java -text +forge-gui/src/main/java/forge/game/StaticEffect.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/StaticEffects.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/TriggerReplacementBase.java -text +forge-gui/src/main/java/forge/game/ability/AbilityApiBased.java -text +forge-gui/src/main/java/forge/game/ability/AbilityFactory.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/ability/AbilityUtils.java -text +forge-gui/src/main/java/forge/game/ability/ApiType.java -text +forge-gui/src/main/java/forge/game/ability/SaTargetRoutines.java -text +forge-gui/src/main/java/forge/game/ability/SpellAbilityEffect.java -text +forge-gui/src/main/java/forge/game/ability/SpellApiBased.java -text +forge-gui/src/main/java/forge/game/ability/StaticAbilityApiBased.java -text +forge-gui/src/main/java/forge/game/ability/effects/AbandonEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/AddPhaseEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/AddTurnEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/AnimateAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/AnimateEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/AnimateEffectBase.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/ability/effects/AttachEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/BalanceEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/BondEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/CharmEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ChooseCardEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ChooseColorEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ChoosePlayerEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ChooseSourceEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ChooseTypeEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ClashEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/CleanUpEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/CloneEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ControlGainEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/CounterEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/CountersMoveEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/CountersPutEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/CountersRemoveAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DamageAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DamageDealEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DamageEachEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DamagePreventAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DamagePreventEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DebuffAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DebuffEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DeclareCombatantsEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DestroyAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DestroyEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DigEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DigUntilEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DiscardEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DrainManaEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/DrawEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ETBReplacementEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/EffectEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/EncodeEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/EndTurnEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/FightEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/FlipCoinEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/FogEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/GameLossEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/GameWinEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/LifeExchangeEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/LifeGainEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/LifeLoseEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/LifeSetEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ManaEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/MillEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/MustAttackEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/MustBlockEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/OwnershipGainEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/PeekAndRevealEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/PermanentCreatureEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/PhasesEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/PlaneswalkEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/PlayEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/PoisonEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/PowerExchangeEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ProtectAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ProtectEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/PumpAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/PumpEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/RearrangeTopOfLibraryEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/RegenerateEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/RepeatEachEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/RepeatEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/RestartGameEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/RevealEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/RevealHandEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/RollPlanarDiceEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/RunSVarAbilityEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/SacrificeEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ScryEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/SetInMotionEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/SetStateEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ShuffleEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/SkipTurnEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/StoreSVarEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/TapAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/TapEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/TapOrUntapAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/TapOrUntapEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/TokenEffect.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/ability/effects/TwoPilesEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/UnattachAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/UntapAllEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/UntapEffect.java -text +forge-gui/src/main/java/forge/game/ability/effects/ZoneExchangeEffect.java -text +forge-gui/src/main/java/forge/game/ability/package-info.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/card/Card.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/card/CardCharacteristics.java -text +forge-gui/src/main/java/forge/game/card/CardColor.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/card/CardDamageHistory.java -text +forge-gui/src/main/java/forge/game/card/CardFactory.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/card/CardFactoryUtil.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/card/CardKeywords.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/card/CardLists.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/card/CardPowerToughness.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/card/CardPredicates.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/card/CardShields.java -text +forge-gui/src/main/java/forge/game/card/CardType.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/card/CardUtil.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/card/CounterType.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/card/package-info.java -text +forge-gui/src/main/java/forge/game/combat/AttackingBand.java -text +forge-gui/src/main/java/forge/game/combat/Combat.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/combat/CombatLki.java -text +forge-gui/src/main/java/forge/game/combat/CombatUtil.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/cost/Cost.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/cost/CostAddMana.java -text +forge-gui/src/main/java/forge/game/cost/CostChooseCreatureType.java -text +forge-gui/src/main/java/forge/game/cost/CostDamage.java -text +forge-gui/src/main/java/forge/game/cost/CostDecisionMakerBase.java -text +forge-gui/src/main/java/forge/game/cost/CostDiscard.java -text +forge-gui/src/main/java/forge/game/cost/CostDraw.java -text +forge-gui/src/main/java/forge/game/cost/CostExile.java -text +forge-gui/src/main/java/forge/game/cost/CostExiledMoveToGrave.java -text +forge-gui/src/main/java/forge/game/cost/CostFlipCoin.java -text +forge-gui/src/main/java/forge/game/cost/CostGainControl.java -text +forge-gui/src/main/java/forge/game/cost/CostGainLife.java -text +forge-gui/src/main/java/forge/game/cost/CostMill.java -text +forge-gui/src/main/java/forge/game/cost/CostPart.java -text +forge-gui/src/main/java/forge/game/cost/CostPartMana.java -text +forge-gui/src/main/java/forge/game/cost/CostPartWithList.java -text +forge-gui/src/main/java/forge/game/cost/CostPayLife.java -text +forge-gui/src/main/java/forge/game/cost/CostPayment.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/cost/CostPutCardToLib.java -text +forge-gui/src/main/java/forge/game/cost/CostPutCounter.java -text +forge-gui/src/main/java/forge/game/cost/CostRemoveAnyCounter.java -text +forge-gui/src/main/java/forge/game/cost/CostRemoveCounter.java -text +forge-gui/src/main/java/forge/game/cost/CostReturn.java -text +forge-gui/src/main/java/forge/game/cost/CostReveal.java -text +forge-gui/src/main/java/forge/game/cost/CostSacrifice.java -text +forge-gui/src/main/java/forge/game/cost/CostTap.java -text +forge-gui/src/main/java/forge/game/cost/CostTapType.java -text +forge-gui/src/main/java/forge/game/cost/CostUnattach.java -text +forge-gui/src/main/java/forge/game/cost/CostUntap.java -text +forge-gui/src/main/java/forge/game/cost/CostUntapType.java -text +forge-gui/src/main/java/forge/game/cost/ICostVisitor.java -text +forge-gui/src/main/java/forge/game/cost/PaymentDecision.java -text +forge-gui/src/main/java/forge/game/cost/package-info.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/event/EventValueChangeType.java -text +forge-gui/src/main/java/forge/game/event/GameEvent.java -text +forge-gui/src/main/java/forge/game/event/GameEventAnteCardsSelected.java -text +forge-gui/src/main/java/forge/game/event/GameEventAttackersDeclared.java -text +forge-gui/src/main/java/forge/game/event/GameEventBlockersDeclared.java -text +forge-gui/src/main/java/forge/game/event/GameEventCardAttachment.java -text +forge-gui/src/main/java/forge/game/event/GameEventCardChangeZone.java -text +forge-gui/src/main/java/forge/game/event/GameEventCardCounters.java -text +forge-gui/src/main/java/forge/game/event/GameEventCardDamaged.java -text +forge-gui/src/main/java/forge/game/event/GameEventCardDestroyed.java -text +forge-gui/src/main/java/forge/game/event/GameEventCardPhased.java -text +forge-gui/src/main/java/forge/game/event/GameEventCardRegenerated.java -text +forge-gui/src/main/java/forge/game/event/GameEventCardSacrificed.java -text +forge-gui/src/main/java/forge/game/event/GameEventCardStatsChanged.java -text +forge-gui/src/main/java/forge/game/event/GameEventCardTapped.java -text +forge-gui/src/main/java/forge/game/event/GameEventCombatEnded.java -text +forge-gui/src/main/java/forge/game/event/GameEventFlipCoin.java -text +forge-gui/src/main/java/forge/game/event/GameEventGameFinished.java -text +forge-gui/src/main/java/forge/game/event/GameEventGameOutcome.java -text +forge-gui/src/main/java/forge/game/event/GameEventGameRestarted.java -text +forge-gui/src/main/java/forge/game/event/GameEventGameStarted.java -text +forge-gui/src/main/java/forge/game/event/GameEventLandPlayed.java -text +forge-gui/src/main/java/forge/game/event/GameEventManaBurn.java -text +forge-gui/src/main/java/forge/game/event/GameEventManaPool.java -text +forge-gui/src/main/java/forge/game/event/GameEventMulligan.java -text +forge-gui/src/main/java/forge/game/event/GameEventPlayerControl.java -text +forge-gui/src/main/java/forge/game/event/GameEventPlayerDamaged.java -text +forge-gui/src/main/java/forge/game/event/GameEventPlayerLivesChanged.java -text +forge-gui/src/main/java/forge/game/event/GameEventPlayerPoisoned.java -text +forge-gui/src/main/java/forge/game/event/GameEventPlayerPriority.java -text +forge-gui/src/main/java/forge/game/event/GameEventShuffle.java -text +forge-gui/src/main/java/forge/game/event/GameEventSpellAbilityCast.java -text +forge-gui/src/main/java/forge/game/event/GameEventSpellRemovedFromStack.java -text +forge-gui/src/main/java/forge/game/event/GameEventSpellResolved.java -text +forge-gui/src/main/java/forge/game/event/GameEventTokenCreated.java -text +forge-gui/src/main/java/forge/game/event/GameEventTurnBegan.java -text +forge-gui/src/main/java/forge/game/event/GameEventTurnEnded.java -text +forge-gui/src/main/java/forge/game/event/GameEventTurnPhase.java -text +forge-gui/src/main/java/forge/game/event/GameEventZone.java -text +forge-gui/src/main/java/forge/game/event/IGameEventVisitor.java -text +forge-gui/src/main/java/forge/game/event/package-info.java -text +forge-gui/src/main/java/forge/game/mana/Mana.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/mana/ManaCostBeingPaid.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/mana/ManaPool.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/mana/package-info.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/package-info.java -text +forge-gui/src/main/java/forge/game/phase/EndOfTurn.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/phase/ExtraTurn.java -text +forge-gui/src/main/java/forge/game/phase/Phase.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/phase/PhaseHandler.java -text +forge-gui/src/main/java/forge/game/phase/PhaseType.java -text +forge-gui/src/main/java/forge/game/phase/Untap.java -text +forge-gui/src/main/java/forge/game/phase/Upkeep.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/phase/package-info.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/player/GameLossReason.java -text +forge-gui/src/main/java/forge/game/player/LobbyPlayer.java -text +forge-gui/src/main/java/forge/game/player/LobbyPlayerAi.java -text +forge-gui/src/main/java/forge/game/player/LobbyPlayerRemote.java -text +forge-gui/src/main/java/forge/game/player/Player.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/player/PlayerActionConfirmMode.java -text +forge-gui/src/main/java/forge/game/player/PlayerController.java -text +forge-gui/src/main/java/forge/game/player/PlayerControllerAi.java -text +forge-gui/src/main/java/forge/game/player/PlayerOutcome.java -text +forge-gui/src/main/java/forge/game/player/PlayerStatistics.java -text +forge-gui/src/main/java/forge/game/player/RegisteredPlayer.java -text +forge-gui/src/main/java/forge/game/player/package-info.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/replacement/ReplaceAddCounter.java -text +forge-gui/src/main/java/forge/game/replacement/ReplaceCounter.java -text +forge-gui/src/main/java/forge/game/replacement/ReplaceDamage.java -text +forge-gui/src/main/java/forge/game/replacement/ReplaceDestroy.java -text +forge-gui/src/main/java/forge/game/replacement/ReplaceDiscard.java -text +forge-gui/src/main/java/forge/game/replacement/ReplaceDraw.java -text +forge-gui/src/main/java/forge/game/replacement/ReplaceGainLife.java -text +forge-gui/src/main/java/forge/game/replacement/ReplaceGameLoss.java -text +forge-gui/src/main/java/forge/game/replacement/ReplaceMoved.java -text +forge-gui/src/main/java/forge/game/replacement/ReplaceProduceMana.java -text +forge-gui/src/main/java/forge/game/replacement/ReplaceSetInMotion.java -text +forge-gui/src/main/java/forge/game/replacement/ReplaceTurnFaceUp.java -text +forge-gui/src/main/java/forge/game/replacement/ReplaceUntap.java -text +forge-gui/src/main/java/forge/game/replacement/ReplacementEffect.java -text +forge-gui/src/main/java/forge/game/replacement/ReplacementHandler.java -text +forge-gui/src/main/java/forge/game/replacement/ReplacementLayer.java -text +forge-gui/src/main/java/forge/game/replacement/ReplacementResult.java -text +forge-gui/src/main/java/forge/game/replacement/ReplacementType.java -text +forge-gui/src/main/java/forge/game/replacement/package-info.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/Ability.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/AbilityActivated.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/AbilityManaPart.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/AbilityStatic.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/AbilitySub.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/AbilityTriggered.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/ISpellAbility.java -text +forge-gui/src/main/java/forge/game/spellability/OptionalCost.java -text +forge-gui/src/main/java/forge/game/spellability/Spell.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/SpellAbility.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/SpellAbilityCondition.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/SpellAbilityRestriction.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/SpellAbilityVariables.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/SpellPermanent.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/TargetChoices.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/TargetRestrictions.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/spellability/package-info.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/staticability/StaticAbility.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java -text +forge-gui/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java -text +forge-gui/src/main/java/forge/game/staticability/StaticAbilityContinuous.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/staticability/StaticAbilityCostChange.java -text +forge-gui/src/main/java/forge/game/staticability/StaticAbilityETBTapped.java -text +forge-gui/src/main/java/forge/game/staticability/StaticAbilityMayLookAt.java -text +forge-gui/src/main/java/forge/game/staticability/StaticAbilityPreventDamage.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/staticability/package-info.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/Trigger.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerAlways.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerAttached.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerAttackerUnblocked.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerAttackersDeclared.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerAttacks.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerBecomeMonstrous.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerBecomesTarget.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerBlockersDeclared.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerBlocks.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerChampioned.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerChangesController.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerChangesZone.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerClashed.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerCombatDamageDoneOnce.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerCounterAdded.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerCounterRemoved.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerCountered.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerCycled.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerDamageDone.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerDestroyed.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerDevoured.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerDiscarded.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerDrawn.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerEvolved.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerFlippedCoin.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerHandler.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerLandPlayed.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerLifeGained.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerLifeLost.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerLosesGame.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerNewGame.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerPayCumulativeUpkeep.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerPhase.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerPlanarDice.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerPlaneswalkedFrom.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerPlaneswalkedTo.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerSacrificed.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerScry.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerSetInMotion.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerShuffled.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerSpellAbilityCast.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerTaps.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerTapsForMana.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerTransformed.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerTurnFaceUp.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerType.java -text +forge-gui/src/main/java/forge/game/trigger/TriggerUnequip.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerUntaps.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/TriggerWaiting.java -text +forge-gui/src/main/java/forge/game/trigger/WrappedAbility.java -text +forge-gui/src/main/java/forge/game/trigger/ZCTrigger.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/trigger/package-info.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/zone/MagicStack.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/zone/PlayerZone.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/zone/PlayerZoneBattlefield.java svneol=native#text/plain +forge-gui/src/main/java/forge/game/zone/Zone.java -text +forge-gui/src/main/java/forge/game/zone/ZoneType.java -text +forge-gui/src/main/java/forge/game/zone/package-info.java svneol=native#text/plain forge-gui/src/main/java/forge/gauntlet/GauntletData.java -text forge-gui/src/main/java/forge/gauntlet/GauntletIO.java -text forge-gui/src/main/java/forge/gui/CardContainer.java svneol=native#text/plain @@ -15584,7 +15574,6 @@ forge-gui/src/main/java/forge/net/FServer.java -text forge-gui/src/main/java/forge/net/IClientSocket.java -text forge-gui/src/main/java/forge/net/IConnectionObserver.java -text forge-gui/src/main/java/forge/net/Lobby.java -text -forge-gui/src/main/java/forge/net/LobbyPlayerRemote.java -text forge-gui/src/main/java/forge/net/NetServer.java -text forge-gui/src/main/java/forge/net/client/INetClient.java -text forge-gui/src/main/java/forge/net/client/InvalidFieldInPacketException.java -text @@ -15679,13 +15668,21 @@ forge-gui/src/main/java/forge/sound/SoundSystem.java -text forge-gui/src/main/java/forge/util/AwtUtil.java -text forge-gui/src/main/java/forge/util/Base64Coder.java svneol=native#text/plain forge-gui/src/main/java/forge/util/Evaluator.java -text +forge-gui/src/main/java/forge/util/Expressions.java -text forge-gui/src/main/java/forge/util/HttpUtil.java svneol=native#text/plain forge-gui/src/main/java/forge/util/IgnoringXStream.java -text forge-gui/src/main/java/forge/util/LineReader.java -text forge-gui/src/main/java/forge/util/MultiplexOutputStream.java svneol=native#text/plain forge-gui/src/main/java/forge/util/NameGenerator.java -text forge-gui/src/main/java/forge/util/OperatingSystem.java -text +forge-gui/src/main/java/forge/util/ReflectionUtil.java -text forge-gui/src/main/java/forge/util/XmlUtil.java -text +forge-gui/src/main/java/forge/util/maps/EnumMapOfLists.java -text +forge-gui/src/main/java/forge/util/maps/EnumMapToAmount.java -text +forge-gui/src/main/java/forge/util/maps/HashMapOfLists.java -text +forge-gui/src/main/java/forge/util/maps/MapOfLists.java -text +forge-gui/src/main/java/forge/util/maps/MapToAmount.java -text +forge-gui/src/main/java/forge/util/maps/package-info.java -text forge-gui/src/main/java/forge/util/package-info.java -text forge-gui/src/main/java/forge/view/ButtonUtil.java svneol=native#text/plain forge-gui/src/main/java/forge/view/CardReaderExperiments.java -text diff --git a/forge-ai/pom.xml b/forge-ai/pom.xml index 3f5cc7a4880..088df599180 100644 --- a/forge-ai/pom.xml +++ b/forge-ai/pom.xml @@ -18,6 +18,11 @@ forge-core ${project.version} + + forge + forge-game + ${project.version} + diff --git a/forge-game/pom.xml b/forge-game/pom.xml index 75e444fec85..370cd0d8adc 100644 --- a/forge-game/pom.xml +++ b/forge-game/pom.xml @@ -18,15 +18,5 @@ forge-core ${project.version} - - com.googlecode - minlog - 1.2 - - - forge - forge-ai - ${project.version} - diff --git a/forge-game/src/main/java/forge/ImageCacheBridge.java b/forge-game/src/main/java/forge/ImageCacheBridge.java deleted file mode 100644 index cfd0e59fbc7..00000000000 --- a/forge-game/src/main/java/forge/ImageCacheBridge.java +++ /dev/null @@ -1,14 +0,0 @@ -package forge; - -import forge.item.InventoryItem; - -public class ImageCacheBridge { - - public static Methods instance; - - public interface Methods { - String getImageKey(InventoryItem cp, boolean altState); - String getTokenKey(String imageName); - String getMorphImage(); - } -} \ No newline at end of file diff --git a/forge-game/src/main/java/forge/PreferencesBridge.java b/forge-game/src/main/java/forge/PreferencesBridge.java deleted file mode 100644 index 19cdc877f1e..00000000000 --- a/forge-game/src/main/java/forge/PreferencesBridge.java +++ /dev/null @@ -1,25 +0,0 @@ -package forge; - - -public class PreferencesBridge { - public static PreferencesSet Instance; - - public interface PreferencesSet { - - public abstract boolean getEnableAiCheats(); - - public abstract boolean getCloneModeSource(); - - public abstract String getLogEntryType(); - - public abstract String getCurrentAiProfile(); - - public abstract boolean canRandomFoil(); - - public abstract boolean isManaBurnEnabled(); - - public abstract boolean areBlocksFree(); - - } - -} \ No newline at end of file diff --git a/forge-game/src/main/java/forge/game/package-info.java b/forge-game/src/main/java/forge/game/package-info.java index c1a6d89a2c5..8921a7477e3 100644 --- a/forge-game/src/main/java/forge/game/package-info.java +++ b/forge-game/src/main/java/forge/game/package-info.java @@ -1,3 +1,8 @@ -/** Forge Card Game. */ -package forge.game; - +/** + * + */ +/** + * @author Max + * + */ +package forge.game; \ No newline at end of file diff --git a/forge-game/src/main/java/forge/Command.java b/forge-gui/src/main/java/forge/Command.java similarity index 91% rename from forge-game/src/main/java/forge/Command.java rename to forge-gui/src/main/java/forge/Command.java index 4ae9fa26ec4..b3ab1d42115 100644 --- a/forge-game/src/main/java/forge/Command.java +++ b/forge-gui/src/main/java/forge/Command.java @@ -1,39 +1,39 @@ -/* - * 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 . - */ -package forge; - -/** - *

- * Command interface. - *

- * - * @author Forge - * @version $Id: Command.java 21051 2013-04-17 11:48:06Z Max mtg $ - */ -public interface Command extends java.io.Serializable, Runnable { - /** Constant Blank. */ - public final Command BLANK = new Command() { - - private static final long serialVersionUID = 2689172297036001710L; - - @Override - public void run() { - } - - }; -} +/* + * 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 . + */ +package forge; + +/** + *

+ * Command interface. + *

+ * + * @author Forge + * @version $Id$ + */ +public interface Command extends java.io.Serializable, Runnable { + /** Constant Blank. */ + public final Command BLANK = new Command() { + + private static final long serialVersionUID = 2689172297036001710L; + + @Override + public void run() { + } + + }; +} diff --git a/forge-game/src/main/java/forge/Constant.java b/forge-gui/src/main/java/forge/Constant.java similarity index 93% rename from forge-game/src/main/java/forge/Constant.java rename to forge-gui/src/main/java/forge/Constant.java index d849cc2e66b..a2225037c7b 100644 --- a/forge-game/src/main/java/forge/Constant.java +++ b/forge-gui/src/main/java/forge/Constant.java @@ -1,71 +1,71 @@ -/* - * 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 . - */ -package forge; - -import java.util.ArrayList; -import java.util.List; - -/** - *

- * Constant interface. - *

- * - * @author Forge - * @version $Id: Constant.java 23713 2013-11-20 08:34:37Z Max mtg $ - */ -public final class Constant { - // used to pass information between the GUI screens - /** - * The Class Runtime. - */ - public static class Preferences { - /** The Constant DevMode. */ - // one for normal mode, one for quest mode - public static boolean DEV_MODE; - /** The Constant UpldDrft. */ - public static boolean UPLOAD_DRAFT; - - } - - public static class Runtime { - /** The Constant NetConn. */ - public static volatile boolean NET_CONN = false; - - /** The Constant width. */ - public static final int WIDTH = 300; - - /** The Constant height. */ - public static final int HEIGHT = 0; - } - - - /** - * The Interface Keywords. - */ - public static class Keywords { - - /** The loaded. */ - public static final boolean[] LOADED = { false }; - - /** The Non stacking list. */ - public static final List NON_STACKING_LIST = new ArrayList(); - } - -} // Constant - - +/* + * 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 . + */ +package forge; + +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * Constant interface. + *

+ * + * @author Forge + * @version $Id$ + */ +public final class Constant { + // used to pass information between the GUI screens + /** + * The Class Runtime. + */ + public static class Preferences { + /** The Constant DevMode. */ + // one for normal mode, one for quest mode + public static boolean DEV_MODE; + /** The Constant UpldDrft. */ + public static boolean UPLOAD_DRAFT; + + } + + public static class Runtime { + /** The Constant NetConn. */ + public static volatile boolean NET_CONN = false; + + /** The Constant width. */ + public static final int WIDTH = 300; + + /** The Constant height. */ + public static final int HEIGHT = 0; + } + + + /** + * The Interface Keywords. + */ + public static class Keywords { + + /** The loaded. */ + public static final boolean[] LOADED = { false }; + + /** The Non stacking list. */ + public static final List NON_STACKING_LIST = new ArrayList(); + } + +} // Constant + + diff --git a/forge-gui/src/main/java/forge/ImageCache.java b/forge-gui/src/main/java/forge/ImageCache.java index 039c83af079..1c2d4ccc046 100644 --- a/forge-gui/src/main/java/forge/ImageCache.java +++ b/forge-gui/src/main/java/forge/ImageCache.java @@ -111,7 +111,7 @@ public class ImageCache { } return scaleImage(key, width, height, true); } - + /** * retrieve an image from the cache. returns null if the image is not found in the cache * and cannot be loaded from disk. pass -1 for width and/or height to avoid resizing in that dimension. @@ -250,12 +250,6 @@ public class ImageCache { return null; } - public static String getTokenImageKey(String tokenName) { - return TOKEN_PREFIX + tokenName; - } - - - private static String getImageLocator(PaperCard cp, boolean backFace, boolean includeSet, boolean isDownloadUrl) { final String nameToUse = getNameToUse(cp, backFace); if ( null == nameToUse ) diff --git a/forge-gui/src/main/java/forge/ImageCacheProvider.java b/forge-gui/src/main/java/forge/ImageCacheProvider.java deleted file mode 100644 index 081a0f9a69b..00000000000 --- a/forge-gui/src/main/java/forge/ImageCacheProvider.java +++ /dev/null @@ -1,22 +0,0 @@ -package forge; - -import forge.item.InventoryItem; -import forge.properties.NewConstants; - -public class ImageCacheProvider implements ImageCacheBridge.Methods { - - @Override - public String getImageKey(InventoryItem cp, boolean altState) { - return ImageCache.getImageKey(cp, altState); - } - - @Override - public String getTokenKey(String imageName) { - return ImageCache.getTokenImageKey(imageName); - } - - @Override - public String getMorphImage() { - return NewConstants.CACHE_MORPH_IMAGE_FILE; - } -} \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/PreferencesProvider.java b/forge-gui/src/main/java/forge/PreferencesProvider.java deleted file mode 100644 index 4c23292b928..00000000000 --- a/forge-gui/src/main/java/forge/PreferencesProvider.java +++ /dev/null @@ -1,45 +0,0 @@ -package forge; - -import forge.PreferencesBridge.PreferencesSet; -import forge.properties.ForgePreferences.FPref; - -public class PreferencesProvider implements PreferencesSet { - @Override - public boolean getEnableAiCheats() { - return Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_ENABLE_AI_CHEATS); - } - - - @Override - public boolean getCloneModeSource() { - return Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_CLONE_MODE_SOURCE); - } - - @Override - public String getLogEntryType() { - return Singletons.getModel().getPreferences().getPref(FPref.DEV_LOG_ENTRY_TYPE); - } - - - @Override - public String getCurrentAiProfile() { - return Singletons.getModel().getPreferences().getPref(FPref.UI_CURRENT_AI_PROFILE); - } - - @Override - public boolean canRandomFoil() { - return Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_RANDOM_FOIL); - } - - - @Override - public boolean isManaBurnEnabled() { - // TODO Auto-generated method stub - return Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_MANABURN); - } - - @Override - public boolean areBlocksFree() { - return Singletons.getModel().getPreferences().getPrefBoolean(FPref.MATCHPREF_PROMPT_FREE_BLOCKS); - } -} \ No newline at end of file diff --git a/forge-game/src/main/java/forge/ai/AiAttackController.java b/forge-gui/src/main/java/forge/ai/AiAttackController.java similarity index 97% rename from forge-game/src/main/java/forge/ai/AiAttackController.java rename to forge-gui/src/main/java/forge/ai/AiAttackController.java index b2b8b35c17f..34fe742c66e 100644 --- a/forge-game/src/main/java/forge/ai/AiAttackController.java +++ b/forge-gui/src/main/java/forge/ai/AiAttackController.java @@ -1,1021 +1,1021 @@ -/* - * 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 . - */ -package forge.ai; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -import com.google.common.base.Predicate; -import com.google.common.collect.Lists; - -import forge.game.GameEntity; -import forge.game.card.Card; -import forge.game.card.CardLists; -import forge.game.card.CounterType; -import forge.game.combat.Combat; -import forge.game.combat.CombatUtil; -import forge.game.player.Player; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerType; -import forge.game.zone.ZoneType; - - -//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer() -/** - *

- * ComputerUtil_Attack2 class. - *

- * - * @author Forge - * @version $Id: AiAttackController.java 24355 2014-01-19 01:33:24Z swordshine $ - */ -public class AiAttackController { - - // possible attackers and blockers - private final List attackers; - private final List blockers; - - private final static Random random = new Random(); - private final static int randomInt = random.nextInt(); - - private List oppList; // holds human player creatures - private List myList; // holds computer creatures - - private final Player ai; - - private int aiAggression = 0; // added by Masher, how aggressive the ai is - // attack will be depending on circumstances - - - /** - *

- * Constructor for ComputerUtil_Attack2. - *

- * - * @param possibleAttackers - * a {@link forge.CardList} object. - * @param possibleBlockers - * a {@link forge.CardList} object. - */ - public AiAttackController(final Player ai) { - this.ai = ai; - Player opponent = ai.getOpponent(); - - this.oppList = Lists.newArrayList(); - this.oppList.addAll(opponent.getCreaturesInPlay()); - this.myList = ai.getCreaturesInPlay(); - - - this.attackers = new ArrayList(); - for (Card c : myList) { - if (CombatUtil.canAttack(c, opponent)) { - attackers.add(c); - } - } - this.blockers = this.getPossibleBlockers(oppList, this.attackers); - } // constructor - - /** - *

- * sortAttackers. - *

- * - * @param in - * a {@link forge.CardList} object. - * @return a {@link forge.CardList} object. - */ - public final List sortAttackers(final List in) { - final List list = new ArrayList(); - - // Cards with triggers should come first (for Battle Cry) - for (final Card attacker : in) { - for (final Trigger trigger : attacker.getTriggers()) { - if (trigger.getMode() == TriggerType.Attacks) { - list.add(attacker); - break; - } - } - } - - for (final Card attacker : in) { - if (!list.contains(attacker)) { - list.add(attacker); - } - } - - return list; - } // sortAttackers() - - // Is there any reward for attacking? (for 0/1 creatures there is not) - /** - *

- * isEffectiveAttacker. - *

- * - * @param attacker - * a {@link forge.game.card.Card} object. - * @param combat - * a {@link forge.game.combat.Combat} object. - * @return a boolean. - */ - public final boolean isEffectiveAttacker(final Player ai, final Card attacker, final Combat combat) { - - // if the attacker will die when attacking don't attack - if ((attacker.getNetDefense() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) { - return false; - } - - final Player opp = ai.getOpponent(); - if (ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat) > 0) { - return true; - } - if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) { - return true; - } - if (this.attackers.size() == 1 && attacker.hasKeyword("Exalted") && ComputerUtilCombat.predictDamageTo(opp, 1, attacker, true) > 0) { - return true; - } - - final List controlledByCompy = ai.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES); - for (final Card c : controlledByCompy) { - for (final Trigger trigger : c.getTriggers()) { - if (ComputerUtilCombat.combatTriggerWillTrigger(attacker, null, trigger, combat)) { - return true; - } - } - } - - return false; - } - - /** - *

- * getPossibleBlockers. - *

- * - * @param blockers - * a {@link forge.CardList} object. - * @param attackers - * a {@link forge.CardList} object. - * @return a {@link forge.CardList} object. - */ - public final List getPossibleBlockers(final List blockers, final List attackers) { - List possibleBlockers = new ArrayList(blockers); - possibleBlockers = CardLists.filter(possibleBlockers, new Predicate() { - @Override - public boolean apply(final Card c) { - return canBlockAnAttacker(c, attackers); - } - }); - return possibleBlockers; - } // getPossibleBlockers() - - /** - *

- * canBlockAnAttacker. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param attackers - * a {@link forge.CardList} object. - * @return a boolean. - */ - public final boolean canBlockAnAttacker(final Card c, final List attackers) { - final List attackerList = new ArrayList(attackers); - if (!c.isCreature()) { - return false; - } - for (final Card attacker : attackerList) { - if (CombatUtil.canBlock(attacker, c)) { - return true; - } - } - return false; - } // getPossibleBlockers() - - // this checks to make sure that the computer player - // doesn't lose when the human player attacks - // this method is used by getAttackers() - /** - *

- * notNeededAsBlockers. - *

- * - * @param attackers - * a {@link forge.CardList} object. - * @param combat - * a {@link forge.game.combat.Combat} object. - * @return a {@link forge.CardList} object. - */ - public final List notNeededAsBlockers(final Player ai, final List attackers) { - final List notNeededAsBlockers = new ArrayList(attackers); - int fixedBlockers = 0; - final List vigilantes = new ArrayList(); - //check for time walks - if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) { - return attackers; - } - for (final Card c : this.myList) { - if (c.getName().equals("Masako the Humorless")) { - // "Tapped creatures you control can block as though they were untapped." - return attackers; - } - if (!attackers.contains(c)) { // this creature can't attack anyway - if (canBlockAnAttacker(c, this.oppList)) { - fixedBlockers++; - } - continue; - } - if (c.hasKeyword("Vigilance")) { - vigilantes.add(c); - notNeededAsBlockers.remove(c); // they will be re-added later - if (canBlockAnAttacker(c, this.oppList)) { - fixedBlockers++; - } - } - } - CardLists.sortByPowerAsc(attackers); - int blockersNeeded = this.oppList.size(); - - // don't hold back creatures that can't block any of the human creatures - final List list = this.getPossibleBlockers(attackers, this.oppList); - - //Calculate the amount of creatures necessary - for (int i = 0; i < list.size(); i++) { - if (!this.doesHumanAttackAndWin(ai, i)) { - blockersNeeded = i; - break; - } - } - int blockersStillNeeded = blockersNeeded - fixedBlockers; - blockersStillNeeded = Math.min(blockersNeeded, list.size()); - for (int i = 0; i < blockersStillNeeded; i++) { - notNeededAsBlockers.remove(list.get(i)); - } - - // re-add creatures with vigilance - notNeededAsBlockers.addAll(vigilantes); - - if (blockersNeeded > 1) { - return notNeededAsBlockers; - } - - final Player opp = ai.getOpponent(); - - // Increase the total number of blockers needed by 1 if Finest Hour in - // play - // (human will get an extra first attack with a creature that untaps) - // In addition, if the computer guesses it needs no blockers, make sure - // that - // it won't be surprised by Exalted - final int humanExaltedBonus = this.countExaltedBonus(opp); - - if (humanExaltedBonus > 0) { - final int nFinestHours = opp.getCardsIn(ZoneType.Battlefield, "Finest Hour").size(); - - if (((blockersNeeded == 0) || (nFinestHours > 0)) && (this.oppList.size() > 0)) { - // - // total attack = biggest creature + exalted, *2 if Rafiq is in - // play - int humanBaseAttack = this.getAttack(this.oppList.get(0)) + humanExaltedBonus; - if (nFinestHours > 0) { - // For Finest Hour, one creature could attack and get the - // bonus TWICE - humanBaseAttack = humanBaseAttack + humanExaltedBonus; - } - final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBaseAttack - : humanBaseAttack; - if (ai.getLife() - 3 <= totalExaltedAttack) { - // We will lose if there is an Exalted attack -- keep one - // blocker - if ((blockersNeeded == 0) && (notNeededAsBlockers.size() > 0)) { - notNeededAsBlockers.remove(0); - } - - // Finest Hour allows a second Exalted attack: keep a - // blocker for that too - if ((nFinestHours > 0) && (notNeededAsBlockers.size() > 0)) { - notNeededAsBlockers.remove(0); - } - } - } - } - return notNeededAsBlockers; - } - - // this uses a global variable, which isn't perfect - /** - *

- * doesHumanAttackAndWin. - *

- * - * @param nBlockingCreatures - * a int. - * @return a boolean. - */ - public final boolean doesHumanAttackAndWin(final Player ai, final int nBlockingCreatures) { - int totalAttack = 0; - int totalPoison = 0; - int blockersLeft = nBlockingCreatures; - - if (ai.cantLose()) { - return false; - } - - for (Card attacker : oppList) { - if (!CombatUtil.canAttackNextTurn(attacker)) { - continue; - } - if (blockersLeft > 0 && CombatUtil.canBeBlocked(attacker, ai)) { - blockersLeft--; - continue; - } - totalAttack += ComputerUtilCombat.damageIfUnblocked(attacker, ai, null); - totalPoison += ComputerUtilCombat.poisonIfUnblocked(attacker, ai); - } - - if (totalAttack > 0 && ai.getLife() <= totalAttack - && !ai.cantLoseForZeroOrLessLife()) { - return true; - } - return ai.getPoisonCounters() + totalPoison > 9; - } - - /** - *

- * doAssault. - *

- * - * @return a boolean. - */ - private boolean doAssault(final Player ai) { - // Beastmaster Ascension - if (ai.isCardInPlay("Beastmaster Ascension") - && (this.attackers.size() > 1)) { - final List beastions = ai.getCardsIn(ZoneType.Battlefield, "Beastmaster Ascension"); - int minCreatures = 7; - for (final Card beastion : beastions) { - final int counters = beastion.getCounters(CounterType.QUEST); - minCreatures = Math.min(minCreatures, 7 - counters); - } - if (this.attackers.size() >= minCreatures) { - return true; - } - } - - CardLists.sortByPowerDesc(this.attackers); - - final List unblockedAttackers = new ArrayList(); - final List remainingAttackers = new ArrayList(this.attackers); - final List remainingBlockers = new ArrayList(this.blockers); - final Player opp = ai.getOpponent(); - - - for (Card attacker : attackers) { - if (!CombatUtil.canBeBlocked(attacker, this.blockers, null) - || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { - unblockedAttackers.add(attacker); - } - } - - for (Card blocker : this.blockers) { - if (blocker.hasKeyword("CARDNAME can block any number of creatures.") - || blocker.hasKeyword("CARDNAME can block an additional ninety-nine creatures.")) { - for (Card attacker : this.attackers) { - if (CombatUtil.canBlock(attacker, blocker)) { - remainingAttackers.remove(attacker); - } - } - remainingBlockers.remove(blocker); - } - } - - // presumes the Human will block - for (Card blocker : remainingBlockers) { - if (remainingAttackers.isEmpty()) { - break; - } - if (blocker.hasKeyword("CARDNAME can block an additional creature.")) { - remainingAttackers.remove(0); - if (remainingAttackers.isEmpty()) { - break; - } - } - remainingAttackers.remove(0); - } - unblockedAttackers.addAll(remainingAttackers); - - if (ComputerUtilCombat.sumDamageIfUnblocked(remainingAttackers, opp) + ComputerUtil.possibleNonCombatDamage(ai) >= opp.getLife() - && !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && (opp.getLife() < 1))) { - return true; - } - - if (ComputerUtilCombat.sumPoisonIfUnblocked(remainingAttackers, opp) >= (10 - opp.getPoisonCounters())) { - return true; - } - - return false; - } // doAssault() - - /** - *

- * chooseDefender. - *

- * - * @param c - * a {@link forge.game.combat.Combat} object. - * @param bAssault - * a boolean. - */ - private final GameEntity chooseDefender(final Combat c, final boolean bAssault) { - final List defs = c.getDefenders(); - if (defs.size() == 1) { - return defs.get(0); - } - - final GameEntity entity = ai.getMustAttackEntity(); - if (null != entity) { - int n = defs.indexOf(entity); - if (-1 == n) { - System.out.println("getMustAttackEntity() returned something not in defenders."); - return defs.get(0); - } else { - return entity; - } - } else { - // 1. assault the opponent if you can kill him - if (bAssault) { - return getDefendingPlayers(c).get(0); - } - // 2. attack planeswalkers - List pwDefending = c.getDefendingPlaneswalkers(); - if (!pwDefending.isEmpty()) { - return pwDefending.get(0); - } else { - return defs.get(0); - } - } - } - - final boolean LOG_AI_ATTACKS = false; - - - public final List getDefendingPlayers(Combat combat) { - final List defending = new ArrayList(); - for (final GameEntity o : combat.getDefenders()) { - if (o instanceof Player) { - defending.add((Player) o); - } - } - return defending; - } - - - /** - *

- * Getter for the field attackers. - *

- * - * @return a {@link forge.game.combat.Combat} object. - */ - public final void declareAttackers(final Combat combat) { - // if this method is called multiple times during a turn, - // it will always return the same value - // randomInt is used so that the computer doesn't always - // do the same thing on turn 3 if he had the same creatures in play - // I know this is a little confusing - - random.setSeed(ai.getGame().getPhaseHandler().getTurn() + AiAttackController.randomInt); - - if (this.attackers.isEmpty()) { - return; - } - - final boolean bAssault = this.doAssault(ai); - // Determine who will be attacked - GameEntity defender = this.chooseDefender(combat, bAssault); - List attackersLeft = new ArrayList(this.attackers); - // Attackers that don't really have a choice - for (final Card attacker : this.attackers) { - if (!CombatUtil.canAttack(attacker, defender, combat)) { - continue; - } - boolean mustAttack = false; - for (String s : attacker.getKeyword()) { - if (s.equals("CARDNAME attacks each turn if able.") - || s.startsWith("CARDNAME attacks specific player each combat if able")) { - mustAttack = true; - break; - } - if ((s.equals("At the beginning of the end step, destroy CARDNAME.") - || s.equals("At the beginning of the end step, exile CARDNAME.") - || s.equals("At the beginning of the end step, sacrifice CARDNAME.")) - && isEffectiveAttacker(ai, attacker, combat)) { - mustAttack = true; - break; - } - } - if (mustAttack || attacker.getController().getMustAttackEntity() != null - || attacker.getSVar("MustAttack").equals("True")) { - combat.addAttacker(attacker, defender); - attackersLeft.remove(attacker); - } - } - if (attackersLeft.isEmpty()) { - return; - } - if (bAssault) { - if ( LOG_AI_ATTACKS ) - System.out.println("Assault"); - CardLists.sortByPowerDesc(attackersLeft); - for (Card attacker : attackersLeft) { - if (CombatUtil.canAttack(attacker, defender, combat) && this.isEffectiveAttacker(ai, attacker, combat)) { - combat.addAttacker(attacker, defender); - } - } - return; - } - - // Exalted - if (combat.getAttackers().isEmpty()) { - boolean exalted = false; - int exaltedCount = 0; - for (Card c : ai.getCardsIn(ZoneType.Battlefield)) { - if (c.getName().equals("Rafiq of the Many") || c.getName().equals("Battlegrace Angel")) { - exalted = true; - break; - } - if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) { - exalted = true; - break; - } - if (c.hasKeyword("Exalted")) { - exaltedCount++; - if (exaltedCount > 2) { - exalted = true; - break; - } - } - } - if (exalted) { - CardLists.sortByPowerDesc(this.attackers); - if ( LOG_AI_ATTACKS ) - System.out.println("Exalted"); - this.aiAggression = 6; - for (Card attacker : this.attackers) { - if (CombatUtil.canAttack(attacker, defender, combat) && this.shouldAttack(ai, attacker, this.blockers, combat)) { - combat.addAttacker(attacker, defender); - return; - } - } - } - } - - // ******************* - // Evaluate the creature forces - // ******************* - - int computerForces = 0; - int humanForces = 0; - int humanForcesForAttritionalAttack = 0; - - // examine the potential forces - final List nextTurnAttackers = new ArrayList(); - int candidateCounterAttackDamage = 0; - - for (final Card pCard : this.oppList) { - // if the creature can attack next turn add it to counter attackers - // list - if (CombatUtil.canAttackNextTurn(pCard)) { - nextTurnAttackers.add(pCard); - if (pCard.getNetCombatDamage() > 0) { - candidateCounterAttackDamage += pCard.getNetCombatDamage(); - humanForces += 1; // player forces they might use to attack - } - } - // increment player forces that are relevant to an attritional - // attack - includes walls - if (CombatUtil.canBlock(pCard, true)) { - humanForcesForAttritionalAttack += 1; - } - } - - // find the potential counter attacking damage compared to AI life total - double aiLifeToPlayerDamageRatio = 1000000; - if (candidateCounterAttackDamage > 0) { - aiLifeToPlayerDamageRatio = (double) ai.getLife() / candidateCounterAttackDamage; - } - - final Player opp = ai.getOpponent(); - // get the potential damage and strength of the AI forces - final List candidateAttackers = new ArrayList(); - int candidateUnblockedDamage = 0; - for (final Card pCard : this.myList) { - // if the creature can attack then it's a potential attacker this - // turn, assume summoning sickness creatures will be able to - if (CombatUtil.canAttackNextTurn(pCard)) { - candidateAttackers.add(pCard); - if (pCard.getNetCombatDamage() > 0) { - candidateUnblockedDamage += ComputerUtilCombat.damageIfUnblocked(pCard, opp, null); - computerForces += 1; - } - } - } - - // find the potential damage ratio the AI can cause - double humanLifeToDamageRatio = 1000000; - if (candidateUnblockedDamage > 0) { - humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai)) / candidateUnblockedDamage; - } - - // determine if the ai outnumbers the player - final int outNumber = computerForces - humanForces; - - for (Card blocker : this.blockers) { - if (blocker.hasKeyword("CARDNAME can block any number of creatures.")) { - aiLifeToPlayerDamageRatio--; - } - } - - // compare the ratios, higher = better for ai - final double ratioDiff = aiLifeToPlayerDamageRatio - humanLifeToDamageRatio; - - // ********************* - // if outnumber and superior ratio work out whether attritional all out - // attacking will work - // attritional attack will expect some creatures to die but to achieve - // victory by sheer weight - // of numbers attacking turn after turn. It's not calculate very - // carefully, the accuracy - // can probably be improved - // ********************* - boolean doAttritionalAttack = false; - // get list of attackers ordered from low power to high - CardLists.sortByPowerAsc(this.attackers); - // get player life total - int humanLife = opp.getLife(); - // get the list of attackers up to the first blocked one - final List attritionalAttackers = new ArrayList(); - for (int x = 0; x < (this.attackers.size() - humanForces); x++) { - attritionalAttackers.add(this.attackers.get(x)); - } - // until the attackers are used up or the player would run out of life - int attackRounds = 1; - while (attritionalAttackers.size() > 0 && humanLife > 0 && attackRounds < 99) { - // sum attacker damage - int damageThisRound = 0; - for (int y = 0; y < attritionalAttackers.size(); y++) { - damageThisRound += attritionalAttackers.get(y).getNetCombatDamage(); - } - // remove from player life - humanLife -= damageThisRound; - // shorten attacker list by the length of the blockers - assuming - // all blocked are killed for convenience - for (int z = 0; z < humanForcesForAttritionalAttack; z++) { - if (attritionalAttackers.size() > 0) { - attritionalAttackers.remove(attritionalAttackers.size() - 1); - } - } - attackRounds += 1; - if (humanLife <= 0) { - doAttritionalAttack = true; - } - } - // System.out.println(doAttritionalAttack + " = do attritional attack"); - // ********************* - // end attritional attack calculation - // ********************* - - // ********************* - // see how long until unblockable attackers will be fatal - // ********************* - double unblockableDamage = 0; - double nextUnblockableDamage = 0; - double turnsUntilDeathByUnblockable = 0; - boolean doUnblockableAttack = false; - for (final Card attacker : this.attackers) { - boolean isUnblockableCreature = true; - // check blockers individually, as the bulk canBeBlocked doesn't - // check all circumstances - for (final Card blocker : this.blockers) { - if (CombatUtil.canBlock(attacker, blocker)) { - isUnblockableCreature = false; - break; - } - } - if (isUnblockableCreature) { - unblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat); - } - } - for (final Card attacker : nextTurnAttackers) { - boolean isUnblockableCreature = true; - // check blockers individually, as the bulk canBeBlocked doesn't - // check all circumstances - for (final Card blocker : this.myList) { - if (CombatUtil.canBlock(attacker, blocker, true)) { - isUnblockableCreature = false; - break; - } - } - if (isUnblockableCreature) { - nextUnblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, opp, null); - } - } - if (unblockableDamage > 0 && !opp.cantLoseForZeroOrLessLife() - && opp.canLoseLife()) { - turnsUntilDeathByUnblockable = 1 + (opp.getLife() - unblockableDamage) / nextUnblockableDamage; - } - if (opp.canLoseLife()) { - doUnblockableAttack = true; - } - // ***************** - // end see how long until unblockable attackers will be fatal - // ***************** - - // decide on attack aggression based on a comparison of forces, life - // totals and other considerations - // some bad "magic numbers" here, TODO replace with nice descriptive - // variable names - if (ratioDiff > 0 && doAttritionalAttack) { - this.aiAggression = 5; // attack at all costs - } else if (ratioDiff >= 1 && (humanLifeToDamageRatio < 2 || outNumber > 0)) { - this.aiAggression = 4; // attack expecting to trade or damage player. - } else if (ratioDiff >= 0) { - this.aiAggression = 3; // attack expecting to make good trades or damage player. - } else if (ratioDiff + outNumber >= -1 || aiLifeToPlayerDamageRatio > 1 - || ratioDiff * -1 < turnsUntilDeathByUnblockable) { - // at 0 ratio expect to potentially gain an advantage by attacking first - // if the ai has a slight advantage - // or the ai has a significant advantage numerically but only a slight disadvantage damage/life - this.aiAggression = 2; // attack expecting to destroy creatures/be unblockable - } else if (doUnblockableAttack) { - this.aiAggression = 1; - // look for unblockable creatures that might be - // able to attack for a bit of fatal damage even if the player is significantly better - } else { - this.aiAggression = 0; - } // stay at home to block - - if ( LOG_AI_ATTACKS ) - System.out.println(String.valueOf(this.aiAggression) + " = ai aggression"); - - // **************** - // Evaluation the end - // **************** - - if ( LOG_AI_ATTACKS ) - System.out.println("Normal attack"); - - attackersLeft = this.notNeededAsBlockers(ai, attackersLeft); - attackersLeft = this.sortAttackers(attackersLeft); - - if ( LOG_AI_ATTACKS ) - System.out.println("attackersLeft = " + attackersLeft); - - for (int i = 0; i < attackersLeft.size(); i++) { - final Card attacker = attackersLeft.get(i); - if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike() - && ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, ai.getOpponent()) - >= ComputerUtilCombat.getDamageToKill(attacker)) { - continue; - } - - if (this.shouldAttack(ai, attacker, this.blockers, combat) && CombatUtil.canAttack(attacker, defender, combat)) { - combat.addAttacker(attacker, defender); - // check if attackers are enough to finish the attacked planeswalker - if (defender instanceof Card) { - Card pw = (Card) defender; - final int blockNum = this.blockers.size(); - int attackNum = 0; - int damage = 0; - List attacking = combat.getAttackersOf(defender); - CardLists.sortByPowerAsc(attacking); - for (Card atta : attacking) { - if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers, combat)) { - damage += ComputerUtilCombat.damageIfUnblocked(atta, opp, null); - } else if (CombatUtil.canBeBlocked(attacker, this.blockers, combat)) { - attackNum++; - } - } - // if enough damage: switch to next planeswalker or player - if (damage >= pw.getCounters(CounterType.LOYALTY)) { - List pwDefending = combat.getDefendingPlaneswalkers(); - boolean found = false; - // look for next planeswalker - for (Card walker : pwDefending) { - if (combat.getAttackersOf(walker).isEmpty()) { - defender = walker; - found = true; - break; - } - } - if (!found) { - defender = getDefendingPlayers(combat).get(0); - } - } - } - } - } - } // getAttackers() - - /** - *

- * countExaltedBonus. - *

- * - * @param player - * a {@link forge.game.player.Player} object. - * @return a int. - */ - public final int countExaltedBonus(final Player player) { - int bonus = 0; - for (Card c : player.getCardsIn(ZoneType.Battlefield)) { - bonus += c.getKeywordAmount("Exalted"); - } - - return bonus; - } - - /** - *

- * getAttack. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a int. - */ - public final int getAttack(final Card c) { - int n = c.getNetCombatDamage(); - - if (c.hasKeyword("Double Strike")) { - n *= 2; - } - - return n; - } - - /** - *

- * shouldAttack. - *

- * - * @param attacker - * a {@link forge.game.card.Card} object. - * @param defenders - * a {@link forge.CardList} object. - * @param combat - * a {@link forge.game.combat.Combat} object. - * @return a boolean. - */ - public final boolean shouldAttack(final Player ai, final Card attacker, final List defenders, final Combat combat) { - boolean canBeKilledByOne = false; // indicates if the attacker can be killed by a single blocker - boolean canKillAll = true; // indicates if the attacker can kill all single blockers - boolean canKillAllDangerous = true; // indicates if the attacker can kill all single blockers with wither or infect - boolean isWorthLessThanAllKillers = true; - boolean canBeBlocked = false; - int numberOfPossibleBlockers = 0; - - - if (!this.isEffectiveAttacker(ai, attacker, combat)) { - return false; - } - boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator"); - // is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...) - boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE"); - if (!hasCombatEffect) { - for (String keyword : attacker.getKeyword()) { - if (keyword.equals("Wither") || keyword.equals("Infect") || keyword.equals("Lifelink")) { - hasCombatEffect = true; - break; - } - } - } - - // look at the attacker in relation to the blockers to establish a - // number of factors about the attacking - // context that will be relevant to the attackers decision according to - // the selected strategy - for (final Card defender : defenders) { - // if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check - if ((isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) - && CombatUtil.canBlock(attacker, defender)) { - numberOfPossibleBlockers += 1; - if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false) - && !(attacker.hasKeyword("Undying") && attacker.getCounters(CounterType.P1P1) == 0)) { - canBeKilledByOne = true; // there is a single creature on - // the battlefield that can kill - // the creature - // see if the defending creature is of higher or lower - // value. We don't want to attack only to lose value - if (isWorthLessThanAllKillers && !attacker.hasSVar("SacMe") - && ComputerUtilCard.evaluateCreature(defender) <= ComputerUtilCard.evaluateCreature(attacker)) { - isWorthLessThanAllKillers = false; - } - } - // see if this attacking creature can destroy this defender, if - // not record that it can't kill everything - if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(ai, defender, attacker, combat, false)) { - canKillAll = false; - if (!canKillAllDangerous) { - continue; - } - if (defender.getSVar("HasCombatEffect").equals("TRUE")) { - canKillAllDangerous = false; - } else { - for (String keyword : defender.getKeyword()) { - if (keyword.equals("Wither") || keyword.equals("Infect") || keyword.equals("Lifelink")) { - canKillAllDangerous = false; - break; - // there is a creature that can survive an attack from this creature - // and combat will have negative effects - } - } - } - } - } - } - - // if the creature cannot block and can kill all opponents they might as - // well attack, they do nothing staying back - if (canKillAll && isWorthLessThanAllKillers && !CombatUtil.canBlock(attacker)) { - if ( LOG_AI_ATTACKS ) - System.out.println(attacker.getName() + " = attacking because they can't block, expecting to kill or damage player"); - return true; - } - - if (numberOfPossibleBlockers > 1 || (numberOfPossibleBlockers == 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, combat))) { - canBeBlocked = true; - } - /*System.out.println(attacker + " canBeKilledByOne: " + canBeKilledByOne + " canKillAll: " - + canKillAll + " isWorthLessThanAllKillers: " + isWorthLessThanAllKillers + " canBeBlocked: " + canBeBlocked);*/ - // decide if the creature should attack based on the prevailing strategy - // choice in aiAggression - switch (this.aiAggression) { - case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked - if ((canKillAll && isWorthLessThanAllKillers) || !canBeBlocked) { - if ( LOG_AI_ATTACKS ) - System.out.println(attacker.getName() + " = attacking expecting to kill creature, or is unblockable"); - return true; - } - break; - case 5: // all out attacking - if ( LOG_AI_ATTACKS ) - System.out.println(attacker.getName() + " = all out attacking"); - return true; - case 4: // expecting to at least trade with something - if (canKillAll || (canKillAllDangerous && !canBeKilledByOne) || !canBeBlocked) { - if ( LOG_AI_ATTACKS ) - System.out.println(attacker.getName() + " = attacking expecting to at least trade with something"); - return true; - } - break; - case 3: // expecting to at least kill a creature of equal value, not be - // blocked - if ((canKillAll && isWorthLessThanAllKillers) - || ((canKillAllDangerous || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne) - || !canBeBlocked) { - if ( LOG_AI_ATTACKS ) - System.out.println(attacker.getName() + " = attacking expecting to kill creature or cause damage, or is unblockable"); - return true; - } - break; - case 2: // attack expecting to attract a group block or destroying a - // single blocker and surviving - if (((canKillAll || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne) || !canBeBlocked) { - if ( LOG_AI_ATTACKS ) - System.out.println(attacker.getName() + " = attacking expecting to survive or attract group block"); - return true; - } - break; - case 1: // unblockable creatures only - if (!canBeBlocked || (numberOfPossibleBlockers == 1 && canKillAll && !canBeKilledByOne)) { - if ( LOG_AI_ATTACKS ) - System.out.println(attacker.getName() + " = attacking expecting not to be blocked"); - return true; - } - break; - default: - break; - } - return false; // don't attack - } - -} // end class ComputerUtil_Attack2 +/* + * 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 . + */ +package forge.ai; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import com.google.common.base.Predicate; +import com.google.common.collect.Lists; + +import forge.game.GameEntity; +import forge.game.card.Card; +import forge.game.card.CardLists; +import forge.game.card.CounterType; +import forge.game.combat.Combat; +import forge.game.combat.CombatUtil; +import forge.game.player.Player; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; + + +//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer() +/** + *

+ * ComputerUtil_Attack2 class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class AiAttackController { + + // possible attackers and blockers + private final List attackers; + private final List blockers; + + private final static Random random = new Random(); + private final static int randomInt = random.nextInt(); + + private List oppList; // holds human player creatures + private List myList; // holds computer creatures + + private final Player ai; + + private int aiAggression = 0; // added by Masher, how aggressive the ai is + // attack will be depending on circumstances + + + /** + *

+ * Constructor for ComputerUtil_Attack2. + *

+ * + * @param possibleAttackers + * a {@link forge.CardList} object. + * @param possibleBlockers + * a {@link forge.CardList} object. + */ + public AiAttackController(final Player ai) { + this.ai = ai; + Player opponent = ai.getOpponent(); + + this.oppList = Lists.newArrayList(); + this.oppList.addAll(opponent.getCreaturesInPlay()); + this.myList = ai.getCreaturesInPlay(); + + + this.attackers = new ArrayList(); + for (Card c : myList) { + if (CombatUtil.canAttack(c, opponent)) { + attackers.add(c); + } + } + this.blockers = this.getPossibleBlockers(oppList, this.attackers); + } // constructor + + /** + *

+ * sortAttackers. + *

+ * + * @param in + * a {@link forge.CardList} object. + * @return a {@link forge.CardList} object. + */ + public final List sortAttackers(final List in) { + final List list = new ArrayList(); + + // Cards with triggers should come first (for Battle Cry) + for (final Card attacker : in) { + for (final Trigger trigger : attacker.getTriggers()) { + if (trigger.getMode() == TriggerType.Attacks) { + list.add(attacker); + break; + } + } + } + + for (final Card attacker : in) { + if (!list.contains(attacker)) { + list.add(attacker); + } + } + + return list; + } // sortAttackers() + + // Is there any reward for attacking? (for 0/1 creatures there is not) + /** + *

+ * isEffectiveAttacker. + *

+ * + * @param attacker + * a {@link forge.game.card.Card} object. + * @param combat + * a {@link forge.game.combat.Combat} object. + * @return a boolean. + */ + public final boolean isEffectiveAttacker(final Player ai, final Card attacker, final Combat combat) { + + // if the attacker will die when attacking don't attack + if ((attacker.getNetDefense() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) { + return false; + } + + final Player opp = ai.getOpponent(); + if (ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat) > 0) { + return true; + } + if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) { + return true; + } + if (this.attackers.size() == 1 && attacker.hasKeyword("Exalted") && ComputerUtilCombat.predictDamageTo(opp, 1, attacker, true) > 0) { + return true; + } + + final List controlledByCompy = ai.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES); + for (final Card c : controlledByCompy) { + for (final Trigger trigger : c.getTriggers()) { + if (ComputerUtilCombat.combatTriggerWillTrigger(attacker, null, trigger, combat)) { + return true; + } + } + } + + return false; + } + + /** + *

+ * getPossibleBlockers. + *

+ * + * @param blockers + * a {@link forge.CardList} object. + * @param attackers + * a {@link forge.CardList} object. + * @return a {@link forge.CardList} object. + */ + public final List getPossibleBlockers(final List blockers, final List attackers) { + List possibleBlockers = new ArrayList(blockers); + possibleBlockers = CardLists.filter(possibleBlockers, new Predicate() { + @Override + public boolean apply(final Card c) { + return canBlockAnAttacker(c, attackers); + } + }); + return possibleBlockers; + } // getPossibleBlockers() + + /** + *

+ * canBlockAnAttacker. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param attackers + * a {@link forge.CardList} object. + * @return a boolean. + */ + public final boolean canBlockAnAttacker(final Card c, final List attackers) { + final List attackerList = new ArrayList(attackers); + if (!c.isCreature()) { + return false; + } + for (final Card attacker : attackerList) { + if (CombatUtil.canBlock(attacker, c)) { + return true; + } + } + return false; + } // getPossibleBlockers() + + // this checks to make sure that the computer player + // doesn't lose when the human player attacks + // this method is used by getAttackers() + /** + *

+ * notNeededAsBlockers. + *

+ * + * @param attackers + * a {@link forge.CardList} object. + * @param combat + * a {@link forge.game.combat.Combat} object. + * @return a {@link forge.CardList} object. + */ + public final List notNeededAsBlockers(final Player ai, final List attackers) { + final List notNeededAsBlockers = new ArrayList(attackers); + int fixedBlockers = 0; + final List vigilantes = new ArrayList(); + //check for time walks + if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) { + return attackers; + } + for (final Card c : this.myList) { + if (c.getName().equals("Masako the Humorless")) { + // "Tapped creatures you control can block as though they were untapped." + return attackers; + } + if (!attackers.contains(c)) { // this creature can't attack anyway + if (canBlockAnAttacker(c, this.oppList)) { + fixedBlockers++; + } + continue; + } + if (c.hasKeyword("Vigilance")) { + vigilantes.add(c); + notNeededAsBlockers.remove(c); // they will be re-added later + if (canBlockAnAttacker(c, this.oppList)) { + fixedBlockers++; + } + } + } + CardLists.sortByPowerAsc(attackers); + int blockersNeeded = this.oppList.size(); + + // don't hold back creatures that can't block any of the human creatures + final List list = this.getPossibleBlockers(attackers, this.oppList); + + //Calculate the amount of creatures necessary + for (int i = 0; i < list.size(); i++) { + if (!this.doesHumanAttackAndWin(ai, i)) { + blockersNeeded = i; + break; + } + } + int blockersStillNeeded = blockersNeeded - fixedBlockers; + blockersStillNeeded = Math.min(blockersNeeded, list.size()); + for (int i = 0; i < blockersStillNeeded; i++) { + notNeededAsBlockers.remove(list.get(i)); + } + + // re-add creatures with vigilance + notNeededAsBlockers.addAll(vigilantes); + + if (blockersNeeded > 1) { + return notNeededAsBlockers; + } + + final Player opp = ai.getOpponent(); + + // Increase the total number of blockers needed by 1 if Finest Hour in + // play + // (human will get an extra first attack with a creature that untaps) + // In addition, if the computer guesses it needs no blockers, make sure + // that + // it won't be surprised by Exalted + final int humanExaltedBonus = this.countExaltedBonus(opp); + + if (humanExaltedBonus > 0) { + final int nFinestHours = opp.getCardsIn(ZoneType.Battlefield, "Finest Hour").size(); + + if (((blockersNeeded == 0) || (nFinestHours > 0)) && (this.oppList.size() > 0)) { + // + // total attack = biggest creature + exalted, *2 if Rafiq is in + // play + int humanBaseAttack = this.getAttack(this.oppList.get(0)) + humanExaltedBonus; + if (nFinestHours > 0) { + // For Finest Hour, one creature could attack and get the + // bonus TWICE + humanBaseAttack = humanBaseAttack + humanExaltedBonus; + } + final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBaseAttack + : humanBaseAttack; + if (ai.getLife() - 3 <= totalExaltedAttack) { + // We will lose if there is an Exalted attack -- keep one + // blocker + if ((blockersNeeded == 0) && (notNeededAsBlockers.size() > 0)) { + notNeededAsBlockers.remove(0); + } + + // Finest Hour allows a second Exalted attack: keep a + // blocker for that too + if ((nFinestHours > 0) && (notNeededAsBlockers.size() > 0)) { + notNeededAsBlockers.remove(0); + } + } + } + } + return notNeededAsBlockers; + } + + // this uses a global variable, which isn't perfect + /** + *

+ * doesHumanAttackAndWin. + *

+ * + * @param nBlockingCreatures + * a int. + * @return a boolean. + */ + public final boolean doesHumanAttackAndWin(final Player ai, final int nBlockingCreatures) { + int totalAttack = 0; + int totalPoison = 0; + int blockersLeft = nBlockingCreatures; + + if (ai.cantLose()) { + return false; + } + + for (Card attacker : oppList) { + if (!CombatUtil.canAttackNextTurn(attacker)) { + continue; + } + if (blockersLeft > 0 && CombatUtil.canBeBlocked(attacker, ai)) { + blockersLeft--; + continue; + } + totalAttack += ComputerUtilCombat.damageIfUnblocked(attacker, ai, null); + totalPoison += ComputerUtilCombat.poisonIfUnblocked(attacker, ai); + } + + if (totalAttack > 0 && ai.getLife() <= totalAttack + && !ai.cantLoseForZeroOrLessLife()) { + return true; + } + return ai.getPoisonCounters() + totalPoison > 9; + } + + /** + *

+ * doAssault. + *

+ * + * @return a boolean. + */ + private boolean doAssault(final Player ai) { + // Beastmaster Ascension + if (ai.isCardInPlay("Beastmaster Ascension") + && (this.attackers.size() > 1)) { + final List beastions = ai.getCardsIn(ZoneType.Battlefield, "Beastmaster Ascension"); + int minCreatures = 7; + for (final Card beastion : beastions) { + final int counters = beastion.getCounters(CounterType.QUEST); + minCreatures = Math.min(minCreatures, 7 - counters); + } + if (this.attackers.size() >= minCreatures) { + return true; + } + } + + CardLists.sortByPowerDesc(this.attackers); + + final List unblockedAttackers = new ArrayList(); + final List remainingAttackers = new ArrayList(this.attackers); + final List remainingBlockers = new ArrayList(this.blockers); + final Player opp = ai.getOpponent(); + + + for (Card attacker : attackers) { + if (!CombatUtil.canBeBlocked(attacker, this.blockers, null) + || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { + unblockedAttackers.add(attacker); + } + } + + for (Card blocker : this.blockers) { + if (blocker.hasKeyword("CARDNAME can block any number of creatures.") + || blocker.hasKeyword("CARDNAME can block an additional ninety-nine creatures.")) { + for (Card attacker : this.attackers) { + if (CombatUtil.canBlock(attacker, blocker)) { + remainingAttackers.remove(attacker); + } + } + remainingBlockers.remove(blocker); + } + } + + // presumes the Human will block + for (Card blocker : remainingBlockers) { + if (remainingAttackers.isEmpty()) { + break; + } + if (blocker.hasKeyword("CARDNAME can block an additional creature.")) { + remainingAttackers.remove(0); + if (remainingAttackers.isEmpty()) { + break; + } + } + remainingAttackers.remove(0); + } + unblockedAttackers.addAll(remainingAttackers); + + if (ComputerUtilCombat.sumDamageIfUnblocked(remainingAttackers, opp) + ComputerUtil.possibleNonCombatDamage(ai) >= opp.getLife() + && !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && (opp.getLife() < 1))) { + return true; + } + + if (ComputerUtilCombat.sumPoisonIfUnblocked(remainingAttackers, opp) >= (10 - opp.getPoisonCounters())) { + return true; + } + + return false; + } // doAssault() + + /** + *

+ * chooseDefender. + *

+ * + * @param c + * a {@link forge.game.combat.Combat} object. + * @param bAssault + * a boolean. + */ + private final GameEntity chooseDefender(final Combat c, final boolean bAssault) { + final List defs = c.getDefenders(); + if (defs.size() == 1) { + return defs.get(0); + } + + final GameEntity entity = ai.getMustAttackEntity(); + if (null != entity) { + int n = defs.indexOf(entity); + if (-1 == n) { + System.out.println("getMustAttackEntity() returned something not in defenders."); + return defs.get(0); + } else { + return entity; + } + } else { + // 1. assault the opponent if you can kill him + if (bAssault) { + return getDefendingPlayers(c).get(0); + } + // 2. attack planeswalkers + List pwDefending = c.getDefendingPlaneswalkers(); + if (!pwDefending.isEmpty()) { + return pwDefending.get(0); + } else { + return defs.get(0); + } + } + } + + final boolean LOG_AI_ATTACKS = false; + + + public final List getDefendingPlayers(Combat combat) { + final List defending = new ArrayList(); + for (final GameEntity o : combat.getDefenders()) { + if (o instanceof Player) { + defending.add((Player) o); + } + } + return defending; + } + + + /** + *

+ * Getter for the field attackers. + *

+ * + * @return a {@link forge.game.combat.Combat} object. + */ + public final void declareAttackers(final Combat combat) { + // if this method is called multiple times during a turn, + // it will always return the same value + // randomInt is used so that the computer doesn't always + // do the same thing on turn 3 if he had the same creatures in play + // I know this is a little confusing + + random.setSeed(ai.getGame().getPhaseHandler().getTurn() + AiAttackController.randomInt); + + if (this.attackers.isEmpty()) { + return; + } + + final boolean bAssault = this.doAssault(ai); + // Determine who will be attacked + GameEntity defender = this.chooseDefender(combat, bAssault); + List attackersLeft = new ArrayList(this.attackers); + // Attackers that don't really have a choice + for (final Card attacker : this.attackers) { + if (!CombatUtil.canAttack(attacker, defender, combat)) { + continue; + } + boolean mustAttack = false; + for (String s : attacker.getKeyword()) { + if (s.equals("CARDNAME attacks each turn if able.") + || s.startsWith("CARDNAME attacks specific player each combat if able")) { + mustAttack = true; + break; + } + if ((s.equals("At the beginning of the end step, destroy CARDNAME.") + || s.equals("At the beginning of the end step, exile CARDNAME.") + || s.equals("At the beginning of the end step, sacrifice CARDNAME.")) + && isEffectiveAttacker(ai, attacker, combat)) { + mustAttack = true; + break; + } + } + if (mustAttack || attacker.getController().getMustAttackEntity() != null + || attacker.getSVar("MustAttack").equals("True")) { + combat.addAttacker(attacker, defender); + attackersLeft.remove(attacker); + } + } + if (attackersLeft.isEmpty()) { + return; + } + if (bAssault) { + if ( LOG_AI_ATTACKS ) + System.out.println("Assault"); + CardLists.sortByPowerDesc(attackersLeft); + for (Card attacker : attackersLeft) { + if (CombatUtil.canAttack(attacker, defender, combat) && this.isEffectiveAttacker(ai, attacker, combat)) { + combat.addAttacker(attacker, defender); + } + } + return; + } + + // Exalted + if (combat.getAttackers().isEmpty()) { + boolean exalted = false; + int exaltedCount = 0; + for (Card c : ai.getCardsIn(ZoneType.Battlefield)) { + if (c.getName().equals("Rafiq of the Many") || c.getName().equals("Battlegrace Angel")) { + exalted = true; + break; + } + if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) { + exalted = true; + break; + } + if (c.hasKeyword("Exalted")) { + exaltedCount++; + if (exaltedCount > 2) { + exalted = true; + break; + } + } + } + if (exalted) { + CardLists.sortByPowerDesc(this.attackers); + if ( LOG_AI_ATTACKS ) + System.out.println("Exalted"); + this.aiAggression = 6; + for (Card attacker : this.attackers) { + if (CombatUtil.canAttack(attacker, defender, combat) && this.shouldAttack(ai, attacker, this.blockers, combat)) { + combat.addAttacker(attacker, defender); + return; + } + } + } + } + + // ******************* + // Evaluate the creature forces + // ******************* + + int computerForces = 0; + int humanForces = 0; + int humanForcesForAttritionalAttack = 0; + + // examine the potential forces + final List nextTurnAttackers = new ArrayList(); + int candidateCounterAttackDamage = 0; + + for (final Card pCard : this.oppList) { + // if the creature can attack next turn add it to counter attackers + // list + if (CombatUtil.canAttackNextTurn(pCard)) { + nextTurnAttackers.add(pCard); + if (pCard.getNetCombatDamage() > 0) { + candidateCounterAttackDamage += pCard.getNetCombatDamage(); + humanForces += 1; // player forces they might use to attack + } + } + // increment player forces that are relevant to an attritional + // attack - includes walls + if (CombatUtil.canBlock(pCard, true)) { + humanForcesForAttritionalAttack += 1; + } + } + + // find the potential counter attacking damage compared to AI life total + double aiLifeToPlayerDamageRatio = 1000000; + if (candidateCounterAttackDamage > 0) { + aiLifeToPlayerDamageRatio = (double) ai.getLife() / candidateCounterAttackDamage; + } + + final Player opp = ai.getOpponent(); + // get the potential damage and strength of the AI forces + final List candidateAttackers = new ArrayList(); + int candidateUnblockedDamage = 0; + for (final Card pCard : this.myList) { + // if the creature can attack then it's a potential attacker this + // turn, assume summoning sickness creatures will be able to + if (CombatUtil.canAttackNextTurn(pCard)) { + candidateAttackers.add(pCard); + if (pCard.getNetCombatDamage() > 0) { + candidateUnblockedDamage += ComputerUtilCombat.damageIfUnblocked(pCard, opp, null); + computerForces += 1; + } + } + } + + // find the potential damage ratio the AI can cause + double humanLifeToDamageRatio = 1000000; + if (candidateUnblockedDamage > 0) { + humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai)) / candidateUnblockedDamage; + } + + // determine if the ai outnumbers the player + final int outNumber = computerForces - humanForces; + + for (Card blocker : this.blockers) { + if (blocker.hasKeyword("CARDNAME can block any number of creatures.")) { + aiLifeToPlayerDamageRatio--; + } + } + + // compare the ratios, higher = better for ai + final double ratioDiff = aiLifeToPlayerDamageRatio - humanLifeToDamageRatio; + + // ********************* + // if outnumber and superior ratio work out whether attritional all out + // attacking will work + // attritional attack will expect some creatures to die but to achieve + // victory by sheer weight + // of numbers attacking turn after turn. It's not calculate very + // carefully, the accuracy + // can probably be improved + // ********************* + boolean doAttritionalAttack = false; + // get list of attackers ordered from low power to high + CardLists.sortByPowerAsc(this.attackers); + // get player life total + int humanLife = opp.getLife(); + // get the list of attackers up to the first blocked one + final List attritionalAttackers = new ArrayList(); + for (int x = 0; x < (this.attackers.size() - humanForces); x++) { + attritionalAttackers.add(this.attackers.get(x)); + } + // until the attackers are used up or the player would run out of life + int attackRounds = 1; + while (attritionalAttackers.size() > 0 && humanLife > 0 && attackRounds < 99) { + // sum attacker damage + int damageThisRound = 0; + for (int y = 0; y < attritionalAttackers.size(); y++) { + damageThisRound += attritionalAttackers.get(y).getNetCombatDamage(); + } + // remove from player life + humanLife -= damageThisRound; + // shorten attacker list by the length of the blockers - assuming + // all blocked are killed for convenience + for (int z = 0; z < humanForcesForAttritionalAttack; z++) { + if (attritionalAttackers.size() > 0) { + attritionalAttackers.remove(attritionalAttackers.size() - 1); + } + } + attackRounds += 1; + if (humanLife <= 0) { + doAttritionalAttack = true; + } + } + // System.out.println(doAttritionalAttack + " = do attritional attack"); + // ********************* + // end attritional attack calculation + // ********************* + + // ********************* + // see how long until unblockable attackers will be fatal + // ********************* + double unblockableDamage = 0; + double nextUnblockableDamage = 0; + double turnsUntilDeathByUnblockable = 0; + boolean doUnblockableAttack = false; + for (final Card attacker : this.attackers) { + boolean isUnblockableCreature = true; + // check blockers individually, as the bulk canBeBlocked doesn't + // check all circumstances + for (final Card blocker : this.blockers) { + if (CombatUtil.canBlock(attacker, blocker)) { + isUnblockableCreature = false; + break; + } + } + if (isUnblockableCreature) { + unblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat); + } + } + for (final Card attacker : nextTurnAttackers) { + boolean isUnblockableCreature = true; + // check blockers individually, as the bulk canBeBlocked doesn't + // check all circumstances + for (final Card blocker : this.myList) { + if (CombatUtil.canBlock(attacker, blocker, true)) { + isUnblockableCreature = false; + break; + } + } + if (isUnblockableCreature) { + nextUnblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, opp, null); + } + } + if (unblockableDamage > 0 && !opp.cantLoseForZeroOrLessLife() + && opp.canLoseLife()) { + turnsUntilDeathByUnblockable = 1 + (opp.getLife() - unblockableDamage) / nextUnblockableDamage; + } + if (opp.canLoseLife()) { + doUnblockableAttack = true; + } + // ***************** + // end see how long until unblockable attackers will be fatal + // ***************** + + // decide on attack aggression based on a comparison of forces, life + // totals and other considerations + // some bad "magic numbers" here, TODO replace with nice descriptive + // variable names + if (ratioDiff > 0 && doAttritionalAttack) { + this.aiAggression = 5; // attack at all costs + } else if (ratioDiff >= 1 && (humanLifeToDamageRatio < 2 || outNumber > 0)) { + this.aiAggression = 4; // attack expecting to trade or damage player. + } else if (ratioDiff >= 0) { + this.aiAggression = 3; // attack expecting to make good trades or damage player. + } else if (ratioDiff + outNumber >= -1 || aiLifeToPlayerDamageRatio > 1 + || ratioDiff * -1 < turnsUntilDeathByUnblockable) { + // at 0 ratio expect to potentially gain an advantage by attacking first + // if the ai has a slight advantage + // or the ai has a significant advantage numerically but only a slight disadvantage damage/life + this.aiAggression = 2; // attack expecting to destroy creatures/be unblockable + } else if (doUnblockableAttack) { + this.aiAggression = 1; + // look for unblockable creatures that might be + // able to attack for a bit of fatal damage even if the player is significantly better + } else { + this.aiAggression = 0; + } // stay at home to block + + if ( LOG_AI_ATTACKS ) + System.out.println(String.valueOf(this.aiAggression) + " = ai aggression"); + + // **************** + // Evaluation the end + // **************** + + if ( LOG_AI_ATTACKS ) + System.out.println("Normal attack"); + + attackersLeft = this.notNeededAsBlockers(ai, attackersLeft); + attackersLeft = this.sortAttackers(attackersLeft); + + if ( LOG_AI_ATTACKS ) + System.out.println("attackersLeft = " + attackersLeft); + + for (int i = 0; i < attackersLeft.size(); i++) { + final Card attacker = attackersLeft.get(i); + if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike() + && ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, ai.getOpponent()) + >= ComputerUtilCombat.getDamageToKill(attacker)) { + continue; + } + + if (this.shouldAttack(ai, attacker, this.blockers, combat) && CombatUtil.canAttack(attacker, defender, combat)) { + combat.addAttacker(attacker, defender); + // check if attackers are enough to finish the attacked planeswalker + if (defender instanceof Card) { + Card pw = (Card) defender; + final int blockNum = this.blockers.size(); + int attackNum = 0; + int damage = 0; + List attacking = combat.getAttackersOf(defender); + CardLists.sortByPowerAsc(attacking); + for (Card atta : attacking) { + if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers, combat)) { + damage += ComputerUtilCombat.damageIfUnblocked(atta, opp, null); + } else if (CombatUtil.canBeBlocked(attacker, this.blockers, combat)) { + attackNum++; + } + } + // if enough damage: switch to next planeswalker or player + if (damage >= pw.getCounters(CounterType.LOYALTY)) { + List pwDefending = combat.getDefendingPlaneswalkers(); + boolean found = false; + // look for next planeswalker + for (Card walker : pwDefending) { + if (combat.getAttackersOf(walker).isEmpty()) { + defender = walker; + found = true; + break; + } + } + if (!found) { + defender = getDefendingPlayers(combat).get(0); + } + } + } + } + } + } // getAttackers() + + /** + *

+ * countExaltedBonus. + *

+ * + * @param player + * a {@link forge.game.player.Player} object. + * @return a int. + */ + public final int countExaltedBonus(final Player player) { + int bonus = 0; + for (Card c : player.getCardsIn(ZoneType.Battlefield)) { + bonus += c.getKeywordAmount("Exalted"); + } + + return bonus; + } + + /** + *

+ * getAttack. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a int. + */ + public final int getAttack(final Card c) { + int n = c.getNetCombatDamage(); + + if (c.hasKeyword("Double Strike")) { + n *= 2; + } + + return n; + } + + /** + *

+ * shouldAttack. + *

+ * + * @param attacker + * a {@link forge.game.card.Card} object. + * @param defenders + * a {@link forge.CardList} object. + * @param combat + * a {@link forge.game.combat.Combat} object. + * @return a boolean. + */ + public final boolean shouldAttack(final Player ai, final Card attacker, final List defenders, final Combat combat) { + boolean canBeKilledByOne = false; // indicates if the attacker can be killed by a single blocker + boolean canKillAll = true; // indicates if the attacker can kill all single blockers + boolean canKillAllDangerous = true; // indicates if the attacker can kill all single blockers with wither or infect + boolean isWorthLessThanAllKillers = true; + boolean canBeBlocked = false; + int numberOfPossibleBlockers = 0; + + + if (!this.isEffectiveAttacker(ai, attacker, combat)) { + return false; + } + boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator"); + // is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...) + boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE"); + if (!hasCombatEffect) { + for (String keyword : attacker.getKeyword()) { + if (keyword.equals("Wither") || keyword.equals("Infect") || keyword.equals("Lifelink")) { + hasCombatEffect = true; + break; + } + } + } + + // look at the attacker in relation to the blockers to establish a + // number of factors about the attacking + // context that will be relevant to the attackers decision according to + // the selected strategy + for (final Card defender : defenders) { + // if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check + if ((isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) + && CombatUtil.canBlock(attacker, defender)) { + numberOfPossibleBlockers += 1; + if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false) + && !(attacker.hasKeyword("Undying") && attacker.getCounters(CounterType.P1P1) == 0)) { + canBeKilledByOne = true; // there is a single creature on + // the battlefield that can kill + // the creature + // see if the defending creature is of higher or lower + // value. We don't want to attack only to lose value + if (isWorthLessThanAllKillers && !attacker.hasSVar("SacMe") + && ComputerUtilCard.evaluateCreature(defender) <= ComputerUtilCard.evaluateCreature(attacker)) { + isWorthLessThanAllKillers = false; + } + } + // see if this attacking creature can destroy this defender, if + // not record that it can't kill everything + if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(ai, defender, attacker, combat, false)) { + canKillAll = false; + if (!canKillAllDangerous) { + continue; + } + if (defender.getSVar("HasCombatEffect").equals("TRUE")) { + canKillAllDangerous = false; + } else { + for (String keyword : defender.getKeyword()) { + if (keyword.equals("Wither") || keyword.equals("Infect") || keyword.equals("Lifelink")) { + canKillAllDangerous = false; + break; + // there is a creature that can survive an attack from this creature + // and combat will have negative effects + } + } + } + } + } + } + + // if the creature cannot block and can kill all opponents they might as + // well attack, they do nothing staying back + if (canKillAll && isWorthLessThanAllKillers && !CombatUtil.canBlock(attacker)) { + if ( LOG_AI_ATTACKS ) + System.out.println(attacker.getName() + " = attacking because they can't block, expecting to kill or damage player"); + return true; + } + + if (numberOfPossibleBlockers > 1 || (numberOfPossibleBlockers == 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, combat))) { + canBeBlocked = true; + } + /*System.out.println(attacker + " canBeKilledByOne: " + canBeKilledByOne + " canKillAll: " + + canKillAll + " isWorthLessThanAllKillers: " + isWorthLessThanAllKillers + " canBeBlocked: " + canBeBlocked);*/ + // decide if the creature should attack based on the prevailing strategy + // choice in aiAggression + switch (this.aiAggression) { + case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked + if ((canKillAll && isWorthLessThanAllKillers) || !canBeBlocked) { + if ( LOG_AI_ATTACKS ) + System.out.println(attacker.getName() + " = attacking expecting to kill creature, or is unblockable"); + return true; + } + break; + case 5: // all out attacking + if ( LOG_AI_ATTACKS ) + System.out.println(attacker.getName() + " = all out attacking"); + return true; + case 4: // expecting to at least trade with something + if (canKillAll || (canKillAllDangerous && !canBeKilledByOne) || !canBeBlocked) { + if ( LOG_AI_ATTACKS ) + System.out.println(attacker.getName() + " = attacking expecting to at least trade with something"); + return true; + } + break; + case 3: // expecting to at least kill a creature of equal value, not be + // blocked + if ((canKillAll && isWorthLessThanAllKillers) + || ((canKillAllDangerous || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne) + || !canBeBlocked) { + if ( LOG_AI_ATTACKS ) + System.out.println(attacker.getName() + " = attacking expecting to kill creature or cause damage, or is unblockable"); + return true; + } + break; + case 2: // attack expecting to attract a group block or destroying a + // single blocker and surviving + if (((canKillAll || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne) || !canBeBlocked) { + if ( LOG_AI_ATTACKS ) + System.out.println(attacker.getName() + " = attacking expecting to survive or attract group block"); + return true; + } + break; + case 1: // unblockable creatures only + if (!canBeBlocked || (numberOfPossibleBlockers == 1 && canKillAll && !canBeKilledByOne)) { + if ( LOG_AI_ATTACKS ) + System.out.println(attacker.getName() + " = attacking expecting not to be blocked"); + return true; + } + break; + default: + break; + } + return false; // don't attack + } + +} // end class ComputerUtil_Attack2 diff --git a/forge-game/src/main/java/forge/ai/AiBlockController.java b/forge-gui/src/main/java/forge/ai/AiBlockController.java similarity index 97% rename from forge-game/src/main/java/forge/ai/AiBlockController.java rename to forge-gui/src/main/java/forge/ai/AiBlockController.java index de2fc174d4a..dae77d3c716 100644 --- a/forge-game/src/main/java/forge/ai/AiBlockController.java +++ b/forge-gui/src/main/java/forge/ai/AiBlockController.java @@ -1,809 +1,809 @@ -/* - * 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 . - */ -package forge.ai; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; - -import forge.game.GameEntity; -import forge.game.TriggerReplacementBase; -import forge.game.card.Card; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; -import forge.game.card.CounterType; -import forge.game.combat.Combat; -import forge.game.combat.CombatUtil; -import forge.game.player.Player; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerType; - - -/** - *

- * ComputerUtil_Block2 class. - *

- * - * @author Forge - * @version $Id: AiBlockController.java 24355 2014-01-19 01:33:24Z swordshine $ - */ -public class AiBlockController { - - private final Player ai; - /** Constant attackers. */ - private List attackers = new ArrayList(); // all attackers - /** Constant attackersLeft. */ - private List attackersLeft = new ArrayList(); // keeps track of - // all currently - // unblocked - // attackers - /** Constant blockedButUnkilled. */ - private List blockedButUnkilled = new ArrayList(); // blocked - // attackers - // that - // currently - // wouldn't be - // destroyed - /** Constant blockersLeft. */ - private List blockersLeft = new ArrayList(); // keeps track of all - // unassigned - // blockers - private int diff = 0; - - private boolean lifeInDanger = false; - public AiBlockController(Player aiPlayer) { - this.ai = aiPlayer; - } - - // finds the creatures able to block the attacker - private List getPossibleBlockers(final Combat combat, final Card attacker, final List blockersLeft, final boolean solo) { - final List blockers = new ArrayList(); - - for (final Card blocker : blockersLeft) { - // if the blocker can block a creature with lure it can't block a - // creature without - if (CombatUtil.canBlock(attacker, blocker, combat)) { - if (solo && blocker.hasKeyword("CARDNAME can't attack or block alone.")) { - continue; - } - blockers.add(blocker); - } - } - - return blockers; - } - - // finds blockers that won't be destroyed - private List getSafeBlockers(final Combat combat, final Card attacker, final List blockersLeft) { - final List blockers = new ArrayList(); - - for (final Card b : blockersLeft) { - if (!ComputerUtilCombat.canDestroyBlocker(ai, b, attacker, combat, false)) { - blockers.add(b); - } - } - - return blockers; - } - - // finds blockers that destroy the attacker - private List getKillingBlockers(final Combat combat, final Card attacker, final List blockersLeft) { - final List blockers = new ArrayList(); - - for (final Card b : blockersLeft) { - if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, b, combat, false)) { - blockers.add(b); - } - } - - return blockers; - } - - - - private List> sortAttackerByDefender(final Combat combat) { - List defenders = combat.getDefenders(); - final ArrayList> attackers = new ArrayList>(defenders.size()); - for (GameEntity defender : defenders) { - attackers.add(combat.getAttackersOf(defender)); - } - return attackers; - } - - private List sortPotentialAttackers(final Combat combat) { - final List> attackerLists = sortAttackerByDefender(combat); - final List sortedAttackers = new ArrayList(); - final List firstAttacker = attackerLists.get(0); - - final List defenders = combat.getDefenders(); - - - // Begin with the attackers that pose the biggest threat - CardLists.sortByEvaluateCreature(firstAttacker); - CardLists.sortByPowerDesc(firstAttacker); - - // If I don't have any planeswalkers than sorting doesn't really matter - if (defenders.size() == 1) { - return firstAttacker; - } - - final boolean bLifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat); - - // TODO Add creatures attacking Planeswalkers in order of which we want - // to protect - // defend planeswalkers with more loyalty before planeswalkers with less - // loyalty - // if planeswalker will be too difficult to defend don't even bother - for (List attacker : attackerLists) { - // Begin with the attackers that pose the biggest threat - CardLists.sortByPowerDesc(attacker); - for (final Card c : attacker) { - sortedAttackers.add(c); - } - } - - if (bLifeInDanger) { - // add creatures attacking the Player to the front of the list - for (final Card c : firstAttacker) { - sortedAttackers.add(0, c); - } - - } else { - // add creatures attacking the Player to the back of the list - for (final Card c : firstAttacker) { - sortedAttackers.add(c); - } - } - - return sortedAttackers; - } - - // ======================= block assignment functions - // ================================ - - // Good Blocks means a good trade or no trade - private void makeGoodBlocks(final Combat combat) { - - List currentAttackers = new ArrayList(attackersLeft); - - for (final Card attacker : attackersLeft) { - - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") - || attacker.hasKeyword("CARDNAME can't be blocked unless " + - "all creatures defending player controls block it.")) { - continue; - } - - Card blocker = null; - - final List blockers = getPossibleBlockers(combat, attacker, blockersLeft, true); - - final List safeBlockers = getSafeBlockers(combat, attacker, blockers); - List killingBlockers; - - if (safeBlockers.size() > 0) { - // 1.Blockers that can destroy the attacker but won't get - // destroyed - killingBlockers = getKillingBlockers(combat, attacker, safeBlockers); - if (!killingBlockers.isEmpty()) { - blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers); - } else if (!attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { - blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers); - blockedButUnkilled.add(attacker); - } - } // no safe blockers - else { - // 3.Blockers that can destroy the attacker and have an upside when dying - killingBlockers = getKillingBlockers(combat, attacker, blockers); - for (Card b : killingBlockers) { - if ((b.hasKeyword("Undying") && b.getCounters(CounterType.P1P1) == 0) - || !b.getSVar("SacMe").equals("")) { - blocker = b; - break; - } - } - // 4.Blockers that can destroy the attacker and are worth less - if (blocker == null && !killingBlockers.isEmpty()) { - final Card worst = ComputerUtilCard.getWorstCreatureAI(killingBlockers); - int value = ComputerUtilCard.evaluateCreature(attacker); - - // check for triggers when unblocked - for (Trigger trigger : attacker.getTriggers()) { - final HashMap trigParams = trigger.getMapParams(); - TriggerType mode = trigger.getMode(); - - if (!trigger.requirementsCheck(attacker.getGame())) { - continue; - } - - if (mode == TriggerType.DamageDone) { - if ((!trigParams.containsKey("ValidSource") - || TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), attacker)) - && attacker.getNetCombatDamage() > 0 - && (!trigParams.containsKey("ValidTarget") - || TriggerReplacementBase.matchesValid(combat.getDefenderByAttacker(attacker), trigParams.get("ValidTarget").split(","), attacker))) { - value += 50; - } - } else if (mode == TriggerType.AttackerUnblocked) { - if (TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), attacker)) { - value += 50; - } - } - } - - if ((ComputerUtilCard.evaluateCreature(worst) + diff) < value) { - blocker = worst; - } - } - } - if (blocker != null) { - currentAttackers.remove(attacker); - combat.addBlocker(attacker, blocker); - } - } - attackersLeft = (new ArrayList(currentAttackers)); - } - - // Good Gang Blocks means a good trade or no trade - /** - *

- * makeGangBlocks. - *

- * - * @param combat - * a {@link forge.game.combat.Combat} object. - * @return a {@link forge.game.combat.Combat} object. - */ - static final Predicate rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT")); - - private void makeGangBlocks(final Combat combat) { - List currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock)); - List blockers; - - // Try to block an attacker without first strike with a gang of first strikers - for (final Card attacker : attackersLeft) { - if (!attacker.hasKeyword("First Strike") && !attacker.hasKeyword("Double Strike")) { - blockers = getPossibleBlockers(combat, attacker, blockersLeft, false); - final List firstStrikeBlockers = new ArrayList(); - final List blockGang = new ArrayList(); - for (int i = 0; i < blockers.size(); i++) { - if (blockers.get(i).hasFirstStrike() || blockers.get(i).hasDoubleStrike()) { - firstStrikeBlockers.add(blockers.get(i)); - } - } - - if (firstStrikeBlockers.size() > 1) { - CardLists.sortByPowerDesc(firstStrikeBlockers); - for (final Card blocker : firstStrikeBlockers) { - final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker) - + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); - // if the total damage of the blockgang was not enough - // without but is enough with this blocker finish the - // blockgang - if (ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang) < damageNeeded - || CombatUtil.needsBlockers(attacker) > blockGang.size()) { - blockGang.add(blocker); - if (ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang) >= damageNeeded) { - currentAttackers.remove(attacker); - for (final Card b : blockGang) { - if (CombatUtil.canBlock(attacker, blocker, combat)) { - combat.addBlocker(attacker, b); - } - } - } - } - } - } - } - } - - attackersLeft = (new ArrayList(currentAttackers)); - currentAttackers = new ArrayList(attackersLeft); - - // Try to block an attacker with two blockers of which only one will die - for (final Card attacker : attackersLeft) { - blockers = getPossibleBlockers(combat, attacker, blockersLeft, false); - List usableBlockers; - final List blockGang = new ArrayList(); - int absorbedDamage = 0; // The amount of damage needed to kill the first blocker - int currentValue = 0; // The value of the creatures in the blockgang - - // AI can't handle good triple blocks yet - if (CombatUtil.needsBlockers(attacker) > 2) { - continue; - } - - // Try to add blockers that could be destroyed, but are worth less than the attacker - // Don't use blockers without First Strike or Double Strike if attacker has it - usableBlockers = CardLists.filter(blockers, new Predicate() { - @Override - public boolean apply(final Card c) { - if ((attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike")) - && !(c.hasKeyword("First Strike") || c.hasKeyword("Double Strike"))) { - return false; - } - return lifeInDanger || (ComputerUtilCard.evaluateCreature(c) + diff) < ComputerUtilCard.evaluateCreature(attacker); - } - }); - if (usableBlockers.size() < 2) { - return; - } - - final Card leader = ComputerUtilCard.getBestCreatureAI(usableBlockers); - blockGang.add(leader); - usableBlockers.remove(leader); - absorbedDamage = ComputerUtilCombat.getEnoughDamageToKill(leader, attacker.getNetCombatDamage(), attacker, true); - currentValue = ComputerUtilCard.evaluateCreature(leader); - - for (final Card blocker : usableBlockers) { - // Add an additional blocker if the current blockers are not - // enough and the new one would deal the remaining damage - final int currentDamage = ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang); - final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker); - final int absorbedDamage2 = ComputerUtilCombat.getEnoughDamageToKill(blocker, attacker.getNetCombatDamage(), attacker, true); - final int addedValue = ComputerUtilCard.evaluateCreature(blocker); - final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker) - + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); - if ((damageNeeded > currentDamage || CombatUtil.needsBlockers(attacker) > blockGang.size()) - && !(damageNeeded > currentDamage + additionalDamage) - // The attacker will be killed - && (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage() - // only one blocker can be killed - || currentValue + addedValue - 50 <= ComputerUtilCard.evaluateCreature(attacker) - // or attacker is worth more - || (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat))) - // or life is in danger - && CombatUtil.canBlock(attacker, blocker, combat)) { - // this is needed for attackers that can't be blocked by - // more than 1 - currentAttackers.remove(attacker); - combat.addBlocker(attacker, blocker); - if (CombatUtil.canBlock(attacker, leader, combat)) { - combat.addBlocker(attacker, leader); - } - break; - } - } - } - - attackersLeft = (new ArrayList(currentAttackers)); - } - - // Bad Trade Blocks (should only be made if life is in danger) - /** - *

- * makeTradeBlocks. - *

- * - * @param combat - * a {@link forge.game.combat.Combat} object. - * @return a {@link forge.game.combat.Combat} object. - */ - private void makeTradeBlocks(final Combat combat) { - - List currentAttackers = new ArrayList(attackersLeft); - List killingBlockers; - - for (final Card attacker : attackersLeft) { - - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") - || attacker.hasKeyword("CARDNAME can't be blocked unless " + - "all creatures defending player controls block it.")) { - continue; - } - - List possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true); - killingBlockers = getKillingBlockers(combat, attacker, possibleBlockers); - if (!killingBlockers.isEmpty() && ComputerUtilCombat.lifeInDanger(ai, combat)) { - final Card blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers); - combat.addBlocker(attacker, blocker); - currentAttackers.remove(attacker); - } - } - attackersLeft = (new ArrayList(currentAttackers)); - } - - // Chump Blocks (should only be made if life is in danger) - private void makeChumpBlocks(final Combat combat) { - - List currentAttackers = new ArrayList(attackersLeft); - - makeChumpBlocks(combat, currentAttackers); - - if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - makeMultiChumpBlocks(combat); - } - } - - private void makeChumpBlocks(final Combat combat, List attackers) { - - if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) { - return; - } - - Card attacker = attackers.get(0); - - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") - || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { - attackers.remove(0); - makeChumpBlocks(combat, attackers); - return; - } - - List chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true); - if (!chumpBlockers.isEmpty()) { - final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers); - - // check if it's better to block a creature with lower power and without trample - if (attacker.hasKeyword("Trample")) { - final int damageAbsorbed = blocker.getLethalDamage(); - if (attacker.getNetCombatDamage() > damageAbsorbed) { - for (Card other : attackers) { - if (other.equals(attacker)) { - continue; - } - if (other.getNetCombatDamage() >= damageAbsorbed - && !other.hasKeyword("Trample") - && CombatUtil.canBlock(other, blocker, combat)) { - combat.addBlocker(other, blocker); - attackersLeft.remove(other); - blockedButUnkilled.add(other); - attackers.remove(other); - makeChumpBlocks(combat, attackers); - return; - } - } - } - } - - combat.addBlocker(attacker, blocker); - attackersLeft.remove(attacker); - blockedButUnkilled.add(attacker); - } - attackers.remove(0); - makeChumpBlocks(combat, attackers); - } - - // Block creatures with "can't be blocked except by two or more creatures" - private void makeMultiChumpBlocks(final Combat combat) { - - List currentAttackers = new ArrayList(attackersLeft); - - for (final Card attacker : currentAttackers) { - - if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") - && !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { - continue; - } - List possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true); - if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, possibleBlockers.size(), combat)) { - continue; - } - List usedBlockers = new ArrayList(); - for (Card blocker : possibleBlockers) { - if (CombatUtil.canBlock(attacker, blocker, combat)) { - combat.addBlocker(attacker, blocker); - usedBlockers.add(blocker); - if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size(), combat)) { - break; - } - } - } - if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size(), combat)) { - attackersLeft.remove(attacker); - } else { - for (Card blocker : usedBlockers) { - combat.removeBlockAssignment(attacker, blocker); - } - } - } - } - - /** Reinforce blockers blocking attackers with trample (should only be made if life is in danger) */ - private void reinforceBlockersAgainstTrample(final Combat combat) { - - List chumpBlockers; - - List tramplingAttackers = CardLists.getKeyword(attackers, "Trample"); - tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock)); - - // TODO - should check here for a "rampage-like" trigger that replaced - // the keyword: - // "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it." - - for (final Card attacker : tramplingAttackers) { - - if ((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") && !combat.isBlocked(attacker)) - || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") - || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { - continue; - } - - chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, false); - chumpBlockers.removeAll(combat.getBlockers(attacker)); - for (final Card blocker : chumpBlockers) { - // Add an additional blocker if the current blockers are not - // enough and the new one would suck some of the damage - if (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, combat.getBlockers(attacker)) - && ComputerUtilCombat.shieldDamage(attacker, blocker) > 0 - && CombatUtil.canBlock(attacker, blocker, combat) && ComputerUtilCombat.lifeInDanger(ai, combat)) { - combat.addBlocker(attacker, blocker); - } - } - } - } - - /** Support blockers not destroying the attacker with more blockers to try to kill the attacker */ - private void reinforceBlockersToKill(final Combat combat) { - - List safeBlockers; - List blockers; - - List targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock)); - - // TODO - should check here for a "rampage-like" trigger that replaced - // the keyword: - // "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it." - - for (final Card attacker : targetAttackers) { - blockers = getPossibleBlockers(combat, attacker, blockersLeft, false); - blockers.removeAll(combat.getBlockers(attacker)); - - // Try to use safe blockers first - safeBlockers = getSafeBlockers(combat, attacker, blockers); - for (final Card blocker : safeBlockers) { - final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker) - + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); - // Add an additional blocker if the current blockers are not - // enough and the new one would deal additional damage - if ((damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker))) - && ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0 - && CombatUtil.canBlock(attacker, blocker, combat)) { - combat.addBlocker(attacker, blocker); - } - blockers.remove(blocker); // Don't check them again next - } - - // Try to add blockers that could be destroyed, but are worth less - // than the attacker - // Don't use blockers without First Strike or Double Strike if - // attacker has it - if (attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike")) { - safeBlockers = CardLists.getKeyword(blockers, "First Strike"); - safeBlockers.addAll(CardLists.getKeyword(blockers, "Double Strike")); - } else { - safeBlockers = new ArrayList(blockers); - } - - for (final Card blocker : safeBlockers) { - final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker) - + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); - // Add an additional blocker if the current blockers are not - // enough and the new one would deal the remaining damage - final int currentDamage = ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker)); - final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker); - if ((damageNeeded > currentDamage) - && !(damageNeeded > (currentDamage + additionalDamage)) - && ((ComputerUtilCard.evaluateCreature(blocker) + diff) < ComputerUtilCard - .evaluateCreature(attacker)) && CombatUtil.canBlock(attacker, blocker, combat)) { - combat.addBlocker(attacker, blocker); - blockersLeft.remove(blocker); - } - } - } - } - - private void clearBlockers(final Combat combat, final List possibleBlockers) { - - final List oldBlockers = combat.getAllBlockers(); - for (final Card blocker : oldBlockers) { - if ( blocker.getController() == ai ) // don't touch other player's blockers - combat.removeFromCombat(blocker); - } - - attackersLeft = new ArrayList(attackers); // keeps track of all currently unblocked attackers - blockersLeft = new ArrayList(possibleBlockers); // keeps track of all unassigned blockers - blockedButUnkilled = new ArrayList(); // keeps track of all blocked attackers that currently wouldn't be destroyed - } - - /** Assigns blockers for the provided combat instance (in favor of player passes to ctor) */ - public void assignBlockers(final Combat combat) { - - final List possibleBlockers = ai.getCreaturesInPlay(); - - attackers = sortPotentialAttackers(combat); - - if (attackers.isEmpty()) { - return; - } - - clearBlockers(combat, possibleBlockers); - - List blockers; - List chumpBlockers; - - diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade - - // remove all attackers that can't be blocked anyway - for (final Card a : attackers) { - if (!CombatUtil.canBeBlocked(a, ai)) { - attackersLeft.remove(a); - } - } - - // remove all blockers that can't block anyway - for (final Card b : possibleBlockers) { - if (!CombatUtil.canBlock(b, combat)) { - blockersLeft.remove(b); - } - } - - if (attackersLeft.isEmpty()) { - return; - } - - // Begin with the weakest blockers - CardLists.sortByPowerAsc(blockersLeft); - - // == 1. choose best blocks first == - makeGoodBlocks(combat); - makeGangBlocks(combat); - if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - makeTradeBlocks(combat); // choose necessary trade blocks - } - // if life is in danger - if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - makeChumpBlocks(combat); // choose necessary chump blocks - } - // if life is still in danger - // Reinforce blockers blocking attackers with trample if life is still - // in danger - if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - reinforceBlockersAgainstTrample(combat); - } - // Support blockers not destroying the attacker with more blockers to - // try to kill the attacker - if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { - reinforceBlockersToKill(combat); - } - - // == 2. If the AI life would still be in danger make a safer approach == - if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - lifeInDanger = true; - clearBlockers(combat, possibleBlockers); // reset every block assignment - makeTradeBlocks(combat); // choose necessary trade blocks - // if life is in danger - makeGoodBlocks(combat); - // choose necessary chump blocks if life is still in danger - if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - makeChumpBlocks(combat); - } - // Reinforce blockers blocking attackers with trample if life is - // still in danger - if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - reinforceBlockersAgainstTrample(combat); - } - makeGangBlocks(combat); - reinforceBlockersToKill(combat); - } - - // == 3. If the AI life would be in serious danger make an even safer approach == - if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) { - clearBlockers(combat, possibleBlockers); // reset every block assignment - makeChumpBlocks(combat); // choose chump blocks - if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - makeTradeBlocks(combat); // choose necessary trade - } - - if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { - makeGoodBlocks(combat); - } - // Reinforce blockers blocking attackers with trample if life is - // still in danger - else { - reinforceBlockersAgainstTrample(combat); - } - makeGangBlocks(combat); - // Support blockers not destroying the attacker with more blockers - // to try to kill the attacker - reinforceBlockersToKill(combat); - } - - // assign blockers that have to block - chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able."); - // if an attacker with lure attacks - all that can block - for (final Card blocker : blockersLeft) { - if (CombatUtil.mustBlockAnAttacker(blocker, combat)) { - chumpBlockers.add(blocker); - } - } - if (!chumpBlockers.isEmpty()) { - CardLists.shuffle(attackers); - for (final Card attacker : attackers) { - blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false); - for (final Card blocker : blockers) { - if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker) - && (CombatUtil.mustBlockAnAttacker(blocker, combat) - || blocker.hasKeyword("CARDNAME blocks each turn if able."))) { - combat.addBlocker(attacker, blocker); - if (blocker.getMustBlockCards() != null) { - int mustBlockAmt = blocker.getMustBlockCards().size(); - List blockedSoFar = combat.getAttackersBlockedBy(blocker); - boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar); - if (!canBlockAnother || mustBlockAmt == blockedSoFar.size()) { - blockersLeft.remove(blocker); - } - } else { - blockersLeft.remove(blocker); - } - } - } - } - } - } - - public static List orderBlockers(Card attacker, List blockers) { - // ordering of blockers, sort by evaluate, then try to kill the best - int damage = attacker.getNetCombatDamage(); - CardLists.sortByEvaluateCreature(blockers); - final List first = new ArrayList(); - final List last = new ArrayList(); - for (Card blocker : blockers) { - int lethal = ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true); - if (lethal > damage) { - last.add(blocker); - } else { - first.add(blocker); - damage -= lethal; - } - } - first.addAll(last); - - // TODO: Take total damage, and attempt to maximize killing the greatest evaluation of creatures - // It's probably generally better to kill the largest creature, but sometimes its better to kill a few smaller ones - - return first; - } - - public static List orderAttackers(Card blocker, List attackers) { - // This shouldn't really take trample into account, but otherwise should be pretty similar to orderBlockers - // ordering of blockers, sort by evaluate, then try to kill the best - int damage = blocker.getNetCombatDamage(); - CardLists.sortByEvaluateCreature(attackers); - final List first = new ArrayList(); - final List last = new ArrayList(); - for (Card attacker : attackers) { - int lethal = ComputerUtilCombat.getEnoughDamageToKill(attacker, damage, blocker, true); - if (lethal > damage) { - last.add(attacker); - } else { - first.add(attacker); - damage -= lethal; - } - } - first.addAll(last); - - // TODO: Take total damage, and attempt to maximize killing the greatest evaluation of creatures - // It's probably generally better to kill the largest creature, but sometimes its better to kill a few smaller ones - - return first; - } -} +/* + * 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 . + */ +package forge.ai; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; + +import forge.game.GameEntity; +import forge.game.TriggerReplacementBase; +import forge.game.card.Card; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +import forge.game.card.CounterType; +import forge.game.combat.Combat; +import forge.game.combat.CombatUtil; +import forge.game.player.Player; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerType; + + +/** + *

+ * ComputerUtil_Block2 class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class AiBlockController { + + private final Player ai; + /** Constant attackers. */ + private List attackers = new ArrayList(); // all attackers + /** Constant attackersLeft. */ + private List attackersLeft = new ArrayList(); // keeps track of + // all currently + // unblocked + // attackers + /** Constant blockedButUnkilled. */ + private List blockedButUnkilled = new ArrayList(); // blocked + // attackers + // that + // currently + // wouldn't be + // destroyed + /** Constant blockersLeft. */ + private List blockersLeft = new ArrayList(); // keeps track of all + // unassigned + // blockers + private int diff = 0; + + private boolean lifeInDanger = false; + public AiBlockController(Player aiPlayer) { + this.ai = aiPlayer; + } + + // finds the creatures able to block the attacker + private List getPossibleBlockers(final Combat combat, final Card attacker, final List blockersLeft, final boolean solo) { + final List blockers = new ArrayList(); + + for (final Card blocker : blockersLeft) { + // if the blocker can block a creature with lure it can't block a + // creature without + if (CombatUtil.canBlock(attacker, blocker, combat)) { + if (solo && blocker.hasKeyword("CARDNAME can't attack or block alone.")) { + continue; + } + blockers.add(blocker); + } + } + + return blockers; + } + + // finds blockers that won't be destroyed + private List getSafeBlockers(final Combat combat, final Card attacker, final List blockersLeft) { + final List blockers = new ArrayList(); + + for (final Card b : blockersLeft) { + if (!ComputerUtilCombat.canDestroyBlocker(ai, b, attacker, combat, false)) { + blockers.add(b); + } + } + + return blockers; + } + + // finds blockers that destroy the attacker + private List getKillingBlockers(final Combat combat, final Card attacker, final List blockersLeft) { + final List blockers = new ArrayList(); + + for (final Card b : blockersLeft) { + if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, b, combat, false)) { + blockers.add(b); + } + } + + return blockers; + } + + + + private List> sortAttackerByDefender(final Combat combat) { + List defenders = combat.getDefenders(); + final ArrayList> attackers = new ArrayList>(defenders.size()); + for (GameEntity defender : defenders) { + attackers.add(combat.getAttackersOf(defender)); + } + return attackers; + } + + private List sortPotentialAttackers(final Combat combat) { + final List> attackerLists = sortAttackerByDefender(combat); + final List sortedAttackers = new ArrayList(); + final List firstAttacker = attackerLists.get(0); + + final List defenders = combat.getDefenders(); + + + // Begin with the attackers that pose the biggest threat + CardLists.sortByEvaluateCreature(firstAttacker); + CardLists.sortByPowerDesc(firstAttacker); + + // If I don't have any planeswalkers than sorting doesn't really matter + if (defenders.size() == 1) { + return firstAttacker; + } + + final boolean bLifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat); + + // TODO Add creatures attacking Planeswalkers in order of which we want + // to protect + // defend planeswalkers with more loyalty before planeswalkers with less + // loyalty + // if planeswalker will be too difficult to defend don't even bother + for (List attacker : attackerLists) { + // Begin with the attackers that pose the biggest threat + CardLists.sortByPowerDesc(attacker); + for (final Card c : attacker) { + sortedAttackers.add(c); + } + } + + if (bLifeInDanger) { + // add creatures attacking the Player to the front of the list + for (final Card c : firstAttacker) { + sortedAttackers.add(0, c); + } + + } else { + // add creatures attacking the Player to the back of the list + for (final Card c : firstAttacker) { + sortedAttackers.add(c); + } + } + + return sortedAttackers; + } + + // ======================= block assignment functions + // ================================ + + // Good Blocks means a good trade or no trade + private void makeGoodBlocks(final Combat combat) { + + List currentAttackers = new ArrayList(attackersLeft); + + for (final Card attacker : attackersLeft) { + + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") + || attacker.hasKeyword("CARDNAME can't be blocked unless " + + "all creatures defending player controls block it.")) { + continue; + } + + Card blocker = null; + + final List blockers = getPossibleBlockers(combat, attacker, blockersLeft, true); + + final List safeBlockers = getSafeBlockers(combat, attacker, blockers); + List killingBlockers; + + if (safeBlockers.size() > 0) { + // 1.Blockers that can destroy the attacker but won't get + // destroyed + killingBlockers = getKillingBlockers(combat, attacker, safeBlockers); + if (!killingBlockers.isEmpty()) { + blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers); + } else if (!attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { + blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers); + blockedButUnkilled.add(attacker); + } + } // no safe blockers + else { + // 3.Blockers that can destroy the attacker and have an upside when dying + killingBlockers = getKillingBlockers(combat, attacker, blockers); + for (Card b : killingBlockers) { + if ((b.hasKeyword("Undying") && b.getCounters(CounterType.P1P1) == 0) + || !b.getSVar("SacMe").equals("")) { + blocker = b; + break; + } + } + // 4.Blockers that can destroy the attacker and are worth less + if (blocker == null && !killingBlockers.isEmpty()) { + final Card worst = ComputerUtilCard.getWorstCreatureAI(killingBlockers); + int value = ComputerUtilCard.evaluateCreature(attacker); + + // check for triggers when unblocked + for (Trigger trigger : attacker.getTriggers()) { + final HashMap trigParams = trigger.getMapParams(); + TriggerType mode = trigger.getMode(); + + if (!trigger.requirementsCheck(attacker.getGame())) { + continue; + } + + if (mode == TriggerType.DamageDone) { + if ((!trigParams.containsKey("ValidSource") + || TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), attacker)) + && attacker.getNetCombatDamage() > 0 + && (!trigParams.containsKey("ValidTarget") + || TriggerReplacementBase.matchesValid(combat.getDefenderByAttacker(attacker), trigParams.get("ValidTarget").split(","), attacker))) { + value += 50; + } + } else if (mode == TriggerType.AttackerUnblocked) { + if (TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), attacker)) { + value += 50; + } + } + } + + if ((ComputerUtilCard.evaluateCreature(worst) + diff) < value) { + blocker = worst; + } + } + } + if (blocker != null) { + currentAttackers.remove(attacker); + combat.addBlocker(attacker, blocker); + } + } + attackersLeft = (new ArrayList(currentAttackers)); + } + + // Good Gang Blocks means a good trade or no trade + /** + *

+ * makeGangBlocks. + *

+ * + * @param combat + * a {@link forge.game.combat.Combat} object. + * @return a {@link forge.game.combat.Combat} object. + */ + static final Predicate rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT")); + + private void makeGangBlocks(final Combat combat) { + List currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock)); + List blockers; + + // Try to block an attacker without first strike with a gang of first strikers + for (final Card attacker : attackersLeft) { + if (!attacker.hasKeyword("First Strike") && !attacker.hasKeyword("Double Strike")) { + blockers = getPossibleBlockers(combat, attacker, blockersLeft, false); + final List firstStrikeBlockers = new ArrayList(); + final List blockGang = new ArrayList(); + for (int i = 0; i < blockers.size(); i++) { + if (blockers.get(i).hasFirstStrike() || blockers.get(i).hasDoubleStrike()) { + firstStrikeBlockers.add(blockers.get(i)); + } + } + + if (firstStrikeBlockers.size() > 1) { + CardLists.sortByPowerDesc(firstStrikeBlockers); + for (final Card blocker : firstStrikeBlockers) { + final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker) + + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); + // if the total damage of the blockgang was not enough + // without but is enough with this blocker finish the + // blockgang + if (ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang) < damageNeeded + || CombatUtil.needsBlockers(attacker) > blockGang.size()) { + blockGang.add(blocker); + if (ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang) >= damageNeeded) { + currentAttackers.remove(attacker); + for (final Card b : blockGang) { + if (CombatUtil.canBlock(attacker, blocker, combat)) { + combat.addBlocker(attacker, b); + } + } + } + } + } + } + } + } + + attackersLeft = (new ArrayList(currentAttackers)); + currentAttackers = new ArrayList(attackersLeft); + + // Try to block an attacker with two blockers of which only one will die + for (final Card attacker : attackersLeft) { + blockers = getPossibleBlockers(combat, attacker, blockersLeft, false); + List usableBlockers; + final List blockGang = new ArrayList(); + int absorbedDamage = 0; // The amount of damage needed to kill the first blocker + int currentValue = 0; // The value of the creatures in the blockgang + + // AI can't handle good triple blocks yet + if (CombatUtil.needsBlockers(attacker) > 2) { + continue; + } + + // Try to add blockers that could be destroyed, but are worth less than the attacker + // Don't use blockers without First Strike or Double Strike if attacker has it + usableBlockers = CardLists.filter(blockers, new Predicate() { + @Override + public boolean apply(final Card c) { + if ((attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike")) + && !(c.hasKeyword("First Strike") || c.hasKeyword("Double Strike"))) { + return false; + } + return lifeInDanger || (ComputerUtilCard.evaluateCreature(c) + diff) < ComputerUtilCard.evaluateCreature(attacker); + } + }); + if (usableBlockers.size() < 2) { + return; + } + + final Card leader = ComputerUtilCard.getBestCreatureAI(usableBlockers); + blockGang.add(leader); + usableBlockers.remove(leader); + absorbedDamage = ComputerUtilCombat.getEnoughDamageToKill(leader, attacker.getNetCombatDamage(), attacker, true); + currentValue = ComputerUtilCard.evaluateCreature(leader); + + for (final Card blocker : usableBlockers) { + // Add an additional blocker if the current blockers are not + // enough and the new one would deal the remaining damage + final int currentDamage = ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang); + final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker); + final int absorbedDamage2 = ComputerUtilCombat.getEnoughDamageToKill(blocker, attacker.getNetCombatDamage(), attacker, true); + final int addedValue = ComputerUtilCard.evaluateCreature(blocker); + final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker) + + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); + if ((damageNeeded > currentDamage || CombatUtil.needsBlockers(attacker) > blockGang.size()) + && !(damageNeeded > currentDamage + additionalDamage) + // The attacker will be killed + && (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage() + // only one blocker can be killed + || currentValue + addedValue - 50 <= ComputerUtilCard.evaluateCreature(attacker) + // or attacker is worth more + || (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat))) + // or life is in danger + && CombatUtil.canBlock(attacker, blocker, combat)) { + // this is needed for attackers that can't be blocked by + // more than 1 + currentAttackers.remove(attacker); + combat.addBlocker(attacker, blocker); + if (CombatUtil.canBlock(attacker, leader, combat)) { + combat.addBlocker(attacker, leader); + } + break; + } + } + } + + attackersLeft = (new ArrayList(currentAttackers)); + } + + // Bad Trade Blocks (should only be made if life is in danger) + /** + *

+ * makeTradeBlocks. + *

+ * + * @param combat + * a {@link forge.game.combat.Combat} object. + * @return a {@link forge.game.combat.Combat} object. + */ + private void makeTradeBlocks(final Combat combat) { + + List currentAttackers = new ArrayList(attackersLeft); + List killingBlockers; + + for (final Card attacker : attackersLeft) { + + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") + || attacker.hasKeyword("CARDNAME can't be blocked unless " + + "all creatures defending player controls block it.")) { + continue; + } + + List possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true); + killingBlockers = getKillingBlockers(combat, attacker, possibleBlockers); + if (!killingBlockers.isEmpty() && ComputerUtilCombat.lifeInDanger(ai, combat)) { + final Card blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers); + combat.addBlocker(attacker, blocker); + currentAttackers.remove(attacker); + } + } + attackersLeft = (new ArrayList(currentAttackers)); + } + + // Chump Blocks (should only be made if life is in danger) + private void makeChumpBlocks(final Combat combat) { + + List currentAttackers = new ArrayList(attackersLeft); + + makeChumpBlocks(combat, currentAttackers); + + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { + makeMultiChumpBlocks(combat); + } + } + + private void makeChumpBlocks(final Combat combat, List attackers) { + + if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) { + return; + } + + Card attacker = attackers.get(0); + + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") + || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { + attackers.remove(0); + makeChumpBlocks(combat, attackers); + return; + } + + List chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true); + if (!chumpBlockers.isEmpty()) { + final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers); + + // check if it's better to block a creature with lower power and without trample + if (attacker.hasKeyword("Trample")) { + final int damageAbsorbed = blocker.getLethalDamage(); + if (attacker.getNetCombatDamage() > damageAbsorbed) { + for (Card other : attackers) { + if (other.equals(attacker)) { + continue; + } + if (other.getNetCombatDamage() >= damageAbsorbed + && !other.hasKeyword("Trample") + && CombatUtil.canBlock(other, blocker, combat)) { + combat.addBlocker(other, blocker); + attackersLeft.remove(other); + blockedButUnkilled.add(other); + attackers.remove(other); + makeChumpBlocks(combat, attackers); + return; + } + } + } + } + + combat.addBlocker(attacker, blocker); + attackersLeft.remove(attacker); + blockedButUnkilled.add(attacker); + } + attackers.remove(0); + makeChumpBlocks(combat, attackers); + } + + // Block creatures with "can't be blocked except by two or more creatures" + private void makeMultiChumpBlocks(final Combat combat) { + + List currentAttackers = new ArrayList(attackersLeft); + + for (final Card attacker : currentAttackers) { + + if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") + && !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { + continue; + } + List possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true); + if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, possibleBlockers.size(), combat)) { + continue; + } + List usedBlockers = new ArrayList(); + for (Card blocker : possibleBlockers) { + if (CombatUtil.canBlock(attacker, blocker, combat)) { + combat.addBlocker(attacker, blocker); + usedBlockers.add(blocker); + if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size(), combat)) { + break; + } + } + } + if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size(), combat)) { + attackersLeft.remove(attacker); + } else { + for (Card blocker : usedBlockers) { + combat.removeBlockAssignment(attacker, blocker); + } + } + } + } + + /** Reinforce blockers blocking attackers with trample (should only be made if life is in danger) */ + private void reinforceBlockersAgainstTrample(final Combat combat) { + + List chumpBlockers; + + List tramplingAttackers = CardLists.getKeyword(attackers, "Trample"); + tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock)); + + // TODO - should check here for a "rampage-like" trigger that replaced + // the keyword: + // "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it." + + for (final Card attacker : tramplingAttackers) { + + if ((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") && !combat.isBlocked(attacker)) + || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") + || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { + continue; + } + + chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, false); + chumpBlockers.removeAll(combat.getBlockers(attacker)); + for (final Card blocker : chumpBlockers) { + // Add an additional blocker if the current blockers are not + // enough and the new one would suck some of the damage + if (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, combat.getBlockers(attacker)) + && ComputerUtilCombat.shieldDamage(attacker, blocker) > 0 + && CombatUtil.canBlock(attacker, blocker, combat) && ComputerUtilCombat.lifeInDanger(ai, combat)) { + combat.addBlocker(attacker, blocker); + } + } + } + } + + /** Support blockers not destroying the attacker with more blockers to try to kill the attacker */ + private void reinforceBlockersToKill(final Combat combat) { + + List safeBlockers; + List blockers; + + List targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock)); + + // TODO - should check here for a "rampage-like" trigger that replaced + // the keyword: + // "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it." + + for (final Card attacker : targetAttackers) { + blockers = getPossibleBlockers(combat, attacker, blockersLeft, false); + blockers.removeAll(combat.getBlockers(attacker)); + + // Try to use safe blockers first + safeBlockers = getSafeBlockers(combat, attacker, blockers); + for (final Card blocker : safeBlockers) { + final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker) + + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); + // Add an additional blocker if the current blockers are not + // enough and the new one would deal additional damage + if ((damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker))) + && ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0 + && CombatUtil.canBlock(attacker, blocker, combat)) { + combat.addBlocker(attacker, blocker); + } + blockers.remove(blocker); // Don't check them again next + } + + // Try to add blockers that could be destroyed, but are worth less + // than the attacker + // Don't use blockers without First Strike or Double Strike if + // attacker has it + if (attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike")) { + safeBlockers = CardLists.getKeyword(blockers, "First Strike"); + safeBlockers.addAll(CardLists.getKeyword(blockers, "Double Strike")); + } else { + safeBlockers = new ArrayList(blockers); + } + + for (final Card blocker : safeBlockers) { + final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker) + + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); + // Add an additional blocker if the current blockers are not + // enough and the new one would deal the remaining damage + final int currentDamage = ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker)); + final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker); + if ((damageNeeded > currentDamage) + && !(damageNeeded > (currentDamage + additionalDamage)) + && ((ComputerUtilCard.evaluateCreature(blocker) + diff) < ComputerUtilCard + .evaluateCreature(attacker)) && CombatUtil.canBlock(attacker, blocker, combat)) { + combat.addBlocker(attacker, blocker); + blockersLeft.remove(blocker); + } + } + } + } + + private void clearBlockers(final Combat combat, final List possibleBlockers) { + + final List oldBlockers = combat.getAllBlockers(); + for (final Card blocker : oldBlockers) { + if ( blocker.getController() == ai ) // don't touch other player's blockers + combat.removeFromCombat(blocker); + } + + attackersLeft = new ArrayList(attackers); // keeps track of all currently unblocked attackers + blockersLeft = new ArrayList(possibleBlockers); // keeps track of all unassigned blockers + blockedButUnkilled = new ArrayList(); // keeps track of all blocked attackers that currently wouldn't be destroyed + } + + /** Assigns blockers for the provided combat instance (in favor of player passes to ctor) */ + public void assignBlockers(final Combat combat) { + + final List possibleBlockers = ai.getCreaturesInPlay(); + + attackers = sortPotentialAttackers(combat); + + if (attackers.isEmpty()) { + return; + } + + clearBlockers(combat, possibleBlockers); + + List blockers; + List chumpBlockers; + + diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade + + // remove all attackers that can't be blocked anyway + for (final Card a : attackers) { + if (!CombatUtil.canBeBlocked(a, ai)) { + attackersLeft.remove(a); + } + } + + // remove all blockers that can't block anyway + for (final Card b : possibleBlockers) { + if (!CombatUtil.canBlock(b, combat)) { + blockersLeft.remove(b); + } + } + + if (attackersLeft.isEmpty()) { + return; + } + + // Begin with the weakest blockers + CardLists.sortByPowerAsc(blockersLeft); + + // == 1. choose best blocks first == + makeGoodBlocks(combat); + makeGangBlocks(combat); + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { + makeTradeBlocks(combat); // choose necessary trade blocks + } + // if life is in danger + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { + makeChumpBlocks(combat); // choose necessary chump blocks + } + // if life is still in danger + // Reinforce blockers blocking attackers with trample if life is still + // in danger + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { + reinforceBlockersAgainstTrample(combat); + } + // Support blockers not destroying the attacker with more blockers to + // try to kill the attacker + if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { + reinforceBlockersToKill(combat); + } + + // == 2. If the AI life would still be in danger make a safer approach == + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { + lifeInDanger = true; + clearBlockers(combat, possibleBlockers); // reset every block assignment + makeTradeBlocks(combat); // choose necessary trade blocks + // if life is in danger + makeGoodBlocks(combat); + // choose necessary chump blocks if life is still in danger + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { + makeChumpBlocks(combat); + } + // Reinforce blockers blocking attackers with trample if life is + // still in danger + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { + reinforceBlockersAgainstTrample(combat); + } + makeGangBlocks(combat); + reinforceBlockersToKill(combat); + } + + // == 3. If the AI life would be in serious danger make an even safer approach == + if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) { + clearBlockers(combat, possibleBlockers); // reset every block assignment + makeChumpBlocks(combat); // choose chump blocks + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { + makeTradeBlocks(combat); // choose necessary trade + } + + if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { + makeGoodBlocks(combat); + } + // Reinforce blockers blocking attackers with trample if life is + // still in danger + else { + reinforceBlockersAgainstTrample(combat); + } + makeGangBlocks(combat); + // Support blockers not destroying the attacker with more blockers + // to try to kill the attacker + reinforceBlockersToKill(combat); + } + + // assign blockers that have to block + chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able."); + // if an attacker with lure attacks - all that can block + for (final Card blocker : blockersLeft) { + if (CombatUtil.mustBlockAnAttacker(blocker, combat)) { + chumpBlockers.add(blocker); + } + } + if (!chumpBlockers.isEmpty()) { + CardLists.shuffle(attackers); + for (final Card attacker : attackers) { + blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false); + for (final Card blocker : blockers) { + if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker) + && (CombatUtil.mustBlockAnAttacker(blocker, combat) + || blocker.hasKeyword("CARDNAME blocks each turn if able."))) { + combat.addBlocker(attacker, blocker); + if (blocker.getMustBlockCards() != null) { + int mustBlockAmt = blocker.getMustBlockCards().size(); + List blockedSoFar = combat.getAttackersBlockedBy(blocker); + boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar); + if (!canBlockAnother || mustBlockAmt == blockedSoFar.size()) { + blockersLeft.remove(blocker); + } + } else { + blockersLeft.remove(blocker); + } + } + } + } + } + } + + public static List orderBlockers(Card attacker, List blockers) { + // ordering of blockers, sort by evaluate, then try to kill the best + int damage = attacker.getNetCombatDamage(); + CardLists.sortByEvaluateCreature(blockers); + final List first = new ArrayList(); + final List last = new ArrayList(); + for (Card blocker : blockers) { + int lethal = ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true); + if (lethal > damage) { + last.add(blocker); + } else { + first.add(blocker); + damage -= lethal; + } + } + first.addAll(last); + + // TODO: Take total damage, and attempt to maximize killing the greatest evaluation of creatures + // It's probably generally better to kill the largest creature, but sometimes its better to kill a few smaller ones + + return first; + } + + public static List orderAttackers(Card blocker, List attackers) { + // This shouldn't really take trample into account, but otherwise should be pretty similar to orderBlockers + // ordering of blockers, sort by evaluate, then try to kill the best + int damage = blocker.getNetCombatDamage(); + CardLists.sortByEvaluateCreature(attackers); + final List first = new ArrayList(); + final List last = new ArrayList(); + for (Card attacker : attackers) { + int lethal = ComputerUtilCombat.getEnoughDamageToKill(attacker, damage, blocker, true); + if (lethal > damage) { + last.add(attacker); + } else { + first.add(attacker); + damage -= lethal; + } + } + first.addAll(last); + + // TODO: Take total damage, and attempt to maximize killing the greatest evaluation of creatures + // It's probably generally better to kill the largest creature, but sometimes its better to kill a few smaller ones + + return first; + } +} diff --git a/forge-game/src/main/java/forge/ai/AiController.java b/forge-gui/src/main/java/forge/ai/AiController.java similarity index 96% rename from forge-game/src/main/java/forge/ai/AiController.java rename to forge-gui/src/main/java/forge/ai/AiController.java index 5dfb3ef5199..d1e805ff55f 100644 --- a/forge-game/src/main/java/forge/ai/AiController.java +++ b/forge-gui/src/main/java/forge/ai/AiController.java @@ -1,1110 +1,1111 @@ -/* - * 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 . - */ -package forge.ai; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import com.esotericsoftware.minlog.Log; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - -import forge.PreferencesBridge; -import forge.card.CardType; -import forge.card.MagicColor; -import forge.deck.CardPool; -import forge.deck.Deck; -import forge.deck.DeckSection; -import forge.game.GameActionUtil; -import forge.game.Game; -import forge.game.GameEntity; -import forge.game.ability.ApiType; -import forge.game.ai.AiProps; -import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; -import forge.game.card.CardPredicates.Presets; -import forge.game.card.CounterType; -import forge.game.combat.Combat; -import forge.game.cost.CostDiscard; -import forge.game.cost.CostPart; -import forge.game.phase.PhaseType; -import forge.game.player.Player; -import forge.game.player.PlayerActionConfirmMode; -import forge.game.spellability.AbilityManaPart; -import forge.game.spellability.Spell; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.SpellPermanent; -import forge.game.zone.ZoneType; -import forge.item.PaperCard; -import forge.util.Aggregates; -import forge.util.Expressions; -import forge.util.MyRandom; - -/** - *

- * ComputerAI_General class. - *

- * - * @author Forge - * @version $Id: AiController.java 24379 2014-01-20 14:13:13Z swordshine $ - */ -public class AiController { - - private final Player player; - private final Game game; - public boolean canCheatShuffle; - public Game getGame() - { - return game; - } - - public Player getPlayer() - { - return player; - } - - /** - *

- * Constructor for ComputerAI_General. - *

- */ - public AiController(final Player computerPlayer, final Game game0) { - player = computerPlayer; - game = game0; - - canCheatShuffle = PreferencesBridge.Instance.getEnableAiCheats(); - } - - /** - *

- * getAvailableSpellAbilities. - *

- * - * @return a {@link forge.CardList} object. - */ - private List getAvailableCards() { - List all = new ArrayList(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; - } - - /** - *

- * getPossibleETBCounters. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - private ArrayList getPossibleETBCounters() { - - final Player opp = player.getOpponent(); - List all = new ArrayList(player.getCardsIn(ZoneType.Hand)); - all.addAll(player.getCardsIn(ZoneType.Exile)); - all.addAll(player.getCardsIn(ZoneType.Graveyard)); - if (!player.getCardsIn(ZoneType.Library).isEmpty()) { - all.add(player.getCardsIn(ZoneType.Library).get(0)); - } - all.addAll(opp.getCardsIn(ZoneType.Exile)); - - final ArrayList spellAbilities = new ArrayList(); - for (final Card c : all) { - for (final SpellAbility sa : c.getNonManaSpellAbilities()) { - if (sa instanceof SpellPermanent) { - sa.setActivatingPlayer(player); - if (SpellPermanent.checkETBEffects(c, sa, ApiType.Counter, player)) { - spellAbilities.add(sa); - } - } - } - } - return spellAbilities; - } - - private List getOriginalAndAltCostAbilities(final List originList) - { - final ArrayList newAbilities = new ArrayList(); - for (SpellAbility sa : originList) { - sa.setActivatingPlayer(player); - //add alternative costs as additional spell abilities - newAbilities.add(sa); - newAbilities.addAll(GameActionUtil.getAlternativeCosts(sa)); - } - - final List result = new ArrayList(); - for (SpellAbility sa : newAbilities) { - sa.setActivatingPlayer(player); - result.addAll(GameActionUtil.getOptionalCosts(sa)); - } - return result; - } - - /** - * Returns the spellAbilities from the card list. - * - * @param l - * a {@link forge.CardList} object. - * @return an array of {@link forge.game.spellability.SpellAbility} objects. - */ - private ArrayList getSpellAbilities(final List l) { - final ArrayList spellAbilities = new ArrayList(); - for (final Card c : l) { - for (final SpellAbility sa : c.getSpellAbilities()) { - spellAbilities.add(sa); - } - } - return spellAbilities; - } - - /** - *

- * getPlayableCounters. - *

- * - * @param l - * a {@link forge.CardList} object. - * @return a {@link java.util.ArrayList} object. - */ - private ArrayList getPlayableCounters(final List l) { - final ArrayList spellAbility = new ArrayList(); - for (final Card c : l) { - for (final SpellAbility sa : c.getNonManaSpellAbilities()) { - // Check if this AF is a Counterpsell - if (sa.getApi() == ApiType.Counter) { - spellAbility.add(sa); - } - } - } - - return spellAbility; - } - - // plays a land if one is available - /** - *

- * chooseLandsToPlay. - *

- * - * @return a boolean. - */ - public List getLandsToPlay() { - - final List hand = new ArrayList(player.getCardsIn(ZoneType.Hand)); - hand.addAll(player.getCardsIn(ZoneType.Exile)); - List landList = CardLists.filter(hand, Presets.LANDS); - List nonLandList = CardLists.filter(hand, Predicates.not(CardPredicates.Presets.LANDS)); - - //filter out cards that can't be played - landList = CardLists.filter(landList, new Predicate() { - @Override - public boolean apply(final Card c) { - return player.canPlayLand(c); - } - }); - - final List landsNotInHand = new ArrayList(player.getCardsIn(ZoneType.Graveyard)); - if (!player.getCardsIn(ZoneType.Library).isEmpty()) { - landsNotInHand.add(player.getCardsIn(ZoneType.Library).get(0)); - } - for (final Card crd : landsNotInHand) { - if (crd.isLand() && crd.hasKeyword("May be played")) { - landList.add(crd); - } - } - if (landList.isEmpty()) { - return null; - } - if (landList.size() == 1 && nonLandList.size() < 3) { - List cardsInPlay = player.getCardsIn(ZoneType.Battlefield); - List landsInPlay = CardLists.filter(cardsInPlay, Presets.LANDS); - List allCards = new ArrayList(player.getCardsIn(ZoneType.Graveyard)); - allCards.addAll(player.getCardsIn(ZoneType.Command)); - allCards.addAll(cardsInPlay); - int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc); - int max = Math.max(maxCmcInHand, 6); - // consider not playing lands if there are enough already and an ability with a discard cost is present - if (landsInPlay.size() + landList.size() > max) { - for (Card c : allCards) { - for (SpellAbility sa : c.getSpellAbilities()) { - if (sa.getPayCosts() != null) { - for (CostPart part : sa.getPayCosts().getCostParts()) { - if (part instanceof CostDiscard) { - return null; - } - } - } - } - } - } - } - - landList = CardLists.filter(landList, new Predicate() { - @Override - public boolean apply(final Card c) { - if (c.getSVar("NeedsToPlay").length() > 0) { - final String needsToPlay = c.getSVar("NeedsToPlay"); - List list = game.getCardsIn(ZoneType.Battlefield); - - list = CardLists.getValidCards(list, needsToPlay.split(","), c.getController(), c); - if (list.isEmpty()) { - return false; - } - } - if (c.getSVar("NeedsToPlayVar").length() > 0) { - final String needsToPlay = c.getSVar("NeedsToPlayVar"); - int x = 0; - int y = 0; - String sVar = needsToPlay.split(" ")[0]; - String comparator = needsToPlay.split(" ")[1]; - String compareTo = comparator.substring(2); - try { - x = Integer.parseInt(sVar); - } catch (final NumberFormatException e) { - x = CardFactoryUtil.xCount(c, c.getSVar(sVar)); - } - try { - y = Integer.parseInt(compareTo); - } catch (final NumberFormatException e) { - y = CardFactoryUtil.xCount(c, c.getSVar(compareTo)); - } - if (!Expressions.compare(x, comparator, y)) { - return false; - } - } - if (c.isType("Legendary") && !c.getName().equals("Flagstones of Trokair")) { - final List list = player.getCardsIn(ZoneType.Battlefield); - if (Iterables.any(list, CardPredicates.nameEquals(c.getName()))) { - return false; - } - } - - // don't play the land if it has cycling and enough lands are - // available - final ArrayList spellAbilities = c.getSpellAbilities(); - - final List hand = player.getCardsIn(ZoneType.Hand); - List lands = player.getCardsIn(ZoneType.Battlefield); - lands.addAll(hand); - lands = CardLists.filter(lands, CardPredicates.Presets.LANDS); - int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc); - for (final SpellAbility sa : spellAbilities) { - if (sa.isCycling()) { - if (lands.size() >= Math.max(maxCmcInHand, 6)) { - return false; - } - } - } - return true; - } - }); - - return landList; - } - - public Card chooseBestLandToPlay(List landList) - { - if (landList.isEmpty()) - return null; - - //Skip reflected lands. - List unreflectedLands = new ArrayList(landList); - for (Card l : landList) { - if (l.isReflectedLand()) { - unreflectedLands.remove(l); - } - } - - if (!unreflectedLands.isEmpty()) { - landList = unreflectedLands; - } - - // Choose first land to be able to play a one drop - if (player.getLandsInPlay().isEmpty()) { - List oneDrops = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(1)); - for (int i = 0; i < MagicColor.WUBRG.length; i++) { - byte color = MagicColor.WUBRG[i]; - if (!CardLists.filter(oneDrops, CardPredicates.isColor(color)).isEmpty()) { - for (Card land : landList) { - if (land.isType(MagicColor.Constant.BASIC_LANDS.get(i))) - return land; - - for (final SpellAbility m : ComputerUtilMana.getAIPlayableMana(land)) { - AbilityManaPart mp = m.getManaPart(); - if (mp.canProduce(MagicColor.toShortString(color), m)) { - return land; - } - } - } - } - } - } - - //play basic lands that are needed the most - if (Iterables.any(landList, CardPredicates.Presets.BASIC_LANDS)) { - final List combined = player.getCardsIn(ZoneType.Battlefield); - - final ArrayList basics = new ArrayList(); - - // what types can I go get? - for (final String name : CardType.Constant.BASIC_TYPES) { - if (Iterables.any(landList, CardPredicates.isType(name))) { - basics.add(name); - } - } - - // Which basic land is least available from hand and play, that I still - // have in my deck - int minSize = Integer.MAX_VALUE; - String minType = null; - - for (int i = 0; i < basics.size(); i++) { - final String b = basics.get(i); - final int num = CardLists.getType(combined, b).size(); - if (num < minSize) { - minType = b; - minSize = num; - } - } - - if (minType != null) { - landList = CardLists.getType(landList, minType); - } - } - return landList.get(0); - } - - // if return true, go to next phase - /** - *

- * playCounterSpell. - *

- * - * @param possibleCounters - * a {@link java.util.ArrayList} object. - * @return a boolean. - */ - private SpellAbility chooseCounterSpell(final List possibleCounters) { - if ( possibleCounters == null || possibleCounters.isEmpty()) - return null;; - - SpellAbility bestSA = null; - int bestRestriction = Integer.MIN_VALUE; - - - for (final SpellAbility sa : getOriginalAndAltCostAbilities(possibleCounters)) { - SpellAbility currentSA = sa; - sa.setActivatingPlayer(player); - // check everything necessary - if (canPlayAndPayFor(currentSA)) { - if (bestSA == null) { - bestSA = currentSA; - bestRestriction = ComputerUtil.counterSpellRestriction(player, currentSA); - } else { - // Compare bestSA with this SA - final int restrictionLevel = ComputerUtil.counterSpellRestriction(player, currentSA); - - if (restrictionLevel > bestRestriction) { - bestRestriction = restrictionLevel; - bestSA = currentSA; - } - } - } - } - - // TODO - "Look" at Targeted SA and "calculate" the threshold - // if (bestRestriction < targetedThreshold) return false; - return bestSA; - } // playCounterSpell() - - // if return true, go to next phase - /** - *

- * playSpellAbilities. - *

- * - * @param all - * an array of {@link forge.game.spellability.SpellAbility} - * objects. - * @return a boolean. - */ - private SpellAbility chooseSpellAbilyToPlay(final List all, boolean skipCounter) { - if ( all == null || all.isEmpty() ) - return null; - - Collections.sort(all, saComparator); // put best spells first - - for (final SpellAbility sa : getOriginalAndAltCostAbilities(all)) { - // Don't add Counterspells to the "normal" playcard lookups - if (sa.getApi() == ApiType.Counter && skipCounter) { - continue; - } - sa.setActivatingPlayer(player); - - if (!canPlayAndPayFor(sa)) - continue; - - return sa; - } - - return null; - } // playCards() - - - // This is for playing spells regularly (no Cascade/Ripple etc.) - private boolean canPlayAndPayFor(final SpellAbility sa) { - if (!sa.canPlay()) { - return false; - } - //System.out.printf("Ai thinks of %s @ %s >>> ", sa, sa.getActivatingPlayer().getGame().getPhaseHandler().debugPrintState()); - if (!sa.canPlayAI(player)) { - return false; - } - //System.out.printf("wouldPlay: %s, canPay: %s%n", aiWouldPlay, canPay); - return ComputerUtilCost.canPayCost(sa, player); - } - - // not sure "playing biggest spell" matters? - private final static Comparator saComparator = new Comparator() { - @Override - public int compare(final SpellAbility a, final SpellAbility b) { - // sort from highest cost to lowest - // we want the highest costs first - int a1 = a.getPayCosts() == null ? 0 : a.getPayCosts().getTotalMana().getCMC(); - int b1 = b.getPayCosts() == null ? 0 : b.getPayCosts().getTotalMana().getCMC(); - - // deprioritize planar die roll marked with AIRollPlanarDieParams:LowPriority$ True - if (ApiType.RollPlanarDice == a.getApi() && a.getSourceCard().hasSVar("AIRollPlanarDieParams") && a.getSourceCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) { - return 1; - } else if (ApiType.RollPlanarDice == b.getApi() && b.getSourceCard().hasSVar("AIRollPlanarDieParams") && b.getSourceCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) { - return -1; - } - - // cast 0 mana cost spells first (might be a Mox) - if (a1 == 0) { - return -1; - } else if (b1 == 0) { - return 1; - } - - a1 += getSpellAbilityPriority(a); - b1 += getSpellAbilityPriority(b); - - return b1 - a1; - } - - private int getSpellAbilityPriority(SpellAbility sa) { - int p = 0; - Card source = sa.getSourceCard(); - // puts creatures in front of spells - if (source.isCreature()) { - p += 1; - } - // don't play equipments before having any creatures - if (source.isEquipment() && sa.getSourceCard().getController().getCreaturesInPlay().isEmpty()) { - p -= 9; - } - // artifacts and enchantments with effects that do not stack - if ("True".equals(source.getSVar("NonStackingEffect")) && source.getController().isCardInPlay(source.getName())) { - p -= 9; - } - // sort planeswalker abilities for ultimate - if (sa.getRestrictions().getPlaneswalker()) { - if (sa.hasParam("Ultimate")) { - p += 9; - } - } - - if (ApiType.DestroyAll == sa.getApi()) { - p += 4; - } - - return p; - } - }; // Comparator - - /** - *

- * AI_discardNumType. - *

- * - * @param numDiscard - * a int. - * @param uTypes - * an array of {@link java.lang.String} objects. May be null for - * no restrictions. - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a List of discarded cards. - */ - public List getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa) { - List hand = new ArrayList(player.getCardsIn(ZoneType.Hand)); - - - if ((uTypes != null) && (sa != null)) { - hand = CardLists.getValidCards(hand, uTypes, sa.getActivatingPlayer(), sa.getSourceCard()); - } - return getCardsToDiscard(numDiscard, numDiscard, hand, sa); - } - - public List getCardsToDiscard(final int min, final int max, final List validCards, final SpellAbility sa) { - - if (validCards.size() < min) { - return null; - } - - Card sourceCard = null; - final List discardList = new ArrayList(); - int count = 0; - if (sa != null) { - sourceCard = sa.getSourceCard(); - } - - // look for good discards - while (count < min) { - Card prefCard = null; - if (sa != null && sa.getActivatingPlayer() != null && sa.getActivatingPlayer().isOpponentOf(player)) { - for (Card c : validCards) { - if (c.hasKeyword("If a spell or ability an opponent controls causes you to discard CARDNAME," - + " put it onto the battlefield instead of putting it into your graveyard.") - || !c.getSVar("DiscardMeByOpp").isEmpty()) { - prefCard = c; - break; - } - } - } - if (prefCard == null) { - prefCard = ComputerUtil.getCardPreference(player, sourceCard, "DiscardCost", validCards); - } - if (prefCard != null) { - discardList.add(prefCard); - validCards.remove(prefCard); - count++; - } else { - break; - } - } - - final int discardsLeft = min - count; - - // choose rest - for (int i = 0; i < discardsLeft; i++) { - if (validCards.isEmpty()) { - continue; - } - final int numLandsInPlay = Iterables.size(Iterables.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS)); - final List landsInHand = CardLists.filter(validCards, CardPredicates.Presets.LANDS); - final int numLandsInHand = landsInHand.size(); - - // Discard a land - boolean canDiscardLands = numLandsInHand > 3 || (numLandsInHand > 2 && numLandsInPlay > 0) - || (numLandsInHand > 1 && numLandsInPlay > 2) || (numLandsInHand > 0 && numLandsInPlay > 5); - - if (canDiscardLands) { - discardList.add(landsInHand.get(0)); - validCards.remove(landsInHand.get(0)); - } else { // Discard other stuff - CardLists.sortByCmcDesc(validCards); - int numLandsAvailable = numLandsInPlay; - if (numLandsInHand > 0) { - numLandsAvailable++; - } - //Discard unplayable card - if (validCards.get(0).getCMC() > numLandsAvailable) { - discardList.add(validCards.get(0)); - validCards.remove(validCards.get(0)); - } else { //Discard worst card - Card worst = ComputerUtilCard.getWorstAI(validCards); - discardList.add(worst); - validCards.remove(worst); - } - } - } - - return discardList; - } - - @SuppressWarnings("incomplete-switch") - public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) { - ApiType api = sa.getApi(); - - // Abilities without api may also use this routine, However they should provide a unique mode value - if ( null == api ) { - if( mode != null ) switch (mode) { - // case BraidOfFire: return true; - // case Ripple: return true; - } - - String exMsg = String.format("AI confirmAction does not know what to decide about %s mode (api is null).", mode); - throw new IllegalArgumentException(exMsg); - - } else - return api.getAi().confirmAction(player, sa, mode, message); - } - - public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) { - if (logic.equalsIgnoreCase("ProtectFriendly")) { - final Player controller = hostCard.getController(); - if (affected instanceof Player) { - return !((Player) affected).isOpponentOf(controller); - } else if (affected instanceof Card) { - return !((Card) affected).getController().isOpponentOf(controller); - } - } - return true; - } - -// /** -// * AI decides if he wants to use dredge ability and which one if many available -// * @param dredgers - contains at least single element -// * @return -// */ -// public Card chooseCardToDredge(List dredgers) { -// Player ai = getPlayer(); -// //don't dredge when the library is nearly empty -// if (ai.getCardsIn(ZoneType.Library).size() < 8 && !ai.isCardInPlay("Laboratory Maniac")) { -// return null; -// } -// // use dredge if there are more than one of them in your graveyard -// if (dredgers.size() > 1 || MyRandom.getRandom().nextBoolean()) { -// return Aggregates.random(dredgers); -// } -// return null; -// } - - public String getProperty(AiProps propName) { - return AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName); - } - - public int getIntProperty(AiProps propName) { - return Integer.parseInt(AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName)); - } - - public boolean getBooleanProperty(AiProps propName) { - return Boolean.parseBoolean(AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName)); - } - - /** Returns the spell ability which has already been played - use it for reference only */ - public SpellAbility chooseAndPlaySa(boolean mandatory, boolean withoutPayingManaCost, final SpellAbility... list) { - return chooseAndPlaySa(Arrays.asList(list), mandatory, withoutPayingManaCost); - } - /** Returns the spell ability which has already been played - use it for reference only */ - public SpellAbility chooseAndPlaySa(final List choices, boolean mandatory, boolean withoutPayingManaCost) { - for (final SpellAbility sa : choices) { - sa.setActivatingPlayer(player); - //Spells - if (sa instanceof Spell) { - if (!((Spell) sa).canPlayFromEffectAI(player, mandatory, withoutPayingManaCost)) { - continue; - } - } else { - if (sa.canPlayAI(player)) { - continue; - } - } - - if ( withoutPayingManaCost ) - ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, sa, game); - else if (!ComputerUtilCost.canPayCost(sa, player)) - continue; - else - ComputerUtil.playStack(sa, player, game); - return sa; - } - return null; - } - - public void onPriorityRecieved() { - final PhaseType phase = game.getPhaseHandler().getPhase(); - switch(phase) { - case MAIN1: - case MAIN2: - Log.debug("Computer " + phase.nameForUi); - - if (game.getStack().isEmpty()) - playLands(); - // fall through is intended - default: - playSpellAbilities(game); - break; - } - } - - // declares blockers for given defender in a given combat - public void declareBlockersFor(Player defender, Combat combat) { - AiBlockController block = new AiBlockController(defender); - // When player != defender, AI should declare blockers for its benefit. - block.assignBlockers(combat); - } - - - public Combat declareAttackers(Player attacker, Combat combat) { - // 12/2/10(sol) the decision making here has moved to getAttackers() - AiAttackController aiAtk = new AiAttackController(attacker); - aiAtk.declareAttackers(combat); - - for (final Card element : combat.getAttackers()) { - // tapping of attackers happens after Propaganda is paid for - final StringBuilder sb = new StringBuilder(); - sb.append("Computer just assigned ").append(element.getName()).append(" as an attacker."); - Log.debug(sb.toString()); - } - - // ai is about to attack, cancel all phase skipping - for (Player p : game.getPlayers()) { - p.getController().autoPassCancel(); - } - return combat; - } - - private void playLands() { - final Player player = getPlayer(); - List landsWannaPlay = getLandsToPlay(); - - while(landsWannaPlay != null && !landsWannaPlay.isEmpty() && player.canPlayLand(null)) { - Card land = chooseBestLandToPlay(landsWannaPlay); - if (ComputerUtil.damageFromETB(player, land) >= player.getLife() && player.canLoseLife()) { - break; - } - landsWannaPlay.remove(land); - player.playLand(land, false); - game.getPhaseHandler().setPriority(player); - game.getAction().checkStateEffects(); - } - } - - private void playSpellAbilities(final Game game) - { - SpellAbility sa; - do { - if ( game.isGameOver() ) - return; - sa = getSpellAbilityToPlay(); - if ( sa == null ) break; - //System.out.println("Playing sa: " + sa); - if (!ComputerUtil.handlePlayingSpellAbility(player, sa, game)) { - break; - } - } while ( sa != null ); - } - - private final SpellAbility getSpellAbilityToPlay() { - // if top of stack is owned by me - if (!game.getStack().isEmpty() && game.getStack().peekAbility().getActivatingPlayer().equals(player)) { - // probably should let my stuff resolve - return null; - } - final List cards = getAvailableCards(); - - if ( !game.getStack().isEmpty() ) { - SpellAbility counter = chooseCounterSpell(getPlayableCounters(cards)); - if( counter != null ) return counter; - - SpellAbility counterETB = chooseSpellAbilyToPlay(this.getPossibleETBCounters(), false); - if( counterETB != null ) - return counterETB; - } - - return chooseSpellAbilyToPlay(getSpellAbilities(cards), true); - } - - public List chooseCardsToDelve(int colorlessCost, List grave) { - List toExile = new ArrayList(); - int numToExile = Math.min(grave.size(), colorlessCost); - - for (int i = 0; i < numToExile; i++) { - Card chosen = null; - for (final Card c : grave) { // Exile noncreatures first in - // case we can revive. Might - // wanna do some additional - // checking here for Flashback - // and the like. - if (!c.isCreature()) { - chosen = c; - break; - } - } - if (chosen == null) { - chosen = ComputerUtilCard.getWorstCreatureAI(grave); - } - - if (chosen == null) { - // Should never get here but... You know how it is. - chosen = grave.get(0); - } - - toExile.add(chosen); - grave.remove(chosen); - } - return toExile; - } - - public List chooseSaToActivateFromOpeningHand(List usableFromOpeningHand) { - // AI would play everything. But limits to one copy of (Leyline of Singularity) and (Gemstone Caverns) - - List result = new ArrayList(); - for(SpellAbility sa : usableFromOpeningHand) { - // Is there a better way for the AI to decide this? - if (sa.doTrigger(false, player)) { - result.add(sa); - } - } - - boolean hasLeyline1 = false; - SpellAbility saGemstones = null; - - for(int i = 0; i < result.size(); i++) { - SpellAbility sa = result.get(i); - - String srcName = sa.getSourceCard().getName(); - if("Gemstone Caverns".equals(srcName)) { - if(saGemstones == null) - saGemstones = sa; - else - result.remove(i--); - } else if ("Leyline of Singularity".equals(srcName)) { - if(!hasLeyline1) - hasLeyline1 = true; - else - result.remove(i--); - } - } - - // Play them last - if( saGemstones != null ) { - result.remove(saGemstones); - result.add(saGemstones); - } - - return result; - } - - public int chooseNumber(SpellAbility sa, String title, int min, int max) { - //TODO: AILogic - final String logic = sa.getParam("AILogic"); - if ("GainLife".equals(logic)) { - if (player.getLife() < 5 || player.getCardsIn(ZoneType.Hand).size() >= player.getMaxHandSize()) { - return min; - } - } - else if ("Min".equals(logic)) { - return min; - } - else if ("DigACard".equals(logic)) { - int random = MyRandom.getRandom().nextInt(Math.min(4, max)) + 1; - if (player.getLife() < random + 5) { - return min; - } else { - return random; - } - } - return max; - } - - public boolean confirmPayment(CostPart costPart) { - throw new UnsupportedOperationException("AI is not supposed to reach this code at the moment"); - } - - public Map chooseProliferation() { - final Map result = new HashMap<>(); - - final List allies = player.getAllies(); - allies.add(player); - final List enemies = player.getOpponents(); - final Function predProliferate = new Function() { - @Override - public CounterType apply(Card crd) { - for (final Entry c1 : crd.getCounters().entrySet()) { - if (ComputerUtil.isNegativeCounter(c1.getKey(), crd) && enemies.contains(crd.getController())) { - return c1.getKey(); - } - if (!ComputerUtil.isNegativeCounter(c1.getKey(), crd) && allies.contains(crd.getController())) { - return c1.getKey(); - } - } - return null; - } - }; - - for (Card c : game.getCardsIn(ZoneType.Battlefield)) { - CounterType ct = predProliferate.apply(c); - if( ct != null) - result.put(c, ct); - } - - for (Player e : enemies) { - if (e.getPoisonCounters() > 0) { - result.put(e, null); // poison counter type is hardcoded at data consumer's side (this works while players may have no other counters) - } - } - - return result; - } - - public List chooseCardsForEffect(List pool, SpellAbility sa, int min, int max, boolean isOptional) { - if( sa == null || sa.getApi() == null ) { - throw new UnsupportedOperationException(); - } - List result = new ArrayList<>(); - switch( sa.getApi()) { - case TwoPiles: - Card biggest = null; - Card smallest = null; - biggest = pool.get(0); - smallest = pool.get(0); - - for (Card c : pool) { - if (c.getCMC() >= biggest.getCMC()) { - biggest = c; - } else if (c.getCMC() <= smallest.getCMC()) { - smallest = c; - } - } - result.add(biggest); - - if (max >= 3 && !result.contains(smallest)) { - result.add(smallest); - } - - default: - for (int i = 0; i < max; i++) { - Card c = player.getController().chooseSingleEntityForEffect(pool, sa, null, isOptional); - if (c != null) { - result.add(c); - pool.remove(c); - } else { - break; - } - } - - - } - return result; - - } - - public Collection complainCardsCantPlayWell(Deck myDeck) { - List result = new ArrayList(); - for ( Entry ds : myDeck ) { - for (Entry cp : ds.getValue()) { - if ( cp.getKey().getRules().getAiHints().getRemAIDecks() ) - result.add(cp.getKey()); - } - } - return result; - } - - - // this is where the computer cheats - // changes AllZone.getComputerPlayer().getZone(Zone.Library) - - /** - *

- * smoothComputerManaCurve. - *

- * - * @param in - * an array of {@link forge.game.card.Card} objects. - * @return an array of {@link forge.game.card.Card} objects. - */ - public List cheatShuffle(List in) { - if( in.size() < 20 || !canCheatShuffle ) - return in; - - final List library = Lists.newArrayList(in); - CardLists.shuffle(library); - - // remove all land, keep non-basicland in there, shuffled - List land = CardLists.filter(library, CardPredicates.Presets.LANDS); - for (Card c : land) { - if (c.isLand()) { - library.remove(c); - } - } - - try { - // mana weave, total of 7 land - // The Following have all been reduced by 1, to account for the - // computer starting first. - library.add(5, land.get(0)); - library.add(6, land.get(1)); - library.add(8, land.get(2)); - library.add(9, land.get(3)); - library.add(10, land.get(4)); - - library.add(12, land.get(5)); - library.add(15, land.get(6)); - } catch (final IndexOutOfBoundsException e) { - System.err.println("Error: cannot smooth mana curve, not enough land"); - return in; - } - - // add the rest of land to the end of the deck - for (int i = 0; i < land.size(); i++) { - if (!library.contains(land.get(i))) { - library.add(land.get(i)); - } - } - - // check - for (int i = 0; i < library.size(); i++) { - System.out.println(library.get(i)); - } - - return library; - } // smoothComputerManaCurve() - - - public int chooseNumber(SpellAbility sa, String title,List options, Player relatedPlayer) { - switch(sa.getApi()) - { - case SetLife: - if (relatedPlayer.equals(sa.getSourceCard().getController())) { - return Collections.max(options); - } else if (relatedPlayer.isOpponentOf(sa.getSourceCard().getController())) { - return Collections.min(options); - } else { - return options.get(0); - } - default: - return 0; - } - } - - -} - +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.ai; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.esotericsoftware.minlog.Log; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import forge.Singletons; +import forge.card.CardType; +import forge.card.MagicColor; +import forge.deck.CardPool; +import forge.deck.Deck; +import forge.deck.DeckSection; +import forge.game.GameActionUtil; +import forge.game.Game; +import forge.game.GameEntity; +import forge.game.ability.ApiType; +import forge.game.ai.AiProps; +import forge.game.card.Card; +import forge.game.card.CardFactoryUtil; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +import forge.game.card.CardPredicates.Presets; +import forge.game.card.CounterType; +import forge.game.combat.Combat; +import forge.game.cost.CostDiscard; +import forge.game.cost.CostPart; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.player.PlayerActionConfirmMode; +import forge.game.spellability.AbilityManaPart; +import forge.game.spellability.Spell; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellPermanent; +import forge.game.zone.ZoneType; +import forge.item.PaperCard; +import forge.properties.ForgePreferences.FPref; +import forge.util.Aggregates; +import forge.util.Expressions; +import forge.util.MyRandom; + +/** + *

+ * ComputerAI_General class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class AiController { + + private final Player player; + private final Game game; + public boolean canCheatShuffle; + public Game getGame() + { + return game; + } + + public Player getPlayer() + { + return player; + } + + /** + *

+ * Constructor for ComputerAI_General. + *

+ */ + public AiController(final Player computerPlayer, final Game game0) { + player = computerPlayer; + game = game0; + + canCheatShuffle = Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_ENABLE_AI_CHEATS); + } + + /** + *

+ * getAvailableSpellAbilities. + *

+ * + * @return a {@link forge.CardList} object. + */ + private List getAvailableCards() { + List all = new ArrayList(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; + } + + /** + *

+ * getPossibleETBCounters. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + private ArrayList getPossibleETBCounters() { + + final Player opp = player.getOpponent(); + List all = new ArrayList(player.getCardsIn(ZoneType.Hand)); + all.addAll(player.getCardsIn(ZoneType.Exile)); + all.addAll(player.getCardsIn(ZoneType.Graveyard)); + if (!player.getCardsIn(ZoneType.Library).isEmpty()) { + all.add(player.getCardsIn(ZoneType.Library).get(0)); + } + all.addAll(opp.getCardsIn(ZoneType.Exile)); + + final ArrayList spellAbilities = new ArrayList(); + for (final Card c : all) { + for (final SpellAbility sa : c.getNonManaSpellAbilities()) { + if (sa instanceof SpellPermanent) { + sa.setActivatingPlayer(player); + if (SpellPermanent.checkETBEffects(c, sa, ApiType.Counter, player)) { + spellAbilities.add(sa); + } + } + } + } + return spellAbilities; + } + + private List getOriginalAndAltCostAbilities(final List originList) + { + final ArrayList newAbilities = new ArrayList(); + for (SpellAbility sa : originList) { + sa.setActivatingPlayer(player); + //add alternative costs as additional spell abilities + newAbilities.add(sa); + newAbilities.addAll(GameActionUtil.getAlternativeCosts(sa)); + } + + final List result = new ArrayList(); + for (SpellAbility sa : newAbilities) { + sa.setActivatingPlayer(player); + result.addAll(GameActionUtil.getOptionalCosts(sa)); + } + return result; + } + + /** + * Returns the spellAbilities from the card list. + * + * @param l + * a {@link forge.CardList} object. + * @return an array of {@link forge.game.spellability.SpellAbility} objects. + */ + private ArrayList getSpellAbilities(final List l) { + final ArrayList spellAbilities = new ArrayList(); + for (final Card c : l) { + for (final SpellAbility sa : c.getSpellAbilities()) { + spellAbilities.add(sa); + } + } + return spellAbilities; + } + + /** + *

+ * getPlayableCounters. + *

+ * + * @param l + * a {@link forge.CardList} object. + * @return a {@link java.util.ArrayList} object. + */ + private ArrayList getPlayableCounters(final List l) { + final ArrayList spellAbility = new ArrayList(); + for (final Card c : l) { + for (final SpellAbility sa : c.getNonManaSpellAbilities()) { + // Check if this AF is a Counterpsell + if (sa.getApi() == ApiType.Counter) { + spellAbility.add(sa); + } + } + } + + return spellAbility; + } + + // plays a land if one is available + /** + *

+ * chooseLandsToPlay. + *

+ * + * @return a boolean. + */ + public List getLandsToPlay() { + + final List hand = new ArrayList(player.getCardsIn(ZoneType.Hand)); + hand.addAll(player.getCardsIn(ZoneType.Exile)); + List landList = CardLists.filter(hand, Presets.LANDS); + List nonLandList = CardLists.filter(hand, Predicates.not(CardPredicates.Presets.LANDS)); + + //filter out cards that can't be played + landList = CardLists.filter(landList, new Predicate() { + @Override + public boolean apply(final Card c) { + return player.canPlayLand(c); + } + }); + + final List landsNotInHand = new ArrayList(player.getCardsIn(ZoneType.Graveyard)); + if (!player.getCardsIn(ZoneType.Library).isEmpty()) { + landsNotInHand.add(player.getCardsIn(ZoneType.Library).get(0)); + } + for (final Card crd : landsNotInHand) { + if (crd.isLand() && crd.hasKeyword("May be played")) { + landList.add(crd); + } + } + if (landList.isEmpty()) { + return null; + } + if (landList.size() == 1 && nonLandList.size() < 3) { + List cardsInPlay = player.getCardsIn(ZoneType.Battlefield); + List landsInPlay = CardLists.filter(cardsInPlay, Presets.LANDS); + List allCards = new ArrayList(player.getCardsIn(ZoneType.Graveyard)); + allCards.addAll(player.getCardsIn(ZoneType.Command)); + allCards.addAll(cardsInPlay); + int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc); + int max = Math.max(maxCmcInHand, 6); + // consider not playing lands if there are enough already and an ability with a discard cost is present + if (landsInPlay.size() + landList.size() > max) { + for (Card c : allCards) { + for (SpellAbility sa : c.getSpellAbilities()) { + if (sa.getPayCosts() != null) { + for (CostPart part : sa.getPayCosts().getCostParts()) { + if (part instanceof CostDiscard) { + return null; + } + } + } + } + } + } + } + + landList = CardLists.filter(landList, new Predicate() { + @Override + public boolean apply(final Card c) { + if (c.getSVar("NeedsToPlay").length() > 0) { + final String needsToPlay = c.getSVar("NeedsToPlay"); + List list = game.getCardsIn(ZoneType.Battlefield); + + list = CardLists.getValidCards(list, needsToPlay.split(","), c.getController(), c); + if (list.isEmpty()) { + return false; + } + } + if (c.getSVar("NeedsToPlayVar").length() > 0) { + final String needsToPlay = c.getSVar("NeedsToPlayVar"); + int x = 0; + int y = 0; + String sVar = needsToPlay.split(" ")[0]; + String comparator = needsToPlay.split(" ")[1]; + String compareTo = comparator.substring(2); + try { + x = Integer.parseInt(sVar); + } catch (final NumberFormatException e) { + x = CardFactoryUtil.xCount(c, c.getSVar(sVar)); + } + try { + y = Integer.parseInt(compareTo); + } catch (final NumberFormatException e) { + y = CardFactoryUtil.xCount(c, c.getSVar(compareTo)); + } + if (!Expressions.compare(x, comparator, y)) { + return false; + } + } + if (c.isType("Legendary") && !c.getName().equals("Flagstones of Trokair")) { + final List list = player.getCardsIn(ZoneType.Battlefield); + if (Iterables.any(list, CardPredicates.nameEquals(c.getName()))) { + return false; + } + } + + // don't play the land if it has cycling and enough lands are + // available + final ArrayList spellAbilities = c.getSpellAbilities(); + + final List hand = player.getCardsIn(ZoneType.Hand); + List lands = player.getCardsIn(ZoneType.Battlefield); + lands.addAll(hand); + lands = CardLists.filter(lands, CardPredicates.Presets.LANDS); + int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc); + for (final SpellAbility sa : spellAbilities) { + if (sa.isCycling()) { + if (lands.size() >= Math.max(maxCmcInHand, 6)) { + return false; + } + } + } + return true; + } + }); + + return landList; + } + + public Card chooseBestLandToPlay(List landList) + { + if (landList.isEmpty()) + return null; + + //Skip reflected lands. + List unreflectedLands = new ArrayList(landList); + for (Card l : landList) { + if (l.isReflectedLand()) { + unreflectedLands.remove(l); + } + } + + if (!unreflectedLands.isEmpty()) { + landList = unreflectedLands; + } + + // Choose first land to be able to play a one drop + if (player.getLandsInPlay().isEmpty()) { + List oneDrops = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(1)); + for (int i = 0; i < MagicColor.WUBRG.length; i++) { + byte color = MagicColor.WUBRG[i]; + if (!CardLists.filter(oneDrops, CardPredicates.isColor(color)).isEmpty()) { + for (Card land : landList) { + if (land.isType(MagicColor.Constant.BASIC_LANDS.get(i))) + return land; + + for (final SpellAbility m : ComputerUtilMana.getAIPlayableMana(land)) { + AbilityManaPart mp = m.getManaPart(); + if (mp.canProduce(MagicColor.toShortString(color), m)) { + return land; + } + } + } + } + } + } + + //play basic lands that are needed the most + if (Iterables.any(landList, CardPredicates.Presets.BASIC_LANDS)) { + final List combined = player.getCardsIn(ZoneType.Battlefield); + + final ArrayList basics = new ArrayList(); + + // what types can I go get? + for (final String name : CardType.Constant.BASIC_TYPES) { + if (Iterables.any(landList, CardPredicates.isType(name))) { + basics.add(name); + } + } + + // Which basic land is least available from hand and play, that I still + // have in my deck + int minSize = Integer.MAX_VALUE; + String minType = null; + + for (int i = 0; i < basics.size(); i++) { + final String b = basics.get(i); + final int num = CardLists.getType(combined, b).size(); + if (num < minSize) { + minType = b; + minSize = num; + } + } + + if (minType != null) { + landList = CardLists.getType(landList, minType); + } + } + return landList.get(0); + } + + // if return true, go to next phase + /** + *

+ * playCounterSpell. + *

+ * + * @param possibleCounters + * a {@link java.util.ArrayList} object. + * @return a boolean. + */ + private SpellAbility chooseCounterSpell(final List possibleCounters) { + if ( possibleCounters == null || possibleCounters.isEmpty()) + return null;; + + SpellAbility bestSA = null; + int bestRestriction = Integer.MIN_VALUE; + + + for (final SpellAbility sa : getOriginalAndAltCostAbilities(possibleCounters)) { + SpellAbility currentSA = sa; + sa.setActivatingPlayer(player); + // check everything necessary + if (canPlayAndPayFor(currentSA)) { + if (bestSA == null) { + bestSA = currentSA; + bestRestriction = ComputerUtil.counterSpellRestriction(player, currentSA); + } else { + // Compare bestSA with this SA + final int restrictionLevel = ComputerUtil.counterSpellRestriction(player, currentSA); + + if (restrictionLevel > bestRestriction) { + bestRestriction = restrictionLevel; + bestSA = currentSA; + } + } + } + } + + // TODO - "Look" at Targeted SA and "calculate" the threshold + // if (bestRestriction < targetedThreshold) return false; + return bestSA; + } // playCounterSpell() + + // if return true, go to next phase + /** + *

+ * playSpellAbilities. + *

+ * + * @param all + * an array of {@link forge.game.spellability.SpellAbility} + * objects. + * @return a boolean. + */ + private SpellAbility chooseSpellAbilyToPlay(final List all, boolean skipCounter) { + if ( all == null || all.isEmpty() ) + return null; + + Collections.sort(all, saComparator); // put best spells first + + for (final SpellAbility sa : getOriginalAndAltCostAbilities(all)) { + // Don't add Counterspells to the "normal" playcard lookups + if (sa.getApi() == ApiType.Counter && skipCounter) { + continue; + } + sa.setActivatingPlayer(player); + + if (!canPlayAndPayFor(sa)) + continue; + + return sa; + } + + return null; + } // playCards() + + + // This is for playing spells regularly (no Cascade/Ripple etc.) + private boolean canPlayAndPayFor(final SpellAbility sa) { + if (!sa.canPlay()) { + return false; + } + //System.out.printf("Ai thinks of %s @ %s >>> ", sa, sa.getActivatingPlayer().getGame().getPhaseHandler().debugPrintState()); + if (!sa.canPlayAI(player)) { + return false; + } + //System.out.printf("wouldPlay: %s, canPay: %s%n", aiWouldPlay, canPay); + return ComputerUtilCost.canPayCost(sa, player); + } + + // not sure "playing biggest spell" matters? + private final static Comparator saComparator = new Comparator() { + @Override + public int compare(final SpellAbility a, final SpellAbility b) { + // sort from highest cost to lowest + // we want the highest costs first + int a1 = a.getPayCosts() == null ? 0 : a.getPayCosts().getTotalMana().getCMC(); + int b1 = b.getPayCosts() == null ? 0 : b.getPayCosts().getTotalMana().getCMC(); + + // deprioritize planar die roll marked with AIRollPlanarDieParams:LowPriority$ True + if (ApiType.RollPlanarDice == a.getApi() && a.getSourceCard().hasSVar("AIRollPlanarDieParams") && a.getSourceCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) { + return 1; + } else if (ApiType.RollPlanarDice == b.getApi() && b.getSourceCard().hasSVar("AIRollPlanarDieParams") && b.getSourceCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) { + return -1; + } + + // cast 0 mana cost spells first (might be a Mox) + if (a1 == 0) { + return -1; + } else if (b1 == 0) { + return 1; + } + + a1 += getSpellAbilityPriority(a); + b1 += getSpellAbilityPriority(b); + + return b1 - a1; + } + + private int getSpellAbilityPriority(SpellAbility sa) { + int p = 0; + Card source = sa.getSourceCard(); + // puts creatures in front of spells + if (source.isCreature()) { + p += 1; + } + // don't play equipments before having any creatures + if (source.isEquipment() && sa.getSourceCard().getController().getCreaturesInPlay().isEmpty()) { + p -= 9; + } + // artifacts and enchantments with effects that do not stack + if ("True".equals(source.getSVar("NonStackingEffect")) && source.getController().isCardInPlay(source.getName())) { + p -= 9; + } + // sort planeswalker abilities for ultimate + if (sa.getRestrictions().getPlaneswalker()) { + if (sa.hasParam("Ultimate")) { + p += 9; + } + } + + if (ApiType.DestroyAll == sa.getApi()) { + p += 4; + } + + return p; + } + }; // Comparator + + /** + *

+ * AI_discardNumType. + *

+ * + * @param numDiscard + * a int. + * @param uTypes + * an array of {@link java.lang.String} objects. May be null for + * no restrictions. + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a List of discarded cards. + */ + public List getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa) { + List hand = new ArrayList(player.getCardsIn(ZoneType.Hand)); + + + if ((uTypes != null) && (sa != null)) { + hand = CardLists.getValidCards(hand, uTypes, sa.getActivatingPlayer(), sa.getSourceCard()); + } + return getCardsToDiscard(numDiscard, numDiscard, hand, sa); + } + + public List getCardsToDiscard(final int min, final int max, final List validCards, final SpellAbility sa) { + + if (validCards.size() < min) { + return null; + } + + Card sourceCard = null; + final List discardList = new ArrayList(); + int count = 0; + if (sa != null) { + sourceCard = sa.getSourceCard(); + } + + // look for good discards + while (count < min) { + Card prefCard = null; + if (sa != null && sa.getActivatingPlayer() != null && sa.getActivatingPlayer().isOpponentOf(player)) { + for (Card c : validCards) { + if (c.hasKeyword("If a spell or ability an opponent controls causes you to discard CARDNAME," + + " put it onto the battlefield instead of putting it into your graveyard.") + || !c.getSVar("DiscardMeByOpp").isEmpty()) { + prefCard = c; + break; + } + } + } + if (prefCard == null) { + prefCard = ComputerUtil.getCardPreference(player, sourceCard, "DiscardCost", validCards); + } + if (prefCard != null) { + discardList.add(prefCard); + validCards.remove(prefCard); + count++; + } else { + break; + } + } + + final int discardsLeft = min - count; + + // choose rest + for (int i = 0; i < discardsLeft; i++) { + if (validCards.isEmpty()) { + continue; + } + final int numLandsInPlay = Iterables.size(Iterables.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS)); + final List landsInHand = CardLists.filter(validCards, CardPredicates.Presets.LANDS); + final int numLandsInHand = landsInHand.size(); + + // Discard a land + boolean canDiscardLands = numLandsInHand > 3 || (numLandsInHand > 2 && numLandsInPlay > 0) + || (numLandsInHand > 1 && numLandsInPlay > 2) || (numLandsInHand > 0 && numLandsInPlay > 5); + + if (canDiscardLands) { + discardList.add(landsInHand.get(0)); + validCards.remove(landsInHand.get(0)); + } else { // Discard other stuff + CardLists.sortByCmcDesc(validCards); + int numLandsAvailable = numLandsInPlay; + if (numLandsInHand > 0) { + numLandsAvailable++; + } + //Discard unplayable card + if (validCards.get(0).getCMC() > numLandsAvailable) { + discardList.add(validCards.get(0)); + validCards.remove(validCards.get(0)); + } else { //Discard worst card + Card worst = ComputerUtilCard.getWorstAI(validCards); + discardList.add(worst); + validCards.remove(worst); + } + } + } + + return discardList; + } + + @SuppressWarnings("incomplete-switch") + public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) { + ApiType api = sa.getApi(); + + // Abilities without api may also use this routine, However they should provide a unique mode value + if ( null == api ) { + if( mode != null ) switch (mode) { + // case BraidOfFire: return true; + // case Ripple: return true; + } + + String exMsg = String.format("AI confirmAction does not know what to decide about %s mode (api is null).", mode); + throw new IllegalArgumentException(exMsg); + + } else + return api.getAi().confirmAction(player, sa, mode, message); + } + + public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) { + if (logic.equalsIgnoreCase("ProtectFriendly")) { + final Player controller = hostCard.getController(); + if (affected instanceof Player) { + return !((Player) affected).isOpponentOf(controller); + } else if (affected instanceof Card) { + return !((Card) affected).getController().isOpponentOf(controller); + } + } + return true; + } + +// /** +// * AI decides if he wants to use dredge ability and which one if many available +// * @param dredgers - contains at least single element +// * @return +// */ +// public Card chooseCardToDredge(List dredgers) { +// Player ai = getPlayer(); +// //don't dredge when the library is nearly empty +// if (ai.getCardsIn(ZoneType.Library).size() < 8 && !ai.isCardInPlay("Laboratory Maniac")) { +// return null; +// } +// // use dredge if there are more than one of them in your graveyard +// if (dredgers.size() > 1 || MyRandom.getRandom().nextBoolean()) { +// return Aggregates.random(dredgers); +// } +// return null; +// } + + public String getProperty(AiProps propName) { + return AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName); + } + + public int getIntProperty(AiProps propName) { + return Integer.parseInt(AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName)); + } + + public boolean getBooleanProperty(AiProps propName) { + return Boolean.parseBoolean(AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName)); + } + + /** Returns the spell ability which has already been played - use it for reference only */ + public SpellAbility chooseAndPlaySa(boolean mandatory, boolean withoutPayingManaCost, final SpellAbility... list) { + return chooseAndPlaySa(Arrays.asList(list), mandatory, withoutPayingManaCost); + } + /** Returns the spell ability which has already been played - use it for reference only */ + public SpellAbility chooseAndPlaySa(final List choices, boolean mandatory, boolean withoutPayingManaCost) { + for (final SpellAbility sa : choices) { + sa.setActivatingPlayer(player); + //Spells + if (sa instanceof Spell) { + if (!((Spell) sa).canPlayFromEffectAI(player, mandatory, withoutPayingManaCost)) { + continue; + } + } else { + if (sa.canPlayAI(player)) { + continue; + } + } + + if ( withoutPayingManaCost ) + ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, sa, game); + else if (!ComputerUtilCost.canPayCost(sa, player)) + continue; + else + ComputerUtil.playStack(sa, player, game); + return sa; + } + return null; + } + + public void onPriorityRecieved() { + final PhaseType phase = game.getPhaseHandler().getPhase(); + switch(phase) { + case MAIN1: + case MAIN2: + Log.debug("Computer " + phase.nameForUi); + + if (game.getStack().isEmpty()) + playLands(); + // fall through is intended + default: + playSpellAbilities(game); + break; + } + } + + // declares blockers for given defender in a given combat + public void declareBlockersFor(Player defender, Combat combat) { + AiBlockController block = new AiBlockController(defender); + // When player != defender, AI should declare blockers for its benefit. + block.assignBlockers(combat); + } + + + public Combat declareAttackers(Player attacker, Combat combat) { + // 12/2/10(sol) the decision making here has moved to getAttackers() + AiAttackController aiAtk = new AiAttackController(attacker); + aiAtk.declareAttackers(combat); + + for (final Card element : combat.getAttackers()) { + // tapping of attackers happens after Propaganda is paid for + final StringBuilder sb = new StringBuilder(); + sb.append("Computer just assigned ").append(element.getName()).append(" as an attacker."); + Log.debug(sb.toString()); + } + + // ai is about to attack, cancel all phase skipping + for (Player p : game.getPlayers()) { + p.getController().autoPassCancel(); + } + return combat; + } + + private void playLands() { + final Player player = getPlayer(); + List landsWannaPlay = getLandsToPlay(); + + while(landsWannaPlay != null && !landsWannaPlay.isEmpty() && player.canPlayLand(null)) { + Card land = chooseBestLandToPlay(landsWannaPlay); + if (ComputerUtil.damageFromETB(player, land) >= player.getLife() && player.canLoseLife()) { + break; + } + landsWannaPlay.remove(land); + player.playLand(land, false); + game.getPhaseHandler().setPriority(player); + game.getAction().checkStateEffects(); + } + } + + private void playSpellAbilities(final Game game) + { + SpellAbility sa; + do { + if ( game.isGameOver() ) + return; + sa = getSpellAbilityToPlay(); + if ( sa == null ) break; + //System.out.println("Playing sa: " + sa); + if (!ComputerUtil.handlePlayingSpellAbility(player, sa, game)) { + break; + } + } while ( sa != null ); + } + + private final SpellAbility getSpellAbilityToPlay() { + // if top of stack is owned by me + if (!game.getStack().isEmpty() && game.getStack().peekAbility().getActivatingPlayer().equals(player)) { + // probably should let my stuff resolve + return null; + } + final List cards = getAvailableCards(); + + if ( !game.getStack().isEmpty() ) { + SpellAbility counter = chooseCounterSpell(getPlayableCounters(cards)); + if( counter != null ) return counter; + + SpellAbility counterETB = chooseSpellAbilyToPlay(this.getPossibleETBCounters(), false); + if( counterETB != null ) + return counterETB; + } + + return chooseSpellAbilyToPlay(getSpellAbilities(cards), true); + } + + public List chooseCardsToDelve(int colorlessCost, List grave) { + List toExile = new ArrayList(); + int numToExile = Math.min(grave.size(), colorlessCost); + + for (int i = 0; i < numToExile; i++) { + Card chosen = null; + for (final Card c : grave) { // Exile noncreatures first in + // case we can revive. Might + // wanna do some additional + // checking here for Flashback + // and the like. + if (!c.isCreature()) { + chosen = c; + break; + } + } + if (chosen == null) { + chosen = ComputerUtilCard.getWorstCreatureAI(grave); + } + + if (chosen == null) { + // Should never get here but... You know how it is. + chosen = grave.get(0); + } + + toExile.add(chosen); + grave.remove(chosen); + } + return toExile; + } + + public List chooseSaToActivateFromOpeningHand(List usableFromOpeningHand) { + // AI would play everything. But limits to one copy of (Leyline of Singularity) and (Gemstone Caverns) + + List result = new ArrayList(); + for(SpellAbility sa : usableFromOpeningHand) { + // Is there a better way for the AI to decide this? + if (sa.doTrigger(false, player)) { + result.add(sa); + } + } + + boolean hasLeyline1 = false; + SpellAbility saGemstones = null; + + for(int i = 0; i < result.size(); i++) { + SpellAbility sa = result.get(i); + + String srcName = sa.getSourceCard().getName(); + if("Gemstone Caverns".equals(srcName)) { + if(saGemstones == null) + saGemstones = sa; + else + result.remove(i--); + } else if ("Leyline of Singularity".equals(srcName)) { + if(!hasLeyline1) + hasLeyline1 = true; + else + result.remove(i--); + } + } + + // Play them last + if( saGemstones != null ) { + result.remove(saGemstones); + result.add(saGemstones); + } + + return result; + } + + public int chooseNumber(SpellAbility sa, String title, int min, int max) { + //TODO: AILogic + final String logic = sa.getParam("AILogic"); + if ("GainLife".equals(logic)) { + if (player.getLife() < 5 || player.getCardsIn(ZoneType.Hand).size() >= player.getMaxHandSize()) { + return min; + } + } + else if ("Min".equals(logic)) { + return min; + } + else if ("DigACard".equals(logic)) { + int random = MyRandom.getRandom().nextInt(Math.min(4, max)) + 1; + if (player.getLife() < random + 5) { + return min; + } else { + return random; + } + } + return max; + } + + public boolean confirmPayment(CostPart costPart) { + throw new UnsupportedOperationException("AI is not supposed to reach this code at the moment"); + } + + public Map chooseProliferation() { + final Map result = new HashMap<>(); + + final List allies = player.getAllies(); + allies.add(player); + final List enemies = player.getOpponents(); + final Function predProliferate = new Function() { + @Override + public CounterType apply(Card crd) { + for (final Entry c1 : crd.getCounters().entrySet()) { + if (ComputerUtil.isNegativeCounter(c1.getKey(), crd) && enemies.contains(crd.getController())) { + return c1.getKey(); + } + if (!ComputerUtil.isNegativeCounter(c1.getKey(), crd) && allies.contains(crd.getController())) { + return c1.getKey(); + } + } + return null; + } + }; + + for (Card c : game.getCardsIn(ZoneType.Battlefield)) { + CounterType ct = predProliferate.apply(c); + if( ct != null) + result.put(c, ct); + } + + for (Player e : enemies) { + if (e.getPoisonCounters() > 0) { + result.put(e, null); // poison counter type is hardcoded at data consumer's side (this works while players may have no other counters) + } + } + + return result; + } + + public List chooseCardsForEffect(List pool, SpellAbility sa, int min, int max, boolean isOptional) { + if( sa == null || sa.getApi() == null ) { + throw new UnsupportedOperationException(); + } + List result = new ArrayList<>(); + switch( sa.getApi()) { + case TwoPiles: + Card biggest = null; + Card smallest = null; + biggest = pool.get(0); + smallest = pool.get(0); + + for (Card c : pool) { + if (c.getCMC() >= biggest.getCMC()) { + biggest = c; + } else if (c.getCMC() <= smallest.getCMC()) { + smallest = c; + } + } + result.add(biggest); + + if (max >= 3 && !result.contains(smallest)) { + result.add(smallest); + } + + default: + for (int i = 0; i < max; i++) { + Card c = player.getController().chooseSingleEntityForEffect(pool, sa, null, isOptional); + if (c != null) { + result.add(c); + pool.remove(c); + } else { + break; + } + } + + + } + return result; + + } + + public Collection complainCardsCantPlayWell(Deck myDeck) { + List result = new ArrayList(); + for ( Entry ds : myDeck ) { + for (Entry cp : ds.getValue()) { + if ( cp.getKey().getRules().getAiHints().getRemAIDecks() ) + result.add(cp.getKey()); + } + } + return result; + } + + + // this is where the computer cheats + // changes AllZone.getComputerPlayer().getZone(Zone.Library) + + /** + *

+ * smoothComputerManaCurve. + *

+ * + * @param in + * an array of {@link forge.game.card.Card} objects. + * @return an array of {@link forge.game.card.Card} objects. + */ + public List cheatShuffle(List in) { + if( in.size() < 20 || !canCheatShuffle ) + return in; + + final List library = Lists.newArrayList(in); + CardLists.shuffle(library); + + // remove all land, keep non-basicland in there, shuffled + List land = CardLists.filter(library, CardPredicates.Presets.LANDS); + for (Card c : land) { + if (c.isLand()) { + library.remove(c); + } + } + + try { + // mana weave, total of 7 land + // The Following have all been reduced by 1, to account for the + // computer starting first. + library.add(5, land.get(0)); + library.add(6, land.get(1)); + library.add(8, land.get(2)); + library.add(9, land.get(3)); + library.add(10, land.get(4)); + + library.add(12, land.get(5)); + library.add(15, land.get(6)); + } catch (final IndexOutOfBoundsException e) { + System.err.println("Error: cannot smooth mana curve, not enough land"); + return in; + } + + // add the rest of land to the end of the deck + for (int i = 0; i < land.size(); i++) { + if (!library.contains(land.get(i))) { + library.add(land.get(i)); + } + } + + // check + for (int i = 0; i < library.size(); i++) { + System.out.println(library.get(i)); + } + + return library; + } // smoothComputerManaCurve() + + + public int chooseNumber(SpellAbility sa, String title,List options, Player relatedPlayer) { + switch(sa.getApi()) + { + case SetLife: + if (relatedPlayer.equals(sa.getSourceCard().getController())) { + return Collections.max(options); + } else if (relatedPlayer.isOpponentOf(sa.getSourceCard().getController())) { + return Collections.min(options); + } else { + return options.get(0); + } + default: + return 0; + } + } + + +} + diff --git a/forge-game/src/main/java/forge/ai/AiCostDecision.java b/forge-gui/src/main/java/forge/ai/AiCostDecision.java similarity index 100% rename from forge-game/src/main/java/forge/ai/AiCostDecision.java rename to forge-gui/src/main/java/forge/ai/AiCostDecision.java diff --git a/forge-game/src/main/java/forge/ai/AiProfileUtil.java b/forge-gui/src/main/java/forge/ai/AiProfileUtil.java similarity index 100% rename from forge-game/src/main/java/forge/ai/AiProfileUtil.java rename to forge-gui/src/main/java/forge/ai/AiProfileUtil.java diff --git a/forge-game/src/main/java/forge/ai/ComputerUtil.java b/forge-gui/src/main/java/forge/ai/ComputerUtil.java similarity index 96% rename from forge-game/src/main/java/forge/ai/ComputerUtil.java rename to forge-gui/src/main/java/forge/ai/ComputerUtil.java index 1a860336d48..00585a55f4d 100644 --- a/forge-game/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-gui/src/main/java/forge/ai/ComputerUtil.java @@ -1,1858 +1,1860 @@ -/* - * 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 . - */ -package forge.ai; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; - -import org.apache.commons.lang3.StringUtils; - -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; - -import forge.card.CardType; -import forge.card.MagicColor; -import forge.card.CardType.Constant; -import forge.game.Game; -import forge.game.GameObject; -import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; -import forge.game.ability.effects.CharmEffect; -import forge.game.ai.AiProps; -import forge.game.card.Card; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; -import forge.game.card.CardUtil; -import forge.game.card.CounterType; -import forge.game.card.CardPredicates.Presets; -import forge.game.combat.Combat; -import forge.game.combat.CombatUtil; -import forge.game.cost.Cost; -import forge.game.cost.CostDiscard; -import forge.game.cost.CostPart; -import forge.game.cost.CostPayment; -import forge.game.cost.CostPutCounter; -import forge.game.cost.CostSacrifice; -import forge.game.phase.PhaseHandler; -import forge.game.phase.PhaseType; -import forge.game.player.Player; -import forge.game.player.PlayerControllerAi; -import forge.game.spellability.AbilityManaPart; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.SpellAbilityStackInstance; -import forge.game.spellability.TargetRestrictions; -import forge.game.staticability.StaticAbility; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerType; -import forge.game.zone.Zone; -import forge.game.zone.ZoneType; -import forge.util.Aggregates; -import forge.util.MyRandom; - - -/** - *

- * ComputerUtil class. - *

- * - * @author Forge - * @version $Id: ComputerUtil.java 24317 2014-01-17 08:32:39Z Max mtg $ - */ -public class ComputerUtil { - - /** - *

- * handlePlayingSpellAbility. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean. - */ - public static boolean handlePlayingSpellAbility(final Player ai, final SpellAbility sa, final Game game) { - - game.getStack().freezeStack(); - final Card source = sa.getSourceCard(); - - if (sa.isSpell() && !source.isCopiedSpell()) { - sa.setSourceCard(game.getAction().moveToStack(source)); - } - - if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { - CharmEffect.makeChoices(sa); - } - - if (sa.hasParam("Bestow")) { - sa.getSourceCard().animateBestow(); - } - - final Cost cost = sa.getPayCosts(); - - if (cost == null) { - if (ComputerUtilMana.payManaCost(ai, sa)) { - game.getStack().addAndUnfreeze(sa); - return true; - } - } else { - final CostPayment pay = new CostPayment(cost, sa); - if (pay.payComputerCosts(new AiCostDecision(ai, sa))) { - game.getStack().addAndUnfreeze(sa); - if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty()) { - game.getAction().reveal(sa.getSplicedCards(), ai, true, "Computer reveals spliced cards from "); - } - return true; - // TODO: solve problems with TapsForMana triggers by adding - // sources tapped here if possible (ArsenalNut) - } - } - //Should not arrive here - System.out.println("AI failed to play " + sa.getSourceCard()); - return false; - } - - - private static boolean hasDiscardHandCost(final Cost cost) { - if (cost == null) { - return false; - } - for (final CostPart part : cost.getCostParts()) { - if (part instanceof CostDiscard) { - final CostDiscard disc = (CostDiscard) part; - if (disc.getType().equals("Hand")) { - return true; - } - } - } - return false; - } - /** - *

- * counterSpellRestriction. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a int. - */ - public static int counterSpellRestriction(final Player ai, final SpellAbility sa) { - // Move this to AF? - // Restriction Level is Based off a handful of factors - - int restrict = 0; - - final Card source = sa.getSourceCard(); - final TargetRestrictions tgt = sa.getTargetRestrictions(); - - - // Play higher costing spells first? - final Cost cost = sa.getPayCosts(); - // Convert cost to CMC - // String totalMana = source.getSVar("PayX"); // + cost.getCMC() - - // Consider the costs here for relative "scoring" - if (hasDiscardHandCost(cost)) { - // Null Brooch aid - restrict -= (ai.getCardsIn(ZoneType.Hand).size() * 20); - } - - // Abilities before Spells (card advantage) - if (sa.isAbility()) { - restrict += 40; - } - - // TargetValidTargeting gets biggest bonus - if (tgt.getSAValidTargeting() != null) { - restrict += 35; - } - - // Unless Cost gets significant bonus + 10-Payment Amount - final String unless = sa.getParam("UnlessCost"); - if (unless != null && !unless.endsWith(">")) { - final int amount = AbilityUtils.calculateAmount(source, unless, sa); - - final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size(); - - // If the Unless isn't enough, this should be less likely to be used - if (amount > usableManaSources) { - restrict += 20 - (2 * amount); - } else { - restrict -= (10 - (2 * amount)); - } - } - - // Then base on Targeting Restriction - final String[] validTgts = tgt.getValidTgts(); - if ((validTgts.length != 1) || !validTgts[0].equals("Card")) { - restrict += 10; - } - - // And lastly give some bonus points to least restrictive TargetType - // (Spell,Ability,Triggered) - final String tgtType = sa.getParam("TargetType"); - if (tgtType != null) - restrict -= (5 * tgtType.split(",").length); - - return restrict; - } - - // this is used for AI's counterspells - /** - *

- * playStack. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - */ - public static final void playStack(final SpellAbility sa, final Player ai, final Game game) { - sa.setActivatingPlayer(ai); - if (!ComputerUtilCost.canPayCost(sa, ai)) - return; - - final Card source = sa.getSourceCard(); - if (sa.isSpell() && !source.isCopiedSpell()) { - sa.setSourceCard(game.getAction().moveToStack(source)); - } - final Cost cost = sa.getPayCosts(); - if (cost == null) { - ComputerUtilMana.payManaCost(ai, sa); - game.getStack().add(sa); - } else { - final CostPayment pay = new CostPayment(cost, sa); - if (pay.payComputerCosts(new AiCostDecision(ai, sa))) { - game.getStack().add(sa); - } - } - } - - /** - *

- * playStackFree. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - */ - public static final void playSpellAbilityForFree(final Player ai, final SpellAbility sa) { - sa.setActivatingPlayer(ai); - - final Card source = sa.getSourceCard(); - if (sa.isSpell() && !source.isCopiedSpell()) { - sa.setSourceCard(ai.getGame().getAction().moveToStack(source)); - } - - ai.getGame().getStack().add(sa); - } - - /** - *

- * playSpellAbilityWithoutPayingManaCost. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - */ - public static final void playSpellAbilityWithoutPayingManaCost(final Player ai, final SpellAbility sa, final Game game) { - final SpellAbility newSA = sa.copyWithNoManaCost(); - newSA.setActivatingPlayer(ai); - - if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) { - return; - } - - final Card source = newSA.getSourceCard(); - if (newSA.isSpell() && !source.isCopiedSpell()) { - newSA.setSourceCard(game.getAction().moveToStack(source)); - } - - final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA); - pay.payComputerCosts(new AiCostDecision(ai, sa)); - - game.getStack().add(newSA); - } - - /** - *

- * playNoStack. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - */ - public static final void playNoStack(final Player ai, final SpellAbility sa, final Game game) { - sa.setActivatingPlayer(ai); - // TODO: We should really restrict what doesn't use the Stack - if (ComputerUtilCost.canPayCost(sa, ai)) { - final Card source = sa.getSourceCard(); - if (sa.isSpell() && !source.isCopiedSpell()) { - sa.setSourceCard(game.getAction().moveToStack(source)); - } - - final Cost cost = sa.getPayCosts(); - if (cost == null) { - ComputerUtilMana.payManaCost(ai, sa); - } else { - final CostPayment pay = new CostPayment(cost, sa); - pay.payComputerCosts(new AiCostDecision(ai, sa)); - } - - AbilityUtils.resolve(sa); - - // destroys creatures if they have lethal damage, etc.. - //game.getAction().checkStateEffects(); - } - } // play() - - /** - *

- * getCardPreference. - *

- * - * @param activate - * a {@link forge.game.card.Card} object. - * @param pref - * a {@link java.lang.String} object. - * @param typeList - * a {@link forge.CardList} object. - * @return a {@link forge.game.card.Card} object. - */ - public static Card getCardPreference(final Player ai, final Card activate, final String pref, final List typeList) { - - if (activate != null) { - final String[] prefValid = activate.getSVar("AIPreference").split("\\$"); - if (prefValid[0].equals(pref)) { - final List prefList = CardLists.getValidCards(typeList, prefValid[1].split(","), activate.getController(), activate); - if (prefList.size() != 0) { - CardLists.shuffle(prefList); - return prefList.get(0); - } - } - } - if (pref.contains("SacCost")) { - // search for permanents with SacMe. priority 1 is the lowest, priority 5 the highest - for (int ip = 0; ip < 6; ip++) { - final int priority = 6 - ip; - final List sacMeList = CardLists.filter(typeList, new Predicate() { - @Override - public boolean apply(final Card c) { - return (c.hasSVar("SacMe") && (Integer.parseInt(c.getSVar("SacMe")) == priority)); - } - }); - if (!sacMeList.isEmpty()) { - CardLists.shuffle(sacMeList); - return sacMeList.get(0); - } - } - - // Sac lands - final List landsInPlay = CardLists.getType(typeList, "Land"); - if (!landsInPlay.isEmpty()) { - final List landsInHand = CardLists.getType(ai.getCardsIn(ZoneType.Hand), "Land"); - final List nonLandsInHand = CardLists.getNotType(ai.getCardsIn(ZoneType.Hand), "Land"); - nonLandsInHand.addAll(ai.getCardsIn(ZoneType.Library)); - final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, CardPredicates.Accessors.fnGetCmc)); - if (landsInPlay.size() + landsInHand.size() >= highestCMC) { - // Don't need more land. - return ComputerUtilCard.getWorstLand(landsInPlay); - } - } - } - - else if (pref.contains("DiscardCost")) { // search for permanents with - // DiscardMe - for (int ip = 0; ip < 6; ip++) { // priority 0 is the lowest, - // priority 5 the highest - final int priority = 6 - ip; - for (Card c : typeList) { - if (priority == 3 && c.isLand() - && !ai.getCardsIn(ZoneType.Battlefield, "Crucible of Worlds").isEmpty()) { - return c; - } - if (c.hasSVar("DiscardMe") && Integer.parseInt(c.getSVar("DiscardMe")) == priority) { - return c; - } - } - } - - // Discard lands - final List landsInHand = CardLists.getType(typeList, "Land"); - if (!landsInHand.isEmpty()) { - final List landsInPlay = CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Land"); - final List nonLandsInHand = CardLists.getNotType(ai.getCardsIn(ZoneType.Hand), "Land"); - final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, CardPredicates.Accessors.fnGetCmc)); - if (landsInPlay.size() >= highestCMC - || (landsInPlay.size() + landsInHand.size() > 6 && landsInHand.size() > 1)) { - // Don't need more land. - return ComputerUtilCard.getWorstLand(landsInHand); - } - } - } - - return null; - } - - /** - *

- * chooseSacrificeType. - *

- * - * @param type - * a {@link java.lang.String} object. - * @param source - * a {@link forge.game.card.Card} object. - * @param target - * a {@link forge.game.card.Card} object. - * @param amount - * a int. - * @return a {@link forge.CardList} object. - */ - public static List chooseSacrificeType(final Player ai, final String type, final Card source, final Card target, final int amount) { - List typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source); - if (ai.hasKeyword("You can't sacrifice creatures to cast spells or activate abilities.")) { - typeList = CardLists.getNotType(typeList, "Creature"); - } - - if ((target != null) && target.getController() == ai && typeList.contains(target)) { - typeList.remove(target); // don't sacrifice the card we're pumping - } - - if (typeList.size() < amount) { - return null; - } - - final List sacList = new ArrayList(); - int count = 0; - - while (count < amount) { - Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList); - if (prefCard == null) { - prefCard = ComputerUtilCard.getWorstAI(typeList); - } - if (prefCard == null) { - return null; - } - sacList.add(prefCard); - typeList.remove(prefCard); - count++; - } - return sacList; - } - - /** - *

- * chooseExileFrom. - *

- * - * @param zone - * a {@link java.lang.String} object. - * @param type - * a {@link java.lang.String} object. - * @param activate - * a {@link forge.game.card.Card} object. - * @param target - * a {@link forge.game.card.Card} object. - * @param amount - * a int. - * @return a {@link forge.CardList} object. - */ - public static List chooseExileFrom(final Player ai, final ZoneType zone, final String type, final Card activate, - final Card target, final int amount) { - final Game game = ai.getGame(); - List typeList = new ArrayList(); - if (zone.equals(ZoneType.Stack)) { - for (SpellAbilityStackInstance si : game.getStack()) { - typeList.add(si.getSourceCard()); - } - } else { - typeList = ai.getCardsIn(zone); - } - typeList = CardLists.getValidCards(typeList, type.split(";"), activate.getController(), activate); - - if ((target != null) && target.getController() == ai && typeList.contains(target)) { - typeList.remove(target); // don't exile the card we're pumping - } - - if (typeList.size() < amount) { - return null; - } - - CardLists.sortByPowerAsc(typeList); - final List exileList = new ArrayList(); - - for (int i = 0; i < amount; i++) { - exileList.add(typeList.get(i)); - } - return exileList; - } - - /** - *

- * choosePutToLibraryFrom. - *

- * - * @param zone - * a {@link java.lang.String} object. - * @param type - * a {@link java.lang.String} object. - * @param activate - * a {@link forge.game.card.Card} object. - * @param target - * a {@link forge.game.card.Card} object. - * @param amount - * a int. - * @return a {@link forge.CardList} object. - */ - public static List choosePutToLibraryFrom(final Player ai, final ZoneType zone, final String type, final Card activate, - final Card target, final int amount) { - List typeList = ai.getCardsIn(zone); - - typeList = CardLists.getValidCards(typeList, type.split(";"), activate.getController(), activate); - - if ((target != null) && target.getController() == ai && typeList.contains(target)) { - typeList.remove(target); // don't move the card we're pumping - } - - if (typeList.size() < amount) { - return null; - } - - CardLists.sortByPowerAsc(typeList); - final List list = new ArrayList(); - - if (zone != ZoneType.Hand) { - Collections.reverse(typeList); - } - - for (int i = 0; i < amount; i++) { - list.add(typeList.get(i)); - } - - return list; - } - - /** - *

- * chooseTapType. - *

- * - * @param type - * a {@link java.lang.String} object. - * @param activate - * a {@link forge.game.card.Card} object. - * @param tap - * a boolean. - * @param amount - * a int. - * @return a {@link forge.CardList} object. - */ - public static List chooseTapType(final Player ai, final String type, final Card activate, final boolean tap, final int amount) { - List typeList = - CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(","), activate.getController(), activate); - - // is this needed? - typeList = CardLists.filter(typeList, Presets.UNTAPPED); - - if (tap) { - typeList.remove(activate); - } - - if (typeList.size() < amount) { - return null; - } - - CardLists.sortByPowerAsc(typeList); - - final List tapList = new ArrayList(); - - for (int i = 0; i < amount; i++) { - tapList.add(typeList.get(i)); - } - return tapList; - } - - /** - *

- * chooseUntapType. - *

- * - * @param type - * a {@link java.lang.String} object. - * @param activate - * a {@link forge.game.card.Card} object. - * @param untap - * a boolean. - * @param amount - * a int. - * @return a {@link forge.CardList} object. - */ - public static List chooseUntapType(final Player ai, final String type, final Card activate, final boolean untap, final int amount) { - List typeList = - CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(","), activate.getController(), activate); - - // is this needed? - typeList = CardLists.filter(typeList, Presets.TAPPED); - - if (untap) { - typeList.remove(activate); - } - - if (typeList.size() < amount) { - return null; - } - - CardLists.sortByPowerDesc(typeList); - - final List untapList = new ArrayList(); - - for (int i = 0; i < amount; i++) { - untapList.add(typeList.get(i)); - } - return untapList; - } - - /** - *

- * chooseReturnType. - *

- * - * @param type - * a {@link java.lang.String} object. - * @param activate - * a {@link forge.game.card.Card} object. - * @param target - * a {@link forge.game.card.Card} object. - * @param amount - * a int. - * @return a {@link forge.CardList} object. - */ - public static List chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount) { - final List typeList = - CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate); - if ((target != null) && target.getController() == ai && typeList.contains(target)) { - // don't bounce the card we're pumping - typeList.remove(target); - } - - if (typeList.size() < amount) { - return null; - } - - CardLists.sortByPowerAsc(typeList); - final List returnList = new ArrayList(); - - for (int i = 0; i < amount; i++) { - returnList.add(typeList.get(i)); - } - return returnList; - } - - /** - *

- * sacrificePermanents. - *

- * @param amount - * a int. - * @param source - * the source SpellAbility - * @param destroy - * the destroy - * @param list - * a {@link forge.CardList} object. - * - * @return the card list - */ - public static List choosePermanentsToSacrifice(final Player ai, final List cardlist, final int amount, SpellAbility source, - final boolean destroy, final boolean isOptional) { - List remaining = new ArrayList(cardlist); - final List sacrificed = new ArrayList(); - - if (isOptional && source.getActivatingPlayer().isOpponentOf(ai)) { - return sacrificed; // sacrifice none - } - if (isOptional && source.hasParam("Devour")) { - remaining = CardLists.filter(remaining, new Predicate() { - @Override - public boolean apply(final Card c) { - if ((c.getCMC() <= 1 && c.getNetAttack() < 3) - || c.getNetAttack() + c.getNetDefense() <= 3) { - return true; - } - return false; - } - }); - } - CardLists.sortByCmcDesc(remaining); - Collections.reverse(remaining); - - final int max = Math.min(remaining.size(), amount); - - for (int i = 0; i < max; i++) { - Card c = chooseCardToSacrifice(remaining, ai, destroy); - remaining.remove(c); - sacrificed.add(c); - } - return sacrificed; - } - - // Precondition it wants: remaining are reverse-sorted by CMC - private static Card chooseCardToSacrifice(final List remaining, final Player ai, final boolean destroy) { - // If somehow ("Drop of Honey") they suggest to destroy opponent's card - use the chance! - for(Card c : remaining) { // first compare is fast, second is precise - if (c.getController() != ai && ai.getOpponents().contains(c.getController()) ) - return c; - } - - if (destroy) { - final List indestructibles = CardLists.getKeyword(remaining, "Indestructible"); - if (!indestructibles.isEmpty()) { - return indestructibles.get(0); - } - } - for (int ip = 0; ip < 6; ip++) { // priority 0 is the lowest, priority 5 the highest - final int priority = 6 - ip; - for (Card card : remaining) { - if (card.hasSVar("SacMe") && Integer.parseInt(card.getSVar("SacMe")) == priority) { - return card; - } - } - } - - Card c; - - if (CardLists.getNotType(remaining, "Creature").size() == 0) { - c = ComputerUtilCard.getWorstCreatureAI(remaining); - } else if (CardLists.getNotType(remaining, "Land").size() == 0) { - c = ComputerUtilCard.getWorstLand(CardLists.filter(remaining, CardPredicates.Presets.LANDS)); - } else { - c = ComputerUtilCard.getWorstPermanentAI(remaining, false, false, false, false); - } - - final ArrayList auras = c.getEnchantedBy(); - - if (auras.size() > 0) { - // TODO: choose "worst" controlled enchanting Aura - for (int j = 0; j < auras.size(); j++) { - final Card aura = auras.get(j); - if (aura.getController().equals(c.getController()) && remaining.contains(aura)) { - return aura; - } - } - } - return c; - } - - /** - *

- * canRegenerate. - *

- * @param ai - * - * @param card - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean canRegenerate(Player ai, final Card card) { - - if (card.hasKeyword("CARDNAME can't be regenerated.")) { - return false; - } - - final Player controller = card.getController(); - final Game game = controller.getGame(); - final List l = controller.getCardsIn(ZoneType.Battlefield); - for (final Card c : l) { - for (final SpellAbility sa : c.getSpellAbilities()) { - // This try/catch should fix the "computer is thinking" bug - try { - - if (!sa.isAbility() || sa.getApi() != ApiType.Regenerate) { - continue; // Not a Regenerate ability - } - sa.setActivatingPlayer(controller); - if (!(sa.canPlay() && ComputerUtilCost.canPayCost(sa, controller))) { - continue; // Can't play ability - } - - if (controller == ai) { - final Cost abCost = sa.getPayCosts(); - if (abCost != null) { - if (!ComputerUtilCost.checkLifeCost(controller, abCost, c, 4, null)) { - continue; // Won't play ability - } - - if (!ComputerUtilCost.checkSacrificeCost(controller, abCost, c)) { - continue; // Won't play ability - } - - if (!ComputerUtilCost.checkCreatureSacrificeCost(controller, abCost, c)) { - continue; // Won't play ability - } - } - } - - final TargetRestrictions tgt = sa.getTargetRestrictions(); - if (tgt != null) { - if (CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), controller, sa.getSourceCard()).contains(card)) { - return true; - } - } else if (AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa).contains(card)) { - return true; - } - - } catch (final Exception ex) { - throw new RuntimeException(String.format("There is an error in the card code for %s:%n", c.getName(), ex.getMessage()), ex); - } - } - } - return false; - } - - /** - *

- * possibleDamagePrevention. - *

- * - * @param card - * a {@link forge.game.card.Card} object. - * @return a int. - */ - public static int possibleDamagePrevention(final Card card) { - - int prevented = 0; - - final Player controller = card.getController(); - final Game game = controller.getGame(); - - final List l = controller.getCardsIn(ZoneType.Battlefield); - for (final Card c : l) { - for (final SpellAbility sa : c.getSpellAbilities()) { - // if SA is from AF_Counter don't add to getPlayable - // This try/catch should fix the "computer is thinking" bug - try { - if (sa.getApi() == null || !sa.isAbility()) { - continue; - } - - if (sa.getApi() == ApiType.PreventDamage && sa.canPlay() - && ComputerUtilCost.canPayCost(sa, controller)) { - if (AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa).contains(card)) { - prevented += AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("Amount"), sa); - } - final TargetRestrictions tgt = sa.getTargetRestrictions(); - if (tgt != null) { - if (CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), controller, sa.getSourceCard()).contains(card)) { - prevented += AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("Amount"), sa); - } - - } - } - } catch (final Exception ex) { - throw new RuntimeException(String.format("There is an error in the card code for %s:%n", c.getName(), ex.getMessage()), ex); - } - } - } - return prevented; - } - - /** - *

- * castPermanentInMain1. - *

- * - * @param sa - * a SpellAbility object. - * @return a boolean. - */ - public static boolean castPermanentInMain1(final Player ai, final SpellAbility sa) { - final Card card = sa.getSourceCard(); - if ("True".equals(card.getSVar("NonStackingEffect")) && card.getController().isCardInPlay(card.getName())) { - return false; - } - if (card.getSVar("PlayMain1").equals("TRUE") && (!card.getController().getCreaturesInPlay().isEmpty() || sa.getPayCosts().hasNoManaCost())) { - return true; - } - if ((card.isCreature() && (ComputerUtil.hasACardGivingHaste(ai) - || card.hasKeyword("Haste"))) || card.hasKeyword("Exalted")) { - return true; - } - //cast equipments in Main1 when there are creatures to equip and no other unequipped equipment - if (card.isEquipment()) { - boolean playNow = false; - for (Card c : card.getController().getCardsIn(ZoneType.Battlefield)) { - if (c.isEquipment() && !c.isEquipping()) { - playNow = false; - break; - } - if (!playNow && c.isCreature() && CombatUtil.canAttackNextTurn(c) && c.canBeEquippedBy(card)) { - playNow = true; - } - } - if (playNow) { - return true; - } - } - - // get all cards the computer controls with BuffedBy - final List buffed = ai.getCardsIn(ZoneType.Battlefield); - for (Card buffedcard : buffed) { - if (buffedcard.hasSVar("BuffedBy")) { - final String buffedby = buffedcard.getSVar("BuffedBy"); - final String[] bffdby = buffedby.split(","); - if (card.isValid(bffdby, buffedcard.getController(), buffedcard)) { - return true; - } - } - if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ai.getOpponent())) { - return true; - } - if (card.isCreature()) { - if (buffedcard.hasKeyword("Soulbond") && !buffedcard.isPaired()) { - return true; - } - if (buffedcard.hasKeyword("Evolve")) { - if (buffedcard.getNetAttack() < card.getNetAttack() || buffedcard.getNetDefense() < card.getNetDefense()) { - return true; - } - } - } - if (card.hasKeyword("Soulbond") && buffedcard.isCreature() && !buffedcard.isPaired()) { - return true; - } - - } // BuffedBy - - // get all cards the human controls with AntiBuffedBy - final List antibuffed = ai.getOpponent().getCardsIn(ZoneType.Battlefield); - for (Card buffedcard : antibuffed) { - if (buffedcard.hasSVar("AntiBuffedBy")) { - final String buffedby = buffedcard.getSVar("AntiBuffedBy"); - final String[] bffdby = buffedby.split(","); - if (card.isValid(bffdby, buffedcard.getController(), buffedcard)) { - return true; - } - } - } // AntiBuffedBy - final List vengevines = ai.getCardsIn(ZoneType.Graveyard, "Vengevine"); - if (!vengevines.isEmpty()) { - final List creatures = ai.getCardsIn(ZoneType.Hand); - final List creatures2 = new ArrayList(); - for (int i = 0; i < creatures.size(); i++) { - if (creatures.get(i).isCreature() && creatures.get(i).getManaCost().getCMC() <= 3) { - creatures2.add(creatures.get(i)); - } - } - if (((creatures2.size() + CardUtil.getThisTurnCast("Creature.YouCtrl", vengevines.get(0)).size()) > 1) - && card.isCreature() && card.getManaCost().getCMC() <= 3) { - return true; - } - } - return false; - } - - /** - * Is it OK to cast this for less than the Max Targets? - * @param source the source Card - * @return true if it's OK to cast this Card for less than the max targets - */ - public static boolean shouldCastLessThanMax(final Player ai, final Card source) { - boolean ret = true; - if (source.getManaCost().countX() > 0) { - // If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available. - return ret; - } else { - // Otherwise, if life is possibly in danger, then this is fine. - Combat combat = new Combat(ai.getOpponent()); - List attackers = ai.getOpponent().getCreaturesInPlay(); - for (Card att : attackers) { - if (CombatUtil.canAttackNextTurn(att, ai)) { - combat.addAttacker(att, att.getController().getOpponent()); - } - } - AiBlockController aiBlock = new AiBlockController(ai); - aiBlock.assignBlockers(combat); - if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { - // Otherwise, return false. Do not play now. - ret = false; - } - } - return ret; - } - - /** - * Is this discard probably worse than a random draw? - * @param discard Card to discard - * @return boolean - */ - public static boolean isWorseThanDraw(final Player ai, Card discard) { - if (discard.hasSVar("DiscardMe")) { - return true; - } - - final Game game = ai.getGame(); - final List landsInPlay = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS); - final List landsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS); - final List nonLandsInHand = CardLists.getNotType(ai.getCardsIn(ZoneType.Hand), "Land"); - final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, CardPredicates.Accessors.fnGetCmc)); - final int discardCMC = discard.getCMC(); - if (discard.isLand()) { - if (landsInPlay.size() >= highestCMC - || (landsInPlay.size() + landsInHand.size() > 6 && landsInHand.size() > 1) - || (landsInPlay.size() > 3 && nonLandsInHand.size() == 0)) { - // Don't need more land. - return true; - } - } else { //non-land - if (discardCMC > landsInPlay.size() + landsInHand.size() + 2) { - // not castable for some time. - return true; - } else if (!game.getPhaseHandler().isPlayerTurn(ai) - && game.getPhaseHandler().getPhase().isAfter(PhaseType.MAIN2) - && discardCMC > landsInPlay.size() + landsInHand.size() - && discardCMC > landsInPlay.size() + 1 - && nonLandsInHand.size() > 1) { - // not castable for at least one other turn. - return true; - } else if (landsInPlay.size() > 5 && discard.getCMC() <= 1 - && !discard.hasProperty("hasXCost", ai, null)) { - // Probably don't need small stuff now. - return true; - } - } - return false; - } - - // returns true if it's better to wait until blockers are declared - /** - *

- * waitForBlocking. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean (returns true if it's better to wait until blockers are declared). - */ - public static boolean waitForBlocking(final SpellAbility sa) { - final Game game = sa.getActivatingPlayer().getGame(); - final PhaseHandler ph = game.getPhaseHandler(); - - return (sa.getSourceCard().isCreature() - && sa.getPayCosts().hasTapCost() - && (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) - || !ph.getNextTurn().equals(sa.getActivatingPlayer())) - && !sa.getSourceCard().hasKeyword("At the beginning of the end step, exile CARDNAME.") - && !sa.getSourceCard().hasKeyword("At the beginning of the end step, sacrifice CARDNAME.")); - } - - /** - *

- * castSpellMain1. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean (returns true if it's better to wait until blockers are declared). - */ - public static boolean castSpellInMain1(final Player ai, final SpellAbility sa) { - final Card source = sa.getSourceCard(); - final SpellAbility sub = sa.getSubAbility(); - - // Cipher spells - if (sub != null) { - final ApiType api = sub.getApi(); - if (ApiType.Encode == api && !ai.getCreaturesInPlay().isEmpty()) { - return true; - } - if (ApiType.PumpAll == api && !ai.getCreaturesInPlay().isEmpty()) { - return true; - } - if (ApiType.Pump == api) { - return true; - } - } - final List buffed = ai.getCardsIn(ZoneType.Battlefield); - boolean checkThreshold = sa.isSpell() && !ai.hasThreshold() && !sa.getSourceCard().isInZone(ZoneType.Graveyard); - for (Card buffedCard : buffed) { - if (buffedCard.hasSVar("BuffedBy")) { - final String buffedby = buffedCard.getSVar("BuffedBy"); - final String[] bffdby = buffedby.split(","); - if (source.isValid(bffdby, buffedCard.getController(), buffedCard)) { - return true; - } - } - //Fill the graveyard for Threshold - if (checkThreshold) { - for (StaticAbility stAb : buffedCard.getStaticAbilities()) { - if ("Threshold".equals(stAb.getMapParams().get("Condition"))) { - return true; - } - } - } - } - - // get all cards the human controls with AntiBuffedBy - final List antibuffed = ai.getOpponent().getCardsIn(ZoneType.Battlefield); - for (Card buffedcard : antibuffed) { - if (buffedcard.hasSVar("AntiBuffedBy")) { - final String buffedby = buffedcard.getSVar("AntiBuffedBy"); - final String[] bffdby = buffedby.split(","); - if (source.isValid(bffdby, buffedcard.getController(), buffedcard)) { - return true; - } - } - } // AntiBuffedBy - - if (sub != null) { - return castSpellInMain1(ai, sub); - } - - return false; - } - - // returns true if the AI should stop using the ability - /** - *

- * preventRunAwayActivations. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean (returns true if the AI should stop using the ability). - */ - public static boolean preventRunAwayActivations(final SpellAbility sa) { - int activations = sa.getRestrictions().getNumberTurnActivations(); - - if (sa.isTemporary()) { - final Random r = MyRandom.getRandom(); - return r.nextFloat() >= .95; // Abilities created by static abilities have no memory - } - - if (activations < 10) { //10 activations per turn should still be acceptable - return false; - } - - final Random r = MyRandom.getRandom(); - return r.nextFloat() >= Math.pow(.95, activations); - } - - /** - * TODO: Write javadoc for this method. - * @param sa - * @return - */ - public static boolean activateForCost(SpellAbility sa, final Player ai) { - final Cost abCost = sa.getPayCosts(); - final Card source = sa.getSourceCard(); - if (abCost == null) { - return false; - } - if (abCost.hasTapCost()) { - for (Card c : ai.getGame().getCardsIn(ZoneType.Battlefield)) { - if (c.hasSVar("AITapDown")) { - if (source.isValid(c.getSVar("AITapDown"), c.getController(), c)) { - return true; - } - } - } - } else if (sa.hasParam("Planeswalker") && ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) { - for (final CostPart part : abCost.getCostParts()) { - if (part instanceof CostPutCounter) { - return true; - } - } - } - for (final CostPart part : abCost.getCostParts()) { - if (part instanceof CostSacrifice) { - final CostSacrifice sac = (CostSacrifice) part; - - final String type = sac.getType(); - - if (type.equals("CARDNAME")) { - if (source.getSVar("SacMe").equals("6")) { - return true; - } - continue; - } - - final List typeList = - CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(","), source.getController(), source); - for (Card c : typeList) { - if (c.getSVar("SacMe").equals("6")) { - return true; - } - } - } - } - return false; - } - - /** - *

- * hasACardGivingHaste. - *

- * - * @return a boolean. - */ - public static boolean hasACardGivingHaste(final Player ai) { - final List all = new ArrayList(ai.getCardsIn(ZoneType.Battlefield)); - - for (final Card c : all) { - if (c.isEquipment()) { - for (StaticAbility stAb : c.getStaticAbilities()) { - HashMap params = stAb.getMapParams(); - if ("Continuous".equals(params.get("Mode")) && params.containsKey("AddKeyword") - && params.get("AddKeyword").contains("Haste") && c.getEquippingCard() == null) { - return true; - } - } - } - } - - all.addAll(ai.getCardsActivableInExternalZones()); - all.addAll(ai.getCardsIn(ZoneType.Hand)); - - for (final Card c : all) { - for (final SpellAbility sa : c.getSpellAbilities()) { - if (sa.getApi() == ApiType.Pump && sa.hasParam("KW") && sa.getParam("KW").contains("Haste")) { - return true; - } - } - } - return false; - } // hasACardGivingHaste - - /** - * TODO: Write javadoc for this method. - * @param ai - * @return - */ - public static int possibleNonCombatDamage(Player ai) { - int damage = 0; - final List all = new ArrayList(ai.getCardsIn(ZoneType.Battlefield)); - all.addAll(ai.getCardsActivableInExternalZones()); - all.addAll(ai.getCardsIn(ZoneType.Hand)); - - for (final Card c : all) { - for (final SpellAbility sa : c.getSpellAbilities()) { - if (sa.getApi() != ApiType.DealDamage) { - continue; - } - final String numDam = sa.getParam("NumDmg"); - int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), numDam, sa); - if (dmg <= damage) { - continue; - } - final TargetRestrictions tgt = sa.getTargetRestrictions(); - if (tgt == null) { - continue; - } - final Player enemy = ai.getOpponent(); - if (!sa.canTarget(enemy)) { - continue; - } - if (!ComputerUtilCost.canPayCost(sa, ai)) { - continue; - } - damage = dmg; - } - } - return damage; - } - - /** - *

- * predictThreatenedObjects. - *

- * - * @param saviourAf - * a AbilityFactory object - * @return a {@link java.util.ArrayList} object. - * @since 1.0.15 - */ - public static List predictThreatenedObjects(final Player aiPlayer, final SpellAbility sa) { - final Game game = aiPlayer.getGame(); - final List objects = new ArrayList(); - if (game.getStack().isEmpty()) { - return objects; - } - - // check stack for something that will kill this - final SpellAbility topStack = game.getStack().peekAbility(); - Iterables.addAll(objects, ComputerUtil.predictThreatenedObjects(aiPlayer, sa, topStack)); - - return objects; - } - - /** - *

- * predictThreatenedObjects. - *

- * - * @param saviourAf - * a AbilityFactory object - * @param topStack - * a {@link forge.game.spellability.SpellAbility} object. - * @return a {@link java.util.ArrayList} object. - * @since 1.0.15 - */ - private static Iterable predictThreatenedObjects(final Player aiPlayer, final SpellAbility saviour, - final SpellAbility topStack) { - Iterable objects = new ArrayList(); - final List threatened = new ArrayList(); - ApiType saviourApi = saviour == null ? null : saviour.getApi(); - - if (topStack == null) { - return objects; - } - - final Card source = topStack.getSourceCard(); - final ApiType threatApi = topStack.getApi(); - - // Can only Predict things from AFs - if (threatApi == null) { - return threatened; - } - final TargetRestrictions tgt = topStack.getTargetRestrictions(); - - if (tgt == null) { - if (topStack.hasParam("Defined")) { - objects = AbilityUtils.getDefinedObjects(source, topStack.getParam("Defined"), topStack); - } else if (topStack.hasParam("ValidCards")) { - List battleField = aiPlayer.getCardsIn(ZoneType.Battlefield); - objects = CardLists.getValidCards(battleField, topStack.getParam("ValidCards").split(","), source.getController(), source); - } - } else { - objects = topStack.getTargets().getTargets(); - } - - // Determine if Defined Objects are "threatened" will be destroyed - // due to this SA - - // Lethal Damage => prevent damage/regeneration/bounce/shroud - if (threatApi == ApiType.DealDamage || threatApi == ApiType.DamageAll) { - // If PredictDamage is >= Lethal Damage - final int dmg = AbilityUtils.calculateAmount(topStack.getSourceCard(), - topStack.getParam("NumDmg"), topStack); - for (final Object o : objects) { - if (o instanceof Card) { - final Card c = (Card) o; - - // indestructible - if (c.hasKeyword("Indestructible")) { - continue; - } - - // already regenerated - if (!c.getShield().isEmpty()) { - continue; - } - - // don't use it on creatures that can't be regenerated - if ((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll) && !c.canBeShielded()) { - continue; - } - - // give Shroud to targeted creatures - if (saviourApi == ApiType.Pump && tgt == null && saviour.hasParam("KW") - && (saviour.getParam("KW").endsWith("Shroud") - || saviour.getParam("KW").endsWith("Hexproof"))) { - continue; - } - - // don't bounce or blink a permanent that the human - // player owns or is a token - if (saviourApi == ApiType.ChangeZone && (c.getOwner().isOpponentOf(aiPlayer) || c.isToken())) { - continue; - } - - if (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c)) { - threatened.add(c); - } - } else if (o instanceof Player) { - final Player p = (Player) o; - - if (source.hasKeyword("Infect")) { - if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getPoisonCounters()) { - threatened.add(p); - } - } else if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getLife()) { - threatened.add(p); - } - } - } - } - // Destroy => regeneration/bounce/shroud - else if ((threatApi == ApiType.Destroy || threatApi == ApiType.DestroyAll) - && (((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll) - && !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump - || saviourApi == null)) { - for (final Object o : objects) { - if (o instanceof Card) { - final Card c = (Card) o; - // indestructible - if (c.hasKeyword("Indestructible")) { - continue; - } - - // already regenerated - if (!c.getShield().isEmpty()) { - continue; - } - - // give Shroud to targeted creatures - if (saviourApi == ApiType.Pump && tgt == null && saviour.hasParam("KW") - && (saviour.getParam("KW").endsWith("Shroud") - || saviour.getParam("KW").endsWith("Hexproof"))) { - continue; - } - - // don't bounce or blink a permanent that the human - // player owns or is a token - if (saviourApi == ApiType.ChangeZone && (c.getOwner().isOpponentOf(aiPlayer) || c.isToken())) { - continue; - } - - // don't use it on creatures that can't be regenerated - if (saviourApi == ApiType.Regenerate && !c.canBeShielded()) { - continue; - } - threatened.add(c); - } - } - } - // Exiling => bounce/shroud - else if ((threatApi == ApiType.ChangeZone || threatApi == ApiType.ChangeZoneAll) - && (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == null) - && topStack.hasParam("Destination") - && topStack.getParam("Destination").equals("Exile")) { - for (final Object o : objects) { - if (o instanceof Card) { - final Card c = (Card) o; - // give Shroud to targeted creatures - if (saviourApi == ApiType.Pump && tgt == null && saviour.hasParam("KW") - && (saviour.getParam("KW").endsWith("Shroud") || saviour.getParam("KW").endsWith("Hexproof"))) { - continue; - } - - // don't bounce or blink a permanent that the human - // player owns or is a token - if (saviourApi == ApiType.ChangeZone && (c.getOwner().isOpponentOf(aiPlayer) || c.isToken())) { - continue; - } - - threatened.add(c); - } - } - } - - Iterables.addAll(threatened, ComputerUtil.predictThreatenedObjects(aiPlayer, saviour, topStack.getSubAbility())); - return threatened; - } - - public static boolean playImmediately(Player ai, SpellAbility sa) { - final Card source = sa.getSourceCard(); - final Zone zone = source.getZone(); - - if (zone.getZoneType() == ZoneType.Battlefield) { - if (predictThreatenedObjects(ai, null).contains(source)) { - return true; - } - } - return false; - } - - // Computer mulligans if there are no cards with converted mana cost of - // 0 in its hand - public static boolean wantMulligan(Player ai) { - final List handList = ai.getCardsIn(ZoneType.Hand); - final boolean hasLittleCmc0Cards = CardLists.getValidCards(handList, "Card.cmcEQ0", ai, null).size() < 2; - final AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); - return (handList.size() > aic.getIntProperty(AiProps.MULLIGAN_THRESHOLD)) && hasLittleCmc0Cards; - } - - public static List getPartialParisCandidates(Player ai) { - final List candidates = new ArrayList(); - final List handList = ai.getCardsIn(ZoneType.Hand); - - final List lands = CardLists.getValidCards(handList, "Card.Land", ai, null); - final List nonLands = CardLists.getValidCards(handList, "Card.nonLand", ai, null); - CardLists.sortByCmcDesc(nonLands); - - if(lands.size() >= 3 && lands.size() <= 4) - return candidates; - - if(lands.size() < 3) - { - //Not enough lands! - int tgtCandidates = Math.max(Math.abs(lands.size()-nonLands.size()), 3); - System.out.println("Partial Paris: " + ai.getName() + " lacks lands, aiming to exile " + tgtCandidates + " cards."); - - for(int i=0;i> numProducers = new ArrayList>(cntColors); - for(byte col : MagicColor.WUBRG) { - numProducers.add(col, new ArrayList()); - } - - - for(Card c : lands) - { - for(SpellAbility sa : c.getManaAbility()) - { - AbilityManaPart abmana = sa.getManaPart(); - for(byte col : MagicColor.WUBRG) { - if(abmana.canProduce(MagicColor.toLongString(col))) { - numProducers.get(col).add(c); - } - } - } - } - } - - System.out.print("Partial Paris: " + ai.getName() + " may exile "); - for(Card c : candidates) - { - System.out.print(c.toString() + ", "); - } - System.out.println(); - - if(candidates.size() < 2) - candidates.clear(); - return candidates; - } - - - public static boolean scryWillMoveCardToBottomOfLibrary(Player player, Card c) { - boolean bottom = false; - if (c.isBasicLand()) { - List bl = player.getCardsIn(ZoneType.Battlefield); - int nBasicLands = Iterables.size(Iterables.filter(bl, CardPredicates.Presets.LANDS)); - bottom = nBasicLands > 5; // if control more than 5 Basic land, probably don't need more - } else if (c.isCreature()) { - List cl = player.getCardsIn(ZoneType.Battlefield); - cl = CardLists.filter(cl, CardPredicates.Presets.CREATURES); - bottom = cl.size() > 5; // if control more than 5 Creatures, probably don't need more - } - return bottom; - } - - /** - * TODO: Write javadoc for this method. - * @param chooser - * @param discarder - * @param sa - * @param validCards - * @param min - * @return - */ - public static List getCardsToDiscardFromOpponent(Player chooser, Player discarder, SpellAbility sa, List validCards, int min, int max) { - List goodChoices = CardLists.filter(validCards, new Predicate() { - @Override - public boolean apply(final Card c) { - if (c.hasSVar("DiscardMeByOpp") || c.hasSVar("DiscardMe")) { - return false; - } - return true; - } - }); - if (goodChoices.isEmpty()) { - goodChoices = validCards; - } - final List dChoices = new ArrayList(); - if (sa.hasParam("DiscardValid")) { - final String validString = sa.getParam("DiscardValid"); - if (validString.contains("Creature") && !validString.contains("nonCreature")) { - final Card c = ComputerUtilCard.getBestCreatureAI(goodChoices); - if (c != null) { - dChoices.add(ComputerUtilCard.getBestCreatureAI(goodChoices)); - } - } - } - - Collections.sort(goodChoices, CardLists.TextLenComparator); - - CardLists.sortByCmcDesc(goodChoices); - dChoices.add(goodChoices.get(0)); - - return Aggregates.random(goodChoices, min); - } - - /** - * TODO: Write javadoc for this method. - * @param aiChoser - * @param p - * @param sa - * @param validCards - * @param min - * @return - */ - public static List getCardsToDiscardFromFriend(Player aiChooser, Player p, SpellAbility sa, List validCards, int min, int max) { - if (p == aiChooser) { // ask that ai player what he would like to discard - final AiController aic = ((PlayerControllerAi)p.getController()).getAi(); - return aic.getCardsToDiscard(min, max, validCards, sa); - } - // no special options for human or remote friends - return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min, max); - } - - public static String chooseSomeType(Player ai, String kindOfType, String logic, List invalidTypes) { - final Game game = ai.getGame(); - String chosen = ""; - if( kindOfType.equals("Card")) { - // TODO - // computer will need to choose a type - // based on whether it needs a creature or land, - // otherwise, lib search for most common type left - // then, reveal chosenType to Human - if (game.getPhaseHandler().is(PhaseType.UNTAP) && logic == null) { // Storage Matrix - double amount = 0; - for (String type : Constant.CARD_TYPES) { - if (!invalidTypes.contains(type)) { - List list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(type), Presets.TAPPED); - double i = type.equals("Creature") ? list.size() * 1.5 : list.size(); - if (i > amount) { - amount = i; - chosen = type; - } - } - } - } - if (StringUtils.isEmpty(chosen)) { - chosen = "Creature"; - } - } else if (kindOfType.equals("Creature")) { - Player opp = ai.getOpponent(); - if (logic != null ) { - if (logic.equals("MostProminentOnBattlefield")) { - chosen = ComputerUtilCard.getMostProminentCreatureType(game.getCardsIn(ZoneType.Battlefield)); - } - else if (logic.equals("MostProminentComputerControls")) { - chosen = ComputerUtilCard.getMostProminentCreatureType(ai.getCardsIn(ZoneType.Battlefield)); - } - else if (logic.equals("MostProminentHumanControls")) { - chosen = ComputerUtilCard.getMostProminentCreatureType(opp.getCardsIn(ZoneType.Battlefield)); - if (!CardType.isACreatureType(chosen) || invalidTypes.contains(chosen)) { - chosen = ComputerUtilCard.getMostProminentCreatureType(CardLists.filterControlledBy(game.getCardsInGame(), opp)); - } - } - else if (logic.equals("MostProminentInComputerDeck")) { - chosen = ComputerUtilCard.getMostProminentCreatureType(CardLists.filterControlledBy(game.getCardsInGame(), ai)); - } - else if (logic.equals("MostProminentInComputerGraveyard")) { - chosen = ComputerUtilCard.getMostProminentCreatureType(ai.getCardsIn(ZoneType.Graveyard)); - } - } - if (!CardType.isACreatureType(chosen) || invalidTypes.contains(chosen)) { - chosen = "Sliver"; - } - - } else if ( kindOfType.equals("Basic Land")) { - if (logic != null) { - if (logic.equals("MostNeededType")) { - // Choose a type that is in the deck, but not in hand or on the battlefield - final ArrayList basics = new ArrayList(); - basics.addAll(CardType.Constant.BASIC_TYPES); - List presentCards = ai.getCardsIn(ZoneType.Battlefield); - presentCards.addAll(ai.getCardsIn(ZoneType.Hand)); - List possibleCards = ai.getAllCards(); - - for (String b : basics) { - if(!Iterables.any(presentCards, CardPredicates.isType(b)) && Iterables.any(possibleCards, CardPredicates.isType(b))) { - chosen = b; - } - } - if (chosen.equals("")) { - for (String b : basics) { - if(Iterables.any(possibleCards, CardPredicates.isType(b))) { - chosen = b; - } - } - } - } else if (logic.equals("ChosenLandwalk")) { - for (Card c : ai.getOpponent().getLandsInPlay()) { - for (String t : c.getType()) { - if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) { - chosen = t; - break; - } - } - } - } - } - - if (!CardType.isABasicLandType(chosen) || invalidTypes.contains(chosen)) { - chosen = "Island"; - } - - } else if( kindOfType.equals("Land") ) { - if (logic != null) { - if (logic.equals("ChosenLandwalk")) { - for (Card c : ai.getOpponent().getLandsInPlay()) { - for (String t : c.getType()) { - if (!invalidTypes.contains(t) && CardType.isALandType(t)) { - chosen = t; - break; - } - } - } - } - } - if( StringUtils.isEmpty(chosen)) - chosen = "Island"; - } - - return chosen; - } - - public static List getSafeTargets(final Player ai, SpellAbility sa, List validCards) { - List safeCards = new ArrayList(validCards); - safeCards = CardLists.filter(safeCards, new Predicate() { - @Override - public boolean apply(final Card c) { - if (c.getController() == ai) { - if (c.getSVar("Targeting").equals("Dies") || c.getSVar("Targeting").equals("Counter")) - return false; - } - return true; - } - }); - return safeCards; - } - - public static int damageFromETB(final Player player, final Card permanent) { - int damage = 0; - final Game game = player.getGame(); - final ArrayList theTriggers = new ArrayList(); - - for (Card card : game.getCardsIn(ZoneType.Battlefield)) { - theTriggers.addAll(card.getTriggers()); - } - for (Trigger trigger : theTriggers) { - HashMap trigParams = trigger.getMapParams(); - final Card source = trigger.getHostCard(); - - - if (!trigger.zonesCheck(game.getZoneOf(source))) { - continue; - } - if (!trigger.requirementsCheck(game)) { - continue; - } - if (trigParams.containsKey("CheckOnTriggeredCard") - && AbilityUtils.getDefinedCards(permanent, source.getSVar(trigParams.get("CheckOnTriggeredCard").split(" ")[0]), null).isEmpty()) { - continue; - } - TriggerType mode = trigger.getMode(); - if (mode != TriggerType.ChangesZone) { - continue; - } - if (!"Battlefield".equals(trigParams.get("Destination"))) { - continue; - } - if (trigParams.containsKey("ValidCard")) { - if (!permanent.isValid(trigParams.get("ValidCard"), source.getController(), source)) { - continue; - } - } - - String ability = source.getSVar(trigParams.get("Execute")); - if (ability.isEmpty()) { - continue; - } - - final Map abilityParams = AbilityFactory.getMapParams(ability); - // Destroy triggers - if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage")) - || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) { - if (!"TriggeredCardController".equals(abilityParams.get("Defined"))) { - continue; - } - if (!abilityParams.containsKey("NumDmg")) { - continue; - } - damage += ComputerUtilCombat.predictDamageTo(player, AbilityUtils.calculateAmount(source, abilityParams.get("NumDmg"), null), source, false); - } else if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("LoseLife")) - || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("LoseLife"))) { - if (!"TriggeredCardController".equals(abilityParams.get("Defined"))) { - continue; - } - if (!abilityParams.containsKey("LifeAmount")) { - continue; - } - damage += AbilityUtils.calculateAmount(source, abilityParams.get("LifeAmount"), null); - } - } - return damage; - } - - public static boolean isNegativeCounter(CounterType type, Card c) { - return type == CounterType.AGE || type == CounterType.BLAZE || type == CounterType.BRIBERY || type == CounterType.DOOM - || type == CounterType.ICE || type == CounterType.M1M1 || type == CounterType.M0M2 || type == CounterType.M0M1 - || type == CounterType.M1M0 || type == CounterType.M2M1 || type == CounterType.M2M2 || type == CounterType.MUSIC - || type == CounterType.PARALYZATION || type == CounterType.SHELL || type == CounterType.SLEEP - || type == CounterType.SLEIGHT || (type == CounterType.TIME && !c.isInPlay()) || type == CounterType.WAGE; - } - - /** - *

- * evaluateBoardPosition. - *

- * - * @param listToEvaluate - * a list of players to evaluate. - * @return a Player. - */ - public static Player evaluateBoardPosition(final List listToEvaluate) { - Player bestBoardPosition = listToEvaluate.get(0); - int bestBoardRating = 0; - - for (final Player p : listToEvaluate) { - int pRating = p.getLife() * 3; - pRating += p.getLandsInPlay().size() * 2; - - for (final Card c : p.getCardsIn(ZoneType.Battlefield)) { - pRating += ComputerUtilCard.evaluateCreature(c) / 3; - } - - if (p.getCardsIn(ZoneType.Library).size() < 3) { - pRating /= 5; - } - - System.out.println("Board position evaluation for " + p + ": " + pRating); - - if (pRating > bestBoardRating) { - bestBoardRating = pRating; - bestBoardPosition = p; - } - } - - return bestBoardPosition; - } -} +/* + * 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 . + */ +package forge.ai; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; + +import forge.card.CardType; +import forge.card.MagicColor; +import forge.card.CardType.Constant; +import forge.error.BugReporter; +import forge.game.Game; +import forge.game.GameObject; +import forge.game.ability.AbilityFactory; +import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; +import forge.game.ability.effects.CharmEffect; +import forge.game.ai.AiProps; +import forge.game.card.Card; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +import forge.game.card.CardUtil; +import forge.game.card.CounterType; +import forge.game.card.CardPredicates.Presets; +import forge.game.combat.Combat; +import forge.game.combat.CombatUtil; +import forge.game.cost.Cost; +import forge.game.cost.CostDiscard; +import forge.game.cost.CostPart; +import forge.game.cost.CostPayment; +import forge.game.cost.CostPutCounter; +import forge.game.cost.CostSacrifice; +import forge.game.phase.PhaseHandler; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.player.PlayerControllerAi; +import forge.game.spellability.AbilityManaPart; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityStackInstance; +import forge.game.spellability.TargetRestrictions; +import forge.game.staticability.StaticAbility; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerType; +import forge.game.zone.Zone; +import forge.game.zone.ZoneType; +import forge.util.Aggregates; +import forge.util.MyRandom; + + +/** + *

+ * ComputerUtil class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class ComputerUtil { + + /** + *

+ * handlePlayingSpellAbility. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean. + */ + public static boolean handlePlayingSpellAbility(final Player ai, final SpellAbility sa, final Game game) { + + game.getStack().freezeStack(); + final Card source = sa.getSourceCard(); + + if (sa.isSpell() && !source.isCopiedSpell()) { + sa.setSourceCard(game.getAction().moveToStack(source)); + } + + if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { + CharmEffect.makeChoices(sa); + } + + if (sa.hasParam("Bestow")) { + sa.getSourceCard().animateBestow(); + } + + final Cost cost = sa.getPayCosts(); + + if (cost == null) { + if (ComputerUtilMana.payManaCost(ai, sa)) { + game.getStack().addAndUnfreeze(sa); + return true; + } + } else { + final CostPayment pay = new CostPayment(cost, sa); + if (pay.payComputerCosts(new AiCostDecision(ai, sa))) { + game.getStack().addAndUnfreeze(sa); + if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty()) { + game.getAction().reveal(sa.getSplicedCards(), ai, true, "Computer reveals spliced cards from "); + } + return true; + // TODO: solve problems with TapsForMana triggers by adding + // sources tapped here if possible (ArsenalNut) + } + } + //Should not arrive here + System.out.println("AI failed to play " + sa.getSourceCard()); + return false; + } + + + private static boolean hasDiscardHandCost(final Cost cost) { + if (cost == null) { + return false; + } + for (final CostPart part : cost.getCostParts()) { + if (part instanceof CostDiscard) { + final CostDiscard disc = (CostDiscard) part; + if (disc.getType().equals("Hand")) { + return true; + } + } + } + return false; + } + /** + *

+ * counterSpellRestriction. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a int. + */ + public static int counterSpellRestriction(final Player ai, final SpellAbility sa) { + // Move this to AF? + // Restriction Level is Based off a handful of factors + + int restrict = 0; + + final Card source = sa.getSourceCard(); + final TargetRestrictions tgt = sa.getTargetRestrictions(); + + + // Play higher costing spells first? + final Cost cost = sa.getPayCosts(); + // Convert cost to CMC + // String totalMana = source.getSVar("PayX"); // + cost.getCMC() + + // Consider the costs here for relative "scoring" + if (hasDiscardHandCost(cost)) { + // Null Brooch aid + restrict -= (ai.getCardsIn(ZoneType.Hand).size() * 20); + } + + // Abilities before Spells (card advantage) + if (sa.isAbility()) { + restrict += 40; + } + + // TargetValidTargeting gets biggest bonus + if (tgt.getSAValidTargeting() != null) { + restrict += 35; + } + + // Unless Cost gets significant bonus + 10-Payment Amount + final String unless = sa.getParam("UnlessCost"); + if (unless != null && !unless.endsWith(">")) { + final int amount = AbilityUtils.calculateAmount(source, unless, sa); + + final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size(); + + // If the Unless isn't enough, this should be less likely to be used + if (amount > usableManaSources) { + restrict += 20 - (2 * amount); + } else { + restrict -= (10 - (2 * amount)); + } + } + + // Then base on Targeting Restriction + final String[] validTgts = tgt.getValidTgts(); + if ((validTgts.length != 1) || !validTgts[0].equals("Card")) { + restrict += 10; + } + + // And lastly give some bonus points to least restrictive TargetType + // (Spell,Ability,Triggered) + final String tgtType = sa.getParam("TargetType"); + if (tgtType != null) + restrict -= (5 * tgtType.split(",").length); + + return restrict; + } + + // this is used for AI's counterspells + /** + *

+ * playStack. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + */ + public static final void playStack(final SpellAbility sa, final Player ai, final Game game) { + sa.setActivatingPlayer(ai); + if (!ComputerUtilCost.canPayCost(sa, ai)) + return; + + final Card source = sa.getSourceCard(); + if (sa.isSpell() && !source.isCopiedSpell()) { + sa.setSourceCard(game.getAction().moveToStack(source)); + } + final Cost cost = sa.getPayCosts(); + if (cost == null) { + ComputerUtilMana.payManaCost(ai, sa); + game.getStack().add(sa); + } else { + final CostPayment pay = new CostPayment(cost, sa); + if (pay.payComputerCosts(new AiCostDecision(ai, sa))) { + game.getStack().add(sa); + } + } + } + + /** + *

+ * playStackFree. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + */ + public static final void playSpellAbilityForFree(final Player ai, final SpellAbility sa) { + sa.setActivatingPlayer(ai); + + final Card source = sa.getSourceCard(); + if (sa.isSpell() && !source.isCopiedSpell()) { + sa.setSourceCard(ai.getGame().getAction().moveToStack(source)); + } + + ai.getGame().getStack().add(sa); + } + + /** + *

+ * playSpellAbilityWithoutPayingManaCost. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + */ + public static final void playSpellAbilityWithoutPayingManaCost(final Player ai, final SpellAbility sa, final Game game) { + final SpellAbility newSA = sa.copyWithNoManaCost(); + newSA.setActivatingPlayer(ai); + + if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) { + return; + } + + final Card source = newSA.getSourceCard(); + if (newSA.isSpell() && !source.isCopiedSpell()) { + newSA.setSourceCard(game.getAction().moveToStack(source)); + } + + final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA); + pay.payComputerCosts(new AiCostDecision(ai, sa)); + + game.getStack().add(newSA); + } + + /** + *

+ * playNoStack. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + */ + public static final void playNoStack(final Player ai, final SpellAbility sa, final Game game) { + sa.setActivatingPlayer(ai); + // TODO: We should really restrict what doesn't use the Stack + if (ComputerUtilCost.canPayCost(sa, ai)) { + final Card source = sa.getSourceCard(); + if (sa.isSpell() && !source.isCopiedSpell()) { + sa.setSourceCard(game.getAction().moveToStack(source)); + } + + final Cost cost = sa.getPayCosts(); + if (cost == null) { + ComputerUtilMana.payManaCost(ai, sa); + } else { + final CostPayment pay = new CostPayment(cost, sa); + pay.payComputerCosts(new AiCostDecision(ai, sa)); + } + + AbilityUtils.resolve(sa); + + // destroys creatures if they have lethal damage, etc.. + //game.getAction().checkStateEffects(); + } + } // play() + + /** + *

+ * getCardPreference. + *

+ * + * @param activate + * a {@link forge.game.card.Card} object. + * @param pref + * a {@link java.lang.String} object. + * @param typeList + * a {@link forge.CardList} object. + * @return a {@link forge.game.card.Card} object. + */ + public static Card getCardPreference(final Player ai, final Card activate, final String pref, final List typeList) { + + if (activate != null) { + final String[] prefValid = activate.getSVar("AIPreference").split("\\$"); + if (prefValid[0].equals(pref)) { + final List prefList = CardLists.getValidCards(typeList, prefValid[1].split(","), activate.getController(), activate); + if (prefList.size() != 0) { + CardLists.shuffle(prefList); + return prefList.get(0); + } + } + } + if (pref.contains("SacCost")) { + // search for permanents with SacMe. priority 1 is the lowest, priority 5 the highest + for (int ip = 0; ip < 6; ip++) { + final int priority = 6 - ip; + final List sacMeList = CardLists.filter(typeList, new Predicate() { + @Override + public boolean apply(final Card c) { + return (c.hasSVar("SacMe") && (Integer.parseInt(c.getSVar("SacMe")) == priority)); + } + }); + if (!sacMeList.isEmpty()) { + CardLists.shuffle(sacMeList); + return sacMeList.get(0); + } + } + + // Sac lands + final List landsInPlay = CardLists.getType(typeList, "Land"); + if (!landsInPlay.isEmpty()) { + final List landsInHand = CardLists.getType(ai.getCardsIn(ZoneType.Hand), "Land"); + final List nonLandsInHand = CardLists.getNotType(ai.getCardsIn(ZoneType.Hand), "Land"); + nonLandsInHand.addAll(ai.getCardsIn(ZoneType.Library)); + final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, CardPredicates.Accessors.fnGetCmc)); + if (landsInPlay.size() + landsInHand.size() >= highestCMC) { + // Don't need more land. + return ComputerUtilCard.getWorstLand(landsInPlay); + } + } + } + + else if (pref.contains("DiscardCost")) { // search for permanents with + // DiscardMe + for (int ip = 0; ip < 6; ip++) { // priority 0 is the lowest, + // priority 5 the highest + final int priority = 6 - ip; + for (Card c : typeList) { + if (priority == 3 && c.isLand() + && !ai.getCardsIn(ZoneType.Battlefield, "Crucible of Worlds").isEmpty()) { + return c; + } + if (c.hasSVar("DiscardMe") && Integer.parseInt(c.getSVar("DiscardMe")) == priority) { + return c; + } + } + } + + // Discard lands + final List landsInHand = CardLists.getType(typeList, "Land"); + if (!landsInHand.isEmpty()) { + final List landsInPlay = CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Land"); + final List nonLandsInHand = CardLists.getNotType(ai.getCardsIn(ZoneType.Hand), "Land"); + final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, CardPredicates.Accessors.fnGetCmc)); + if (landsInPlay.size() >= highestCMC + || (landsInPlay.size() + landsInHand.size() > 6 && landsInHand.size() > 1)) { + // Don't need more land. + return ComputerUtilCard.getWorstLand(landsInHand); + } + } + } + + return null; + } + + /** + *

+ * chooseSacrificeType. + *

+ * + * @param type + * a {@link java.lang.String} object. + * @param source + * a {@link forge.game.card.Card} object. + * @param target + * a {@link forge.game.card.Card} object. + * @param amount + * a int. + * @return a {@link forge.CardList} object. + */ + public static List chooseSacrificeType(final Player ai, final String type, final Card source, final Card target, final int amount) { + List typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source); + if (ai.hasKeyword("You can't sacrifice creatures to cast spells or activate abilities.")) { + typeList = CardLists.getNotType(typeList, "Creature"); + } + + if ((target != null) && target.getController() == ai && typeList.contains(target)) { + typeList.remove(target); // don't sacrifice the card we're pumping + } + + if (typeList.size() < amount) { + return null; + } + + final List sacList = new ArrayList(); + int count = 0; + + while (count < amount) { + Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList); + if (prefCard == null) { + prefCard = ComputerUtilCard.getWorstAI(typeList); + } + if (prefCard == null) { + return null; + } + sacList.add(prefCard); + typeList.remove(prefCard); + count++; + } + return sacList; + } + + /** + *

+ * chooseExileFrom. + *

+ * + * @param zone + * a {@link java.lang.String} object. + * @param type + * a {@link java.lang.String} object. + * @param activate + * a {@link forge.game.card.Card} object. + * @param target + * a {@link forge.game.card.Card} object. + * @param amount + * a int. + * @return a {@link forge.CardList} object. + */ + public static List chooseExileFrom(final Player ai, final ZoneType zone, final String type, final Card activate, + final Card target, final int amount) { + final Game game = ai.getGame(); + List typeList = new ArrayList(); + if (zone.equals(ZoneType.Stack)) { + for (SpellAbilityStackInstance si : game.getStack()) { + typeList.add(si.getSourceCard()); + } + } else { + typeList = ai.getCardsIn(zone); + } + typeList = CardLists.getValidCards(typeList, type.split(";"), activate.getController(), activate); + + if ((target != null) && target.getController() == ai && typeList.contains(target)) { + typeList.remove(target); // don't exile the card we're pumping + } + + if (typeList.size() < amount) { + return null; + } + + CardLists.sortByPowerAsc(typeList); + final List exileList = new ArrayList(); + + for (int i = 0; i < amount; i++) { + exileList.add(typeList.get(i)); + } + return exileList; + } + + /** + *

+ * choosePutToLibraryFrom. + *

+ * + * @param zone + * a {@link java.lang.String} object. + * @param type + * a {@link java.lang.String} object. + * @param activate + * a {@link forge.game.card.Card} object. + * @param target + * a {@link forge.game.card.Card} object. + * @param amount + * a int. + * @return a {@link forge.CardList} object. + */ + public static List choosePutToLibraryFrom(final Player ai, final ZoneType zone, final String type, final Card activate, + final Card target, final int amount) { + List typeList = ai.getCardsIn(zone); + + typeList = CardLists.getValidCards(typeList, type.split(";"), activate.getController(), activate); + + if ((target != null) && target.getController() == ai && typeList.contains(target)) { + typeList.remove(target); // don't move the card we're pumping + } + + if (typeList.size() < amount) { + return null; + } + + CardLists.sortByPowerAsc(typeList); + final List list = new ArrayList(); + + if (zone != ZoneType.Hand) { + Collections.reverse(typeList); + } + + for (int i = 0; i < amount; i++) { + list.add(typeList.get(i)); + } + + return list; + } + + /** + *

+ * chooseTapType. + *

+ * + * @param type + * a {@link java.lang.String} object. + * @param activate + * a {@link forge.game.card.Card} object. + * @param tap + * a boolean. + * @param amount + * a int. + * @return a {@link forge.CardList} object. + */ + public static List chooseTapType(final Player ai, final String type, final Card activate, final boolean tap, final int amount) { + List typeList = + CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(","), activate.getController(), activate); + + // is this needed? + typeList = CardLists.filter(typeList, Presets.UNTAPPED); + + if (tap) { + typeList.remove(activate); + } + + if (typeList.size() < amount) { + return null; + } + + CardLists.sortByPowerAsc(typeList); + + final List tapList = new ArrayList(); + + for (int i = 0; i < amount; i++) { + tapList.add(typeList.get(i)); + } + return tapList; + } + + /** + *

+ * chooseUntapType. + *

+ * + * @param type + * a {@link java.lang.String} object. + * @param activate + * a {@link forge.game.card.Card} object. + * @param untap + * a boolean. + * @param amount + * a int. + * @return a {@link forge.CardList} object. + */ + public static List chooseUntapType(final Player ai, final String type, final Card activate, final boolean untap, final int amount) { + List typeList = + CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(","), activate.getController(), activate); + + // is this needed? + typeList = CardLists.filter(typeList, Presets.TAPPED); + + if (untap) { + typeList.remove(activate); + } + + if (typeList.size() < amount) { + return null; + } + + CardLists.sortByPowerDesc(typeList); + + final List untapList = new ArrayList(); + + for (int i = 0; i < amount; i++) { + untapList.add(typeList.get(i)); + } + return untapList; + } + + /** + *

+ * chooseReturnType. + *

+ * + * @param type + * a {@link java.lang.String} object. + * @param activate + * a {@link forge.game.card.Card} object. + * @param target + * a {@link forge.game.card.Card} object. + * @param amount + * a int. + * @return a {@link forge.CardList} object. + */ + public static List chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount) { + final List typeList = + CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate); + if ((target != null) && target.getController() == ai && typeList.contains(target)) { + // don't bounce the card we're pumping + typeList.remove(target); + } + + if (typeList.size() < amount) { + return null; + } + + CardLists.sortByPowerAsc(typeList); + final List returnList = new ArrayList(); + + for (int i = 0; i < amount; i++) { + returnList.add(typeList.get(i)); + } + return returnList; + } + + /** + *

+ * sacrificePermanents. + *

+ * @param amount + * a int. + * @param source + * the source SpellAbility + * @param destroy + * the destroy + * @param list + * a {@link forge.CardList} object. + * + * @return the card list + */ + public static List choosePermanentsToSacrifice(final Player ai, final List cardlist, final int amount, SpellAbility source, + final boolean destroy, final boolean isOptional) { + List remaining = new ArrayList(cardlist); + final List sacrificed = new ArrayList(); + + if (isOptional && source.getActivatingPlayer().isOpponentOf(ai)) { + return sacrificed; // sacrifice none + } + if (isOptional && source.hasParam("Devour")) { + remaining = CardLists.filter(remaining, new Predicate() { + @Override + public boolean apply(final Card c) { + if ((c.getCMC() <= 1 && c.getNetAttack() < 3) + || c.getNetAttack() + c.getNetDefense() <= 3) { + return true; + } + return false; + } + }); + } + CardLists.sortByCmcDesc(remaining); + Collections.reverse(remaining); + + final int max = Math.min(remaining.size(), amount); + + for (int i = 0; i < max; i++) { + Card c = chooseCardToSacrifice(remaining, ai, destroy); + remaining.remove(c); + sacrificed.add(c); + } + return sacrificed; + } + + // Precondition it wants: remaining are reverse-sorted by CMC + private static Card chooseCardToSacrifice(final List remaining, final Player ai, final boolean destroy) { + // If somehow ("Drop of Honey") they suggest to destroy opponent's card - use the chance! + for(Card c : remaining) { // first compare is fast, second is precise + if (c.getController() != ai && ai.getOpponents().contains(c.getController()) ) + return c; + } + + if (destroy) { + final List indestructibles = CardLists.getKeyword(remaining, "Indestructible"); + if (!indestructibles.isEmpty()) { + return indestructibles.get(0); + } + } + for (int ip = 0; ip < 6; ip++) { // priority 0 is the lowest, priority 5 the highest + final int priority = 6 - ip; + for (Card card : remaining) { + if (card.hasSVar("SacMe") && Integer.parseInt(card.getSVar("SacMe")) == priority) { + return card; + } + } + } + + Card c; + + if (CardLists.getNotType(remaining, "Creature").size() == 0) { + c = ComputerUtilCard.getWorstCreatureAI(remaining); + } else if (CardLists.getNotType(remaining, "Land").size() == 0) { + c = ComputerUtilCard.getWorstLand(CardLists.filter(remaining, CardPredicates.Presets.LANDS)); + } else { + c = ComputerUtilCard.getWorstPermanentAI(remaining, false, false, false, false); + } + + final ArrayList auras = c.getEnchantedBy(); + + if (auras.size() > 0) { + // TODO: choose "worst" controlled enchanting Aura + for (int j = 0; j < auras.size(); j++) { + final Card aura = auras.get(j); + if (aura.getController().equals(c.getController()) && remaining.contains(aura)) { + return aura; + } + } + } + return c; + } + + /** + *

+ * canRegenerate. + *

+ * @param ai + * + * @param card + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public static boolean canRegenerate(Player ai, final Card card) { + + if (card.hasKeyword("CARDNAME can't be regenerated.")) { + return false; + } + + final Player controller = card.getController(); + final Game game = controller.getGame(); + final List l = controller.getCardsIn(ZoneType.Battlefield); + for (final Card c : l) { + for (final SpellAbility sa : c.getSpellAbilities()) { + // This try/catch should fix the "computer is thinking" bug + try { + + if (!sa.isAbility() || sa.getApi() != ApiType.Regenerate) { + continue; // Not a Regenerate ability + } + sa.setActivatingPlayer(controller); + if (!(sa.canPlay() && ComputerUtilCost.canPayCost(sa, controller))) { + continue; // Can't play ability + } + + if (controller == ai) { + final Cost abCost = sa.getPayCosts(); + if (abCost != null) { + if (!ComputerUtilCost.checkLifeCost(controller, abCost, c, 4, null)) { + continue; // Won't play ability + } + + if (!ComputerUtilCost.checkSacrificeCost(controller, abCost, c)) { + continue; // Won't play ability + } + + if (!ComputerUtilCost.checkCreatureSacrificeCost(controller, abCost, c)) { + continue; // Won't play ability + } + } + } + + final TargetRestrictions tgt = sa.getTargetRestrictions(); + if (tgt != null) { + if (CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), controller, sa.getSourceCard()).contains(card)) { + return true; + } + } else if (AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa).contains(card)) { + return true; + } + + } catch (final Exception ex) { + BugReporter.reportException(ex, "There is an error in the card code for %s:%n", c.getName(), ex.getMessage()); + } + } + } + return false; + } + + /** + *

+ * possibleDamagePrevention. + *

+ * + * @param card + * a {@link forge.game.card.Card} object. + * @return a int. + */ + public static int possibleDamagePrevention(final Card card) { + + int prevented = 0; + + final Player controller = card.getController(); + final Game game = controller.getGame(); + + final List l = controller.getCardsIn(ZoneType.Battlefield); + for (final Card c : l) { + for (final SpellAbility sa : c.getSpellAbilities()) { + // if SA is from AF_Counter don't add to getPlayable + // This try/catch should fix the "computer is thinking" bug + try { + if (sa.getApi() == null || !sa.isAbility()) { + continue; + } + + if (sa.getApi() == ApiType.PreventDamage && sa.canPlay() + && ComputerUtilCost.canPayCost(sa, controller)) { + if (AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa).contains(card)) { + prevented += AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("Amount"), sa); + } + final TargetRestrictions tgt = sa.getTargetRestrictions(); + if (tgt != null) { + if (CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), controller, sa.getSourceCard()).contains(card)) { + prevented += AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("Amount"), sa); + } + + } + } + } catch (final Exception ex) { + BugReporter.reportException(ex, "There is an error in the card code for %s:%n", c.getName(), + ex.getMessage()); + } + } + } + return prevented; + } + + /** + *

+ * castPermanentInMain1. + *

+ * + * @param sa + * a SpellAbility object. + * @return a boolean. + */ + public static boolean castPermanentInMain1(final Player ai, final SpellAbility sa) { + final Card card = sa.getSourceCard(); + if ("True".equals(card.getSVar("NonStackingEffect")) && card.getController().isCardInPlay(card.getName())) { + return false; + } + if (card.getSVar("PlayMain1").equals("TRUE") && (!card.getController().getCreaturesInPlay().isEmpty() || sa.getPayCosts().hasNoManaCost())) { + return true; + } + if ((card.isCreature() && (ComputerUtil.hasACardGivingHaste(ai) + || card.hasKeyword("Haste"))) || card.hasKeyword("Exalted")) { + return true; + } + //cast equipments in Main1 when there are creatures to equip and no other unequipped equipment + if (card.isEquipment()) { + boolean playNow = false; + for (Card c : card.getController().getCardsIn(ZoneType.Battlefield)) { + if (c.isEquipment() && !c.isEquipping()) { + playNow = false; + break; + } + if (!playNow && c.isCreature() && CombatUtil.canAttackNextTurn(c) && c.canBeEquippedBy(card)) { + playNow = true; + } + } + if (playNow) { + return true; + } + } + + // get all cards the computer controls with BuffedBy + final List buffed = ai.getCardsIn(ZoneType.Battlefield); + for (Card buffedcard : buffed) { + if (buffedcard.hasSVar("BuffedBy")) { + final String buffedby = buffedcard.getSVar("BuffedBy"); + final String[] bffdby = buffedby.split(","); + if (card.isValid(bffdby, buffedcard.getController(), buffedcard)) { + return true; + } + } + if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ai.getOpponent())) { + return true; + } + if (card.isCreature()) { + if (buffedcard.hasKeyword("Soulbond") && !buffedcard.isPaired()) { + return true; + } + if (buffedcard.hasKeyword("Evolve")) { + if (buffedcard.getNetAttack() < card.getNetAttack() || buffedcard.getNetDefense() < card.getNetDefense()) { + return true; + } + } + } + if (card.hasKeyword("Soulbond") && buffedcard.isCreature() && !buffedcard.isPaired()) { + return true; + } + + } // BuffedBy + + // get all cards the human controls with AntiBuffedBy + final List antibuffed = ai.getOpponent().getCardsIn(ZoneType.Battlefield); + for (Card buffedcard : antibuffed) { + if (buffedcard.hasSVar("AntiBuffedBy")) { + final String buffedby = buffedcard.getSVar("AntiBuffedBy"); + final String[] bffdby = buffedby.split(","); + if (card.isValid(bffdby, buffedcard.getController(), buffedcard)) { + return true; + } + } + } // AntiBuffedBy + final List vengevines = ai.getCardsIn(ZoneType.Graveyard, "Vengevine"); + if (!vengevines.isEmpty()) { + final List creatures = ai.getCardsIn(ZoneType.Hand); + final List creatures2 = new ArrayList(); + for (int i = 0; i < creatures.size(); i++) { + if (creatures.get(i).isCreature() && creatures.get(i).getManaCost().getCMC() <= 3) { + creatures2.add(creatures.get(i)); + } + } + if (((creatures2.size() + CardUtil.getThisTurnCast("Creature.YouCtrl", vengevines.get(0)).size()) > 1) + && card.isCreature() && card.getManaCost().getCMC() <= 3) { + return true; + } + } + return false; + } + + /** + * Is it OK to cast this for less than the Max Targets? + * @param source the source Card + * @return true if it's OK to cast this Card for less than the max targets + */ + public static boolean shouldCastLessThanMax(final Player ai, final Card source) { + boolean ret = true; + if (source.getManaCost().countX() > 0) { + // If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available. + return ret; + } else { + // Otherwise, if life is possibly in danger, then this is fine. + Combat combat = new Combat(ai.getOpponent()); + List attackers = ai.getOpponent().getCreaturesInPlay(); + for (Card att : attackers) { + if (CombatUtil.canAttackNextTurn(att, ai)) { + combat.addAttacker(att, att.getController().getOpponent()); + } + } + AiBlockController aiBlock = new AiBlockController(ai); + aiBlock.assignBlockers(combat); + if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { + // Otherwise, return false. Do not play now. + ret = false; + } + } + return ret; + } + + /** + * Is this discard probably worse than a random draw? + * @param discard Card to discard + * @return boolean + */ + public static boolean isWorseThanDraw(final Player ai, Card discard) { + if (discard.hasSVar("DiscardMe")) { + return true; + } + + final Game game = ai.getGame(); + final List landsInPlay = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS); + final List landsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS); + final List nonLandsInHand = CardLists.getNotType(ai.getCardsIn(ZoneType.Hand), "Land"); + final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, CardPredicates.Accessors.fnGetCmc)); + final int discardCMC = discard.getCMC(); + if (discard.isLand()) { + if (landsInPlay.size() >= highestCMC + || (landsInPlay.size() + landsInHand.size() > 6 && landsInHand.size() > 1) + || (landsInPlay.size() > 3 && nonLandsInHand.size() == 0)) { + // Don't need more land. + return true; + } + } else { //non-land + if (discardCMC > landsInPlay.size() + landsInHand.size() + 2) { + // not castable for some time. + return true; + } else if (!game.getPhaseHandler().isPlayerTurn(ai) + && game.getPhaseHandler().getPhase().isAfter(PhaseType.MAIN2) + && discardCMC > landsInPlay.size() + landsInHand.size() + && discardCMC > landsInPlay.size() + 1 + && nonLandsInHand.size() > 1) { + // not castable for at least one other turn. + return true; + } else if (landsInPlay.size() > 5 && discard.getCMC() <= 1 + && !discard.hasProperty("hasXCost", ai, null)) { + // Probably don't need small stuff now. + return true; + } + } + return false; + } + + // returns true if it's better to wait until blockers are declared + /** + *

+ * waitForBlocking. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean (returns true if it's better to wait until blockers are declared). + */ + public static boolean waitForBlocking(final SpellAbility sa) { + final Game game = sa.getActivatingPlayer().getGame(); + final PhaseHandler ph = game.getPhaseHandler(); + + return (sa.getSourceCard().isCreature() + && sa.getPayCosts().hasTapCost() + && (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) + || !ph.getNextTurn().equals(sa.getActivatingPlayer())) + && !sa.getSourceCard().hasKeyword("At the beginning of the end step, exile CARDNAME.") + && !sa.getSourceCard().hasKeyword("At the beginning of the end step, sacrifice CARDNAME.")); + } + + /** + *

+ * castSpellMain1. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean (returns true if it's better to wait until blockers are declared). + */ + public static boolean castSpellInMain1(final Player ai, final SpellAbility sa) { + final Card source = sa.getSourceCard(); + final SpellAbility sub = sa.getSubAbility(); + + // Cipher spells + if (sub != null) { + final ApiType api = sub.getApi(); + if (ApiType.Encode == api && !ai.getCreaturesInPlay().isEmpty()) { + return true; + } + if (ApiType.PumpAll == api && !ai.getCreaturesInPlay().isEmpty()) { + return true; + } + if (ApiType.Pump == api) { + return true; + } + } + final List buffed = ai.getCardsIn(ZoneType.Battlefield); + boolean checkThreshold = sa.isSpell() && !ai.hasThreshold() && !sa.getSourceCard().isInZone(ZoneType.Graveyard); + for (Card buffedCard : buffed) { + if (buffedCard.hasSVar("BuffedBy")) { + final String buffedby = buffedCard.getSVar("BuffedBy"); + final String[] bffdby = buffedby.split(","); + if (source.isValid(bffdby, buffedCard.getController(), buffedCard)) { + return true; + } + } + //Fill the graveyard for Threshold + if (checkThreshold) { + for (StaticAbility stAb : buffedCard.getStaticAbilities()) { + if ("Threshold".equals(stAb.getMapParams().get("Condition"))) { + return true; + } + } + } + } + + // get all cards the human controls with AntiBuffedBy + final List antibuffed = ai.getOpponent().getCardsIn(ZoneType.Battlefield); + for (Card buffedcard : antibuffed) { + if (buffedcard.hasSVar("AntiBuffedBy")) { + final String buffedby = buffedcard.getSVar("AntiBuffedBy"); + final String[] bffdby = buffedby.split(","); + if (source.isValid(bffdby, buffedcard.getController(), buffedcard)) { + return true; + } + } + } // AntiBuffedBy + + if (sub != null) { + return castSpellInMain1(ai, sub); + } + + return false; + } + + // returns true if the AI should stop using the ability + /** + *

+ * preventRunAwayActivations. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean (returns true if the AI should stop using the ability). + */ + public static boolean preventRunAwayActivations(final SpellAbility sa) { + int activations = sa.getRestrictions().getNumberTurnActivations(); + + if (sa.isTemporary()) { + final Random r = MyRandom.getRandom(); + return r.nextFloat() >= .95; // Abilities created by static abilities have no memory + } + + if (activations < 10) { //10 activations per turn should still be acceptable + return false; + } + + final Random r = MyRandom.getRandom(); + return r.nextFloat() >= Math.pow(.95, activations); + } + + /** + * TODO: Write javadoc for this method. + * @param sa + * @return + */ + public static boolean activateForCost(SpellAbility sa, final Player ai) { + final Cost abCost = sa.getPayCosts(); + final Card source = sa.getSourceCard(); + if (abCost == null) { + return false; + } + if (abCost.hasTapCost()) { + for (Card c : ai.getGame().getCardsIn(ZoneType.Battlefield)) { + if (c.hasSVar("AITapDown")) { + if (source.isValid(c.getSVar("AITapDown"), c.getController(), c)) { + return true; + } + } + } + } else if (sa.hasParam("Planeswalker") && ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) { + for (final CostPart part : abCost.getCostParts()) { + if (part instanceof CostPutCounter) { + return true; + } + } + } + for (final CostPart part : abCost.getCostParts()) { + if (part instanceof CostSacrifice) { + final CostSacrifice sac = (CostSacrifice) part; + + final String type = sac.getType(); + + if (type.equals("CARDNAME")) { + if (source.getSVar("SacMe").equals("6")) { + return true; + } + continue; + } + + final List typeList = + CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(","), source.getController(), source); + for (Card c : typeList) { + if (c.getSVar("SacMe").equals("6")) { + return true; + } + } + } + } + return false; + } + + /** + *

+ * hasACardGivingHaste. + *

+ * + * @return a boolean. + */ + public static boolean hasACardGivingHaste(final Player ai) { + final List all = new ArrayList(ai.getCardsIn(ZoneType.Battlefield)); + + for (final Card c : all) { + if (c.isEquipment()) { + for (StaticAbility stAb : c.getStaticAbilities()) { + HashMap params = stAb.getMapParams(); + if ("Continuous".equals(params.get("Mode")) && params.containsKey("AddKeyword") + && params.get("AddKeyword").contains("Haste") && c.getEquippingCard() == null) { + return true; + } + } + } + } + + all.addAll(ai.getCardsActivableInExternalZones()); + all.addAll(ai.getCardsIn(ZoneType.Hand)); + + for (final Card c : all) { + for (final SpellAbility sa : c.getSpellAbilities()) { + if (sa.getApi() == ApiType.Pump && sa.hasParam("KW") && sa.getParam("KW").contains("Haste")) { + return true; + } + } + } + return false; + } // hasACardGivingHaste + + /** + * TODO: Write javadoc for this method. + * @param ai + * @return + */ + public static int possibleNonCombatDamage(Player ai) { + int damage = 0; + final List all = new ArrayList(ai.getCardsIn(ZoneType.Battlefield)); + all.addAll(ai.getCardsActivableInExternalZones()); + all.addAll(ai.getCardsIn(ZoneType.Hand)); + + for (final Card c : all) { + for (final SpellAbility sa : c.getSpellAbilities()) { + if (sa.getApi() != ApiType.DealDamage) { + continue; + } + final String numDam = sa.getParam("NumDmg"); + int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), numDam, sa); + if (dmg <= damage) { + continue; + } + final TargetRestrictions tgt = sa.getTargetRestrictions(); + if (tgt == null) { + continue; + } + final Player enemy = ai.getOpponent(); + if (!sa.canTarget(enemy)) { + continue; + } + if (!ComputerUtilCost.canPayCost(sa, ai)) { + continue; + } + damage = dmg; + } + } + return damage; + } + + /** + *

+ * predictThreatenedObjects. + *

+ * + * @param saviourAf + * a AbilityFactory object + * @return a {@link java.util.ArrayList} object. + * @since 1.0.15 + */ + public static List predictThreatenedObjects(final Player aiPlayer, final SpellAbility sa) { + final Game game = aiPlayer.getGame(); + final List objects = new ArrayList(); + if (game.getStack().isEmpty()) { + return objects; + } + + // check stack for something that will kill this + final SpellAbility topStack = game.getStack().peekAbility(); + Iterables.addAll(objects, ComputerUtil.predictThreatenedObjects(aiPlayer, sa, topStack)); + + return objects; + } + + /** + *

+ * predictThreatenedObjects. + *

+ * + * @param saviourAf + * a AbilityFactory object + * @param topStack + * a {@link forge.game.spellability.SpellAbility} object. + * @return a {@link java.util.ArrayList} object. + * @since 1.0.15 + */ + private static Iterable predictThreatenedObjects(final Player aiPlayer, final SpellAbility saviour, + final SpellAbility topStack) { + Iterable objects = new ArrayList(); + final List threatened = new ArrayList(); + ApiType saviourApi = saviour == null ? null : saviour.getApi(); + + if (topStack == null) { + return objects; + } + + final Card source = topStack.getSourceCard(); + final ApiType threatApi = topStack.getApi(); + + // Can only Predict things from AFs + if (threatApi == null) { + return threatened; + } + final TargetRestrictions tgt = topStack.getTargetRestrictions(); + + if (tgt == null) { + if (topStack.hasParam("Defined")) { + objects = AbilityUtils.getDefinedObjects(source, topStack.getParam("Defined"), topStack); + } else if (topStack.hasParam("ValidCards")) { + List battleField = aiPlayer.getCardsIn(ZoneType.Battlefield); + objects = CardLists.getValidCards(battleField, topStack.getParam("ValidCards").split(","), source.getController(), source); + } + } else { + objects = topStack.getTargets().getTargets(); + } + + // Determine if Defined Objects are "threatened" will be destroyed + // due to this SA + + // Lethal Damage => prevent damage/regeneration/bounce/shroud + if (threatApi == ApiType.DealDamage || threatApi == ApiType.DamageAll) { + // If PredictDamage is >= Lethal Damage + final int dmg = AbilityUtils.calculateAmount(topStack.getSourceCard(), + topStack.getParam("NumDmg"), topStack); + for (final Object o : objects) { + if (o instanceof Card) { + final Card c = (Card) o; + + // indestructible + if (c.hasKeyword("Indestructible")) { + continue; + } + + // already regenerated + if (!c.getShield().isEmpty()) { + continue; + } + + // don't use it on creatures that can't be regenerated + if ((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll) && !c.canBeShielded()) { + continue; + } + + // give Shroud to targeted creatures + if (saviourApi == ApiType.Pump && tgt == null && saviour.hasParam("KW") + && (saviour.getParam("KW").endsWith("Shroud") + || saviour.getParam("KW").endsWith("Hexproof"))) { + continue; + } + + // don't bounce or blink a permanent that the human + // player owns or is a token + if (saviourApi == ApiType.ChangeZone && (c.getOwner().isOpponentOf(aiPlayer) || c.isToken())) { + continue; + } + + if (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c)) { + threatened.add(c); + } + } else if (o instanceof Player) { + final Player p = (Player) o; + + if (source.hasKeyword("Infect")) { + if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getPoisonCounters()) { + threatened.add(p); + } + } else if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getLife()) { + threatened.add(p); + } + } + } + } + // Destroy => regeneration/bounce/shroud + else if ((threatApi == ApiType.Destroy || threatApi == ApiType.DestroyAll) + && (((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll) + && !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump + || saviourApi == null)) { + for (final Object o : objects) { + if (o instanceof Card) { + final Card c = (Card) o; + // indestructible + if (c.hasKeyword("Indestructible")) { + continue; + } + + // already regenerated + if (!c.getShield().isEmpty()) { + continue; + } + + // give Shroud to targeted creatures + if (saviourApi == ApiType.Pump && tgt == null && saviour.hasParam("KW") + && (saviour.getParam("KW").endsWith("Shroud") + || saviour.getParam("KW").endsWith("Hexproof"))) { + continue; + } + + // don't bounce or blink a permanent that the human + // player owns or is a token + if (saviourApi == ApiType.ChangeZone && (c.getOwner().isOpponentOf(aiPlayer) || c.isToken())) { + continue; + } + + // don't use it on creatures that can't be regenerated + if (saviourApi == ApiType.Regenerate && !c.canBeShielded()) { + continue; + } + threatened.add(c); + } + } + } + // Exiling => bounce/shroud + else if ((threatApi == ApiType.ChangeZone || threatApi == ApiType.ChangeZoneAll) + && (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == null) + && topStack.hasParam("Destination") + && topStack.getParam("Destination").equals("Exile")) { + for (final Object o : objects) { + if (o instanceof Card) { + final Card c = (Card) o; + // give Shroud to targeted creatures + if (saviourApi == ApiType.Pump && tgt == null && saviour.hasParam("KW") + && (saviour.getParam("KW").endsWith("Shroud") || saviour.getParam("KW").endsWith("Hexproof"))) { + continue; + } + + // don't bounce or blink a permanent that the human + // player owns or is a token + if (saviourApi == ApiType.ChangeZone && (c.getOwner().isOpponentOf(aiPlayer) || c.isToken())) { + continue; + } + + threatened.add(c); + } + } + } + + Iterables.addAll(threatened, ComputerUtil.predictThreatenedObjects(aiPlayer, saviour, topStack.getSubAbility())); + return threatened; + } + + public static boolean playImmediately(Player ai, SpellAbility sa) { + final Card source = sa.getSourceCard(); + final Zone zone = source.getZone(); + + if (zone.getZoneType() == ZoneType.Battlefield) { + if (predictThreatenedObjects(ai, null).contains(source)) { + return true; + } + } + return false; + } + + // Computer mulligans if there are no cards with converted mana cost of + // 0 in its hand + public static boolean wantMulligan(Player ai) { + final List handList = ai.getCardsIn(ZoneType.Hand); + final boolean hasLittleCmc0Cards = CardLists.getValidCards(handList, "Card.cmcEQ0", ai, null).size() < 2; + final AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); + return (handList.size() > aic.getIntProperty(AiProps.MULLIGAN_THRESHOLD)) && hasLittleCmc0Cards; + } + + public static List getPartialParisCandidates(Player ai) { + final List candidates = new ArrayList(); + final List handList = ai.getCardsIn(ZoneType.Hand); + + final List lands = CardLists.getValidCards(handList, "Card.Land", ai, null); + final List nonLands = CardLists.getValidCards(handList, "Card.nonLand", ai, null); + CardLists.sortByCmcDesc(nonLands); + + if(lands.size() >= 3 && lands.size() <= 4) + return candidates; + + if(lands.size() < 3) + { + //Not enough lands! + int tgtCandidates = Math.max(Math.abs(lands.size()-nonLands.size()), 3); + System.out.println("Partial Paris: " + ai.getName() + " lacks lands, aiming to exile " + tgtCandidates + " cards."); + + for(int i=0;i> numProducers = new ArrayList>(cntColors); + for(byte col : MagicColor.WUBRG) { + numProducers.add(col, new ArrayList()); + } + + + for(Card c : lands) + { + for(SpellAbility sa : c.getManaAbility()) + { + AbilityManaPart abmana = sa.getManaPart(); + for(byte col : MagicColor.WUBRG) { + if(abmana.canProduce(MagicColor.toLongString(col))) { + numProducers.get(col).add(c); + } + } + } + } + } + + System.out.print("Partial Paris: " + ai.getName() + " may exile "); + for(Card c : candidates) + { + System.out.print(c.toString() + ", "); + } + System.out.println(); + + if(candidates.size() < 2) + candidates.clear(); + return candidates; + } + + + public static boolean scryWillMoveCardToBottomOfLibrary(Player player, Card c) { + boolean bottom = false; + if (c.isBasicLand()) { + List bl = player.getCardsIn(ZoneType.Battlefield); + int nBasicLands = Iterables.size(Iterables.filter(bl, CardPredicates.Presets.LANDS)); + bottom = nBasicLands > 5; // if control more than 5 Basic land, probably don't need more + } else if (c.isCreature()) { + List cl = player.getCardsIn(ZoneType.Battlefield); + cl = CardLists.filter(cl, CardPredicates.Presets.CREATURES); + bottom = cl.size() > 5; // if control more than 5 Creatures, probably don't need more + } + return bottom; + } + + /** + * TODO: Write javadoc for this method. + * @param chooser + * @param discarder + * @param sa + * @param validCards + * @param min + * @return + */ + public static List getCardsToDiscardFromOpponent(Player chooser, Player discarder, SpellAbility sa, List validCards, int min, int max) { + List goodChoices = CardLists.filter(validCards, new Predicate() { + @Override + public boolean apply(final Card c) { + if (c.hasSVar("DiscardMeByOpp") || c.hasSVar("DiscardMe")) { + return false; + } + return true; + } + }); + if (goodChoices.isEmpty()) { + goodChoices = validCards; + } + final List dChoices = new ArrayList(); + if (sa.hasParam("DiscardValid")) { + final String validString = sa.getParam("DiscardValid"); + if (validString.contains("Creature") && !validString.contains("nonCreature")) { + final Card c = ComputerUtilCard.getBestCreatureAI(goodChoices); + if (c != null) { + dChoices.add(ComputerUtilCard.getBestCreatureAI(goodChoices)); + } + } + } + + Collections.sort(goodChoices, CardLists.TextLenComparator); + + CardLists.sortByCmcDesc(goodChoices); + dChoices.add(goodChoices.get(0)); + + return Aggregates.random(goodChoices, min); + } + + /** + * TODO: Write javadoc for this method. + * @param aiChoser + * @param p + * @param sa + * @param validCards + * @param min + * @return + */ + public static List getCardsToDiscardFromFriend(Player aiChooser, Player p, SpellAbility sa, List validCards, int min, int max) { + if (p == aiChooser) { // ask that ai player what he would like to discard + final AiController aic = ((PlayerControllerAi)p.getController()).getAi(); + return aic.getCardsToDiscard(min, max, validCards, sa); + } + // no special options for human or remote friends + return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min, max); + } + + public static String chooseSomeType(Player ai, String kindOfType, String logic, List invalidTypes) { + final Game game = ai.getGame(); + String chosen = ""; + if( kindOfType.equals("Card")) { + // TODO + // computer will need to choose a type + // based on whether it needs a creature or land, + // otherwise, lib search for most common type left + // then, reveal chosenType to Human + if (game.getPhaseHandler().is(PhaseType.UNTAP) && logic == null) { // Storage Matrix + double amount = 0; + for (String type : Constant.CARD_TYPES) { + if (!invalidTypes.contains(type)) { + List list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(type), Presets.TAPPED); + double i = type.equals("Creature") ? list.size() * 1.5 : list.size(); + if (i > amount) { + amount = i; + chosen = type; + } + } + } + } + if (StringUtils.isEmpty(chosen)) { + chosen = "Creature"; + } + } else if (kindOfType.equals("Creature")) { + Player opp = ai.getOpponent(); + if (logic != null ) { + if (logic.equals("MostProminentOnBattlefield")) { + chosen = ComputerUtilCard.getMostProminentCreatureType(game.getCardsIn(ZoneType.Battlefield)); + } + else if (logic.equals("MostProminentComputerControls")) { + chosen = ComputerUtilCard.getMostProminentCreatureType(ai.getCardsIn(ZoneType.Battlefield)); + } + else if (logic.equals("MostProminentHumanControls")) { + chosen = ComputerUtilCard.getMostProminentCreatureType(opp.getCardsIn(ZoneType.Battlefield)); + if (!CardType.isACreatureType(chosen) || invalidTypes.contains(chosen)) { + chosen = ComputerUtilCard.getMostProminentCreatureType(CardLists.filterControlledBy(game.getCardsInGame(), opp)); + } + } + else if (logic.equals("MostProminentInComputerDeck")) { + chosen = ComputerUtilCard.getMostProminentCreatureType(CardLists.filterControlledBy(game.getCardsInGame(), ai)); + } + else if (logic.equals("MostProminentInComputerGraveyard")) { + chosen = ComputerUtilCard.getMostProminentCreatureType(ai.getCardsIn(ZoneType.Graveyard)); + } + } + if (!CardType.isACreatureType(chosen) || invalidTypes.contains(chosen)) { + chosen = "Sliver"; + } + + } else if ( kindOfType.equals("Basic Land")) { + if (logic != null) { + if (logic.equals("MostNeededType")) { + // Choose a type that is in the deck, but not in hand or on the battlefield + final ArrayList basics = new ArrayList(); + basics.addAll(CardType.Constant.BASIC_TYPES); + List presentCards = ai.getCardsIn(ZoneType.Battlefield); + presentCards.addAll(ai.getCardsIn(ZoneType.Hand)); + List possibleCards = ai.getAllCards(); + + for (String b : basics) { + if(!Iterables.any(presentCards, CardPredicates.isType(b)) && Iterables.any(possibleCards, CardPredicates.isType(b))) { + chosen = b; + } + } + if (chosen.equals("")) { + for (String b : basics) { + if(Iterables.any(possibleCards, CardPredicates.isType(b))) { + chosen = b; + } + } + } + } else if (logic.equals("ChosenLandwalk")) { + for (Card c : ai.getOpponent().getLandsInPlay()) { + for (String t : c.getType()) { + if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) { + chosen = t; + break; + } + } + } + } + } + + if (!CardType.isABasicLandType(chosen) || invalidTypes.contains(chosen)) { + chosen = "Island"; + } + + } else if( kindOfType.equals("Land") ) { + if (logic != null) { + if (logic.equals("ChosenLandwalk")) { + for (Card c : ai.getOpponent().getLandsInPlay()) { + for (String t : c.getType()) { + if (!invalidTypes.contains(t) && CardType.isALandType(t)) { + chosen = t; + break; + } + } + } + } + } + if( StringUtils.isEmpty(chosen)) + chosen = "Island"; + } + + return chosen; + } + + public static List getSafeTargets(final Player ai, SpellAbility sa, List validCards) { + List safeCards = new ArrayList(validCards); + safeCards = CardLists.filter(safeCards, new Predicate() { + @Override + public boolean apply(final Card c) { + if (c.getController() == ai) { + if (c.getSVar("Targeting").equals("Dies") || c.getSVar("Targeting").equals("Counter")) + return false; + } + return true; + } + }); + return safeCards; + } + + public static int damageFromETB(final Player player, final Card permanent) { + int damage = 0; + final Game game = player.getGame(); + final ArrayList theTriggers = new ArrayList(); + + for (Card card : game.getCardsIn(ZoneType.Battlefield)) { + theTriggers.addAll(card.getTriggers()); + } + for (Trigger trigger : theTriggers) { + HashMap trigParams = trigger.getMapParams(); + final Card source = trigger.getHostCard(); + + + if (!trigger.zonesCheck(game.getZoneOf(source))) { + continue; + } + if (!trigger.requirementsCheck(game)) { + continue; + } + if (trigParams.containsKey("CheckOnTriggeredCard") + && AbilityUtils.getDefinedCards(permanent, source.getSVar(trigParams.get("CheckOnTriggeredCard").split(" ")[0]), null).isEmpty()) { + continue; + } + TriggerType mode = trigger.getMode(); + if (mode != TriggerType.ChangesZone) { + continue; + } + if (!"Battlefield".equals(trigParams.get("Destination"))) { + continue; + } + if (trigParams.containsKey("ValidCard")) { + if (!permanent.isValid(trigParams.get("ValidCard"), source.getController(), source)) { + continue; + } + } + + String ability = source.getSVar(trigParams.get("Execute")); + if (ability.isEmpty()) { + continue; + } + + final Map abilityParams = AbilityFactory.getMapParams(ability); + // Destroy triggers + if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage")) + || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) { + if (!"TriggeredCardController".equals(abilityParams.get("Defined"))) { + continue; + } + if (!abilityParams.containsKey("NumDmg")) { + continue; + } + damage += ComputerUtilCombat.predictDamageTo(player, AbilityUtils.calculateAmount(source, abilityParams.get("NumDmg"), null), source, false); + } else if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("LoseLife")) + || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("LoseLife"))) { + if (!"TriggeredCardController".equals(abilityParams.get("Defined"))) { + continue; + } + if (!abilityParams.containsKey("LifeAmount")) { + continue; + } + damage += AbilityUtils.calculateAmount(source, abilityParams.get("LifeAmount"), null); + } + } + return damage; + } + + public static boolean isNegativeCounter(CounterType type, Card c) { + return type == CounterType.AGE || type == CounterType.BLAZE || type == CounterType.BRIBERY || type == CounterType.DOOM + || type == CounterType.ICE || type == CounterType.M1M1 || type == CounterType.M0M2 || type == CounterType.M0M1 + || type == CounterType.M1M0 || type == CounterType.M2M1 || type == CounterType.M2M2 || type == CounterType.MUSIC + || type == CounterType.PARALYZATION || type == CounterType.SHELL || type == CounterType.SLEEP + || type == CounterType.SLEIGHT || (type == CounterType.TIME && !c.isInPlay()) || type == CounterType.WAGE; + } + + /** + *

+ * evaluateBoardPosition. + *

+ * + * @param listToEvaluate + * a list of players to evaluate. + * @return a Player. + */ + public static Player evaluateBoardPosition(final List listToEvaluate) { + Player bestBoardPosition = listToEvaluate.get(0); + int bestBoardRating = 0; + + for (final Player p : listToEvaluate) { + int pRating = p.getLife() * 3; + pRating += p.getLandsInPlay().size() * 2; + + for (final Card c : p.getCardsIn(ZoneType.Battlefield)) { + pRating += ComputerUtilCard.evaluateCreature(c) / 3; + } + + if (p.getCardsIn(ZoneType.Library).size() < 3) { + pRating /= 5; + } + + System.out.println("Board position evaluation for " + p + ": " + pRating); + + if (pRating > bestBoardRating) { + bestBoardRating = pRating; + bestBoardPosition = p; + } + } + + return bestBoardPosition; + } +} diff --git a/forge-game/src/main/java/forge/ai/ComputerUtilCard.java b/forge-gui/src/main/java/forge/ai/ComputerUtilCard.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ComputerUtilCard.java rename to forge-gui/src/main/java/forge/ai/ComputerUtilCard.java diff --git a/forge-game/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-gui/src/main/java/forge/ai/ComputerUtilCombat.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ComputerUtilCombat.java rename to forge-gui/src/main/java/forge/ai/ComputerUtilCombat.java diff --git a/forge-game/src/main/java/forge/ai/ComputerUtilCost.java b/forge-gui/src/main/java/forge/ai/ComputerUtilCost.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ComputerUtilCost.java rename to forge-gui/src/main/java/forge/ai/ComputerUtilCost.java diff --git a/forge-game/src/main/java/forge/ai/ComputerUtilMana.java b/forge-gui/src/main/java/forge/ai/ComputerUtilMana.java similarity index 94% rename from forge-game/src/main/java/forge/ai/ComputerUtilMana.java rename to forge-gui/src/main/java/forge/ai/ComputerUtilMana.java index 6fa19427a31..a5fc4aee23d 100644 --- a/forge-game/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-gui/src/main/java/forge/ai/ComputerUtilMana.java @@ -16,6 +16,7 @@ import com.google.common.base.Predicate; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; +import forge.FThreads; import forge.card.MagicColor; import forge.card.mana.ManaAtom; import forge.card.mana.ManaCost; @@ -132,15 +133,15 @@ public class ComputerUtilMana { // select which abilities may be used for each shard MapOfLists sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost); -// if (DEBUG_MANA_PAYMENT) { -// System.out.println((test ? "test -- " : "PROD -- ") + FThreads.debugGetStackTraceItem(5, true)); -// for (Entry> src : sourcesForShards.entrySet()) { -// System.out.println("\t" +src.getKey() + " : " + src.getValue().size() + " source(s)"); -// for (SpellAbility sss : src.getValue()) { -// System.out.printf("\t\t%s - %s%n", sss.getSourceCard(), sss); -// } -// } -// } + if (DEBUG_MANA_PAYMENT) { + System.out.println((test ? "test -- " : "PROD -- ") + FThreads.debugGetStackTraceItem(5, true)); + for (Entry> src : sourcesForShards.entrySet()) { + System.out.println("\t" +src.getKey() + " : " + src.getValue().size() + " source(s)"); + for (SpellAbility sss : src.getValue()) { + System.out.printf("\t\t%s - %s%n", sss.getSourceCard(), sss); + } + } + } List paymentPlan = new ArrayList(); @@ -234,11 +235,11 @@ public class ComputerUtilMana { } handleOfferingsAI(sa, test, cost.isPaid()); -// if (DEBUG_MANA_PAYMENT) { -// System.err.printf("%s > [%s] payment has %s (%s +%d) for (%s) %s:%n\t%s%n%n", -// FThreads.debugGetCurrThreadId(), test ? "test" : "PROD", cost.isPaid() ? "*PAID*" : "failed", originalCost, -// extraMana, sa.getSourceCard(), sa.toUnsuppressedString(), StringUtils.join(paymentPlan, "\n\t")); -// } + if (DEBUG_MANA_PAYMENT) { + System.err.printf("%s > [%s] payment has %s (%s +%d) for (%s) %s:%n\t%s%n%n", + FThreads.debugGetCurrThreadId(), test ? "test" : "PROD", cost.isPaid() ? "*PAID*" : "failed", originalCost, + extraMana, sa.getSourceCard(), sa.toUnsuppressedString(), StringUtils.join(paymentPlan, "\n\t")); + } if (!cost.isPaid()) { if (test) { diff --git a/forge-game/src/main/java/forge/ai/SpellAbilityAi.java b/forge-gui/src/main/java/forge/ai/SpellAbilityAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/SpellAbilityAi.java rename to forge-gui/src/main/java/forge/ai/SpellAbilityAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/AddPhaseAi.java b/forge-gui/src/main/java/forge/ai/ability/AddPhaseAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/AddPhaseAi.java rename to forge-gui/src/main/java/forge/ai/ability/AddPhaseAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/AddTurnAi.java b/forge-gui/src/main/java/forge/ai/ability/AddTurnAi.java similarity index 94% rename from forge-game/src/main/java/forge/ai/ability/AddTurnAi.java rename to forge-gui/src/main/java/forge/ai/ability/AddTurnAi.java index 2445f328c1d..a2570608262 100644 --- a/forge-game/src/main/java/forge/ai/ability/AddTurnAi.java +++ b/forge-gui/src/main/java/forge/ai/ability/AddTurnAi.java @@ -1,80 +1,80 @@ -/* - * 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 . - */ -package forge.ai.ability; - -import java.util.List; - -import forge.ai.SpellAbilityAi; -import forge.game.ability.AbilityUtils; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; - -/** - *

- * AbilityFactory_Turns class. - *

- * - * @author Forge - * @version $Id: AddTurnAi.java 23792 2013-11-24 10:02:58Z Max mtg $ - */ -public class AddTurnAi extends SpellAbilityAi { - - - @Override - protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { - final Player opp = ai.getWeakestOpponent(); - - if (sa.usesTargeting()) { - sa.resetTargets(); - if (sa.canTarget(ai)) { - sa.getTargets().add(ai); - } else if (mandatory) { - for (final Player ally : ai.getAllies()) { - if (sa.canTarget(ally)) { - sa.getTargets().add(ally); - break; - } - } - if (!sa.getTargetRestrictions().isMinTargetsChosen(sa.getSourceCard(), sa) && sa.canTarget(opp)) { - sa.getTargets().add(opp); - } else { - return false; - } - } - } else { - final List tgtPlayers = AbilityUtils.getDefinedPlayers(sa.getSourceCard(), sa.getParam("Defined"), sa); - for (final Player p : tgtPlayers) { - if (p.isOpponentOf(ai) && !mandatory) { - return false; - } - } - // not sure if the AI should be playing with cards that give the - // Human more turns. - } - return true; - } - - /* (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 doTriggerAINoCost(aiPlayer, sa, false); - } - -} +/* + * 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 . + */ +package forge.ai.ability; + +import java.util.List; + +import forge.ai.SpellAbilityAi; +import forge.game.ability.AbilityUtils; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; + +/** + *

+ * AbilityFactory_Turns class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class AddTurnAi extends SpellAbilityAi { + + + @Override + protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { + final Player opp = ai.getWeakestOpponent(); + + if (sa.usesTargeting()) { + sa.resetTargets(); + if (sa.canTarget(ai)) { + sa.getTargets().add(ai); + } else if (mandatory) { + for (final Player ally : ai.getAllies()) { + if (sa.canTarget(ally)) { + sa.getTargets().add(ally); + break; + } + } + if (!sa.getTargetRestrictions().isMinTargetsChosen(sa.getSourceCard(), sa) && sa.canTarget(opp)) { + sa.getTargets().add(opp); + } else { + return false; + } + } + } else { + final List tgtPlayers = AbilityUtils.getDefinedPlayers(sa.getSourceCard(), sa.getParam("Defined"), sa); + for (final Player p : tgtPlayers) { + if (p.isOpponentOf(ai) && !mandatory) { + return false; + } + } + // not sure if the AI should be playing with cards that give the + // Human more turns. + } + return true; + } + + /* (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 doTriggerAINoCost(aiPlayer, sa, false); + } + +} diff --git a/forge-game/src/main/java/forge/ai/ability/AlwaysPlayAi.java b/forge-gui/src/main/java/forge/ai/ability/AlwaysPlayAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/AlwaysPlayAi.java rename to forge-gui/src/main/java/forge/ai/ability/AlwaysPlayAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/AnimateAi.java b/forge-gui/src/main/java/forge/ai/ability/AnimateAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/AnimateAi.java rename to forge-gui/src/main/java/forge/ai/ability/AnimateAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/AnimateAllAi.java b/forge-gui/src/main/java/forge/ai/ability/AnimateAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/AnimateAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/AnimateAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/AttachAi.java b/forge-gui/src/main/java/forge/ai/ability/AttachAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/AttachAi.java rename to forge-gui/src/main/java/forge/ai/ability/AttachAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/BalanceAi.java b/forge-gui/src/main/java/forge/ai/ability/BalanceAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/BalanceAi.java rename to forge-gui/src/main/java/forge/ai/ability/BalanceAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/BecomesBlockedAi.java b/forge-gui/src/main/java/forge/ai/ability/BecomesBlockedAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/BecomesBlockedAi.java rename to forge-gui/src/main/java/forge/ai/ability/BecomesBlockedAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/BondAi.java b/forge-gui/src/main/java/forge/ai/ability/BondAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/BondAi.java rename to forge-gui/src/main/java/forge/ai/ability/BondAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/CanPlayAsDrawbackAi.java b/forge-gui/src/main/java/forge/ai/ability/CanPlayAsDrawbackAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/CanPlayAsDrawbackAi.java rename to forge-gui/src/main/java/forge/ai/ability/CanPlayAsDrawbackAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/CannotPlayAi.java b/forge-gui/src/main/java/forge/ai/ability/CannotPlayAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/CannotPlayAi.java rename to forge-gui/src/main/java/forge/ai/ability/CannotPlayAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ChangeTargetsAi.java b/forge-gui/src/main/java/forge/ai/ability/ChangeTargetsAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ChangeTargetsAi.java rename to forge-gui/src/main/java/forge/ai/ability/ChangeTargetsAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-gui/src/main/java/forge/ai/ability/ChangeZoneAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ChangeZoneAi.java rename to forge-gui/src/main/java/forge/ai/ability/ChangeZoneAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ChangeZoneAllAi.java b/forge-gui/src/main/java/forge/ai/ability/ChangeZoneAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ChangeZoneAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/ChangeZoneAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/CharmAi.java b/forge-gui/src/main/java/forge/ai/ability/CharmAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/CharmAi.java rename to forge-gui/src/main/java/forge/ai/ability/CharmAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ChooseCardAi.java b/forge-gui/src/main/java/forge/ai/ability/ChooseCardAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ChooseCardAi.java rename to forge-gui/src/main/java/forge/ai/ability/ChooseCardAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ChooseCardNameAi.java b/forge-gui/src/main/java/forge/ai/ability/ChooseCardNameAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ChooseCardNameAi.java rename to forge-gui/src/main/java/forge/ai/ability/ChooseCardNameAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ChooseColorAi.java b/forge-gui/src/main/java/forge/ai/ability/ChooseColorAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ChooseColorAi.java rename to forge-gui/src/main/java/forge/ai/ability/ChooseColorAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java b/forge-gui/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java rename to forge-gui/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ChoosePlayerAi.java b/forge-gui/src/main/java/forge/ai/ability/ChoosePlayerAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ChoosePlayerAi.java rename to forge-gui/src/main/java/forge/ai/ability/ChoosePlayerAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ChooseSourceAi.java b/forge-gui/src/main/java/forge/ai/ability/ChooseSourceAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ChooseSourceAi.java rename to forge-gui/src/main/java/forge/ai/ability/ChooseSourceAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ChooseTypeAi.java b/forge-gui/src/main/java/forge/ai/ability/ChooseTypeAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ChooseTypeAi.java rename to forge-gui/src/main/java/forge/ai/ability/ChooseTypeAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ClashAi.java b/forge-gui/src/main/java/forge/ai/ability/ClashAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ClashAi.java rename to forge-gui/src/main/java/forge/ai/ability/ClashAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/CloneAi.java b/forge-gui/src/main/java/forge/ai/ability/CloneAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/CloneAi.java rename to forge-gui/src/main/java/forge/ai/ability/CloneAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ControlExchangeAi.java b/forge-gui/src/main/java/forge/ai/ability/ControlExchangeAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ControlExchangeAi.java rename to forge-gui/src/main/java/forge/ai/ability/ControlExchangeAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ControlGainAi.java b/forge-gui/src/main/java/forge/ai/ability/ControlGainAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ControlGainAi.java rename to forge-gui/src/main/java/forge/ai/ability/ControlGainAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/CopyPermanentAi.java b/forge-gui/src/main/java/forge/ai/ability/CopyPermanentAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/CopyPermanentAi.java rename to forge-gui/src/main/java/forge/ai/ability/CopyPermanentAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/CounterAi.java b/forge-gui/src/main/java/forge/ai/ability/CounterAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/CounterAi.java rename to forge-gui/src/main/java/forge/ai/ability/CounterAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/CountersAi.java b/forge-gui/src/main/java/forge/ai/ability/CountersAi.java similarity index 95% rename from forge-game/src/main/java/forge/ai/ability/CountersAi.java rename to forge-gui/src/main/java/forge/ai/ability/CountersAi.java index a107514e27f..fa8cec27099 100644 --- a/forge-game/src/main/java/forge/ai/ability/CountersAi.java +++ b/forge-gui/src/main/java/forge/ai/ability/CountersAi.java @@ -1,109 +1,109 @@ -/* - * 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 . - */ -package forge.ai.ability; - -import java.util.List; - -import com.google.common.base.Predicate; - -import forge.ai.ComputerUtilCard; -import forge.game.card.Card; -import forge.game.card.CardLists; -import forge.game.card.CounterType; -import forge.util.Aggregates; - - -/** - *

- * AbilityFactory_Counters class. - *

- * - * @author Forge - * @version $Id: CountersAi.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public abstract class CountersAi { - // An AbilityFactory subclass for Putting or Removing Counters on Cards. - - /** - *

- * chooseCursedTarget. - *

- * - * @param list - * a {@link forge.CardList} object. - * @param type - * a {@link java.lang.String} object. - * @param amount - * a int. - * @return a {@link forge.game.card.Card} object. - */ - public static Card chooseCursedTarget(final List list, final String type, final int amount) { - Card choice; - if (type.equals("M1M1")) { - // try to kill the best killable creature, or reduce the best one - final List killable = CardLists.filter(list, new Predicate() { - @Override - public boolean apply(final Card c) { - return c.getNetDefense() <= amount; - } - }); - if (killable.size() > 0) { - choice = ComputerUtilCard.getBestCreatureAI(killable); - } else { - choice = ComputerUtilCard.getBestCreatureAI(list); - } - } else { - // improve random choice here - choice = Aggregates.random(list); - } - return choice; - } - - /** - *

- * chooseBoonTarget. - *

- * - * @param list - * a {@link forge.CardList} object. - * @param type - * a {@link java.lang.String} object. - * @return a {@link forge.game.card.Card} object. - */ - public static Card chooseBoonTarget(final List list, final String type) { - Card choice; - if (type.equals("P1P1")) { - choice = ComputerUtilCard.getBestCreatureAI(list); - } else if (type.equals("DIVINITY")) { - final List boon = CardLists.filter(list, new Predicate() { - @Override - public boolean apply(final Card c) { - return c.getCounters(CounterType.DIVINITY) == 0; - } - }); - choice = ComputerUtilCard.getMostExpensivePermanentAI(boon, null, false); - } else { - // The AI really should put counters on cards that can use it. - // Charge counters on things with Charge abilities, etc. Expand - // these above - choice = Aggregates.random(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 . + */ +package forge.ai.ability; + +import java.util.List; + +import com.google.common.base.Predicate; + +import forge.ai.ComputerUtilCard; +import forge.game.card.Card; +import forge.game.card.CardLists; +import forge.game.card.CounterType; +import forge.util.Aggregates; + + +/** + *

+ * AbilityFactory_Counters class. + *

+ * + * @author Forge + * @version $Id$ + */ +public abstract class CountersAi { + // An AbilityFactory subclass for Putting or Removing Counters on Cards. + + /** + *

+ * chooseCursedTarget. + *

+ * + * @param list + * a {@link forge.CardList} object. + * @param type + * a {@link java.lang.String} object. + * @param amount + * a int. + * @return a {@link forge.game.card.Card} object. + */ + public static Card chooseCursedTarget(final List list, final String type, final int amount) { + Card choice; + if (type.equals("M1M1")) { + // try to kill the best killable creature, or reduce the best one + final List killable = CardLists.filter(list, new Predicate() { + @Override + public boolean apply(final Card c) { + return c.getNetDefense() <= amount; + } + }); + if (killable.size() > 0) { + choice = ComputerUtilCard.getBestCreatureAI(killable); + } else { + choice = ComputerUtilCard.getBestCreatureAI(list); + } + } else { + // improve random choice here + choice = Aggregates.random(list); + } + return choice; + } + + /** + *

+ * chooseBoonTarget. + *

+ * + * @param list + * a {@link forge.CardList} object. + * @param type + * a {@link java.lang.String} object. + * @return a {@link forge.game.card.Card} object. + */ + public static Card chooseBoonTarget(final List list, final String type) { + Card choice; + if (type.equals("P1P1")) { + choice = ComputerUtilCard.getBestCreatureAI(list); + } else if (type.equals("DIVINITY")) { + final List boon = CardLists.filter(list, new Predicate() { + @Override + public boolean apply(final Card c) { + return c.getCounters(CounterType.DIVINITY) == 0; + } + }); + choice = ComputerUtilCard.getMostExpensivePermanentAI(boon, null, false); + } else { + // The AI really should put counters on cards that can use it. + // Charge counters on things with Charge abilities, etc. Expand + // these above + choice = Aggregates.random(list); + } + return choice; + } + +} diff --git a/forge-game/src/main/java/forge/ai/ability/CountersMoveAi.java b/forge-gui/src/main/java/forge/ai/ability/CountersMoveAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/CountersMoveAi.java rename to forge-gui/src/main/java/forge/ai/ability/CountersMoveAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/CountersProliferateAi.java b/forge-gui/src/main/java/forge/ai/ability/CountersProliferateAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/CountersProliferateAi.java rename to forge-gui/src/main/java/forge/ai/ability/CountersProliferateAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-gui/src/main/java/forge/ai/ability/CountersPutAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/CountersPutAi.java rename to forge-gui/src/main/java/forge/ai/ability/CountersPutAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/CountersPutAllAi.java b/forge-gui/src/main/java/forge/ai/ability/CountersPutAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/CountersPutAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/CountersPutAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java b/forge-gui/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java rename to forge-gui/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/CountersRemoveAi.java b/forge-gui/src/main/java/forge/ai/ability/CountersRemoveAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/CountersRemoveAi.java rename to forge-gui/src/main/java/forge/ai/ability/CountersRemoveAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DamageAiBase.java b/forge-gui/src/main/java/forge/ai/ability/DamageAiBase.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DamageAiBase.java rename to forge-gui/src/main/java/forge/ai/ability/DamageAiBase.java diff --git a/forge-game/src/main/java/forge/ai/ability/DamageAllAi.java b/forge-gui/src/main/java/forge/ai/ability/DamageAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DamageAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/DamageAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-gui/src/main/java/forge/ai/ability/DamageDealAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DamageDealAi.java rename to forge-gui/src/main/java/forge/ai/ability/DamageDealAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DamageEachAi.java b/forge-gui/src/main/java/forge/ai/ability/DamageEachAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DamageEachAi.java rename to forge-gui/src/main/java/forge/ai/ability/DamageEachAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DamagePreventAi.java b/forge-gui/src/main/java/forge/ai/ability/DamagePreventAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DamagePreventAi.java rename to forge-gui/src/main/java/forge/ai/ability/DamagePreventAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DamagePreventAllAi.java b/forge-gui/src/main/java/forge/ai/ability/DamagePreventAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DamagePreventAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/DamagePreventAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DebuffAi.java b/forge-gui/src/main/java/forge/ai/ability/DebuffAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DebuffAi.java rename to forge-gui/src/main/java/forge/ai/ability/DebuffAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DebuffAllAi.java b/forge-gui/src/main/java/forge/ai/ability/DebuffAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DebuffAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/DebuffAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DelayedTriggerAi.java b/forge-gui/src/main/java/forge/ai/ability/DelayedTriggerAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DelayedTriggerAi.java rename to forge-gui/src/main/java/forge/ai/ability/DelayedTriggerAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DestroyAi.java b/forge-gui/src/main/java/forge/ai/ability/DestroyAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DestroyAi.java rename to forge-gui/src/main/java/forge/ai/ability/DestroyAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DestroyAllAi.java b/forge-gui/src/main/java/forge/ai/ability/DestroyAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DestroyAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/DestroyAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DigAi.java b/forge-gui/src/main/java/forge/ai/ability/DigAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DigAi.java rename to forge-gui/src/main/java/forge/ai/ability/DigAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DigUntilAi.java b/forge-gui/src/main/java/forge/ai/ability/DigUntilAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DigUntilAi.java rename to forge-gui/src/main/java/forge/ai/ability/DigUntilAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DiscardAi.java b/forge-gui/src/main/java/forge/ai/ability/DiscardAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DiscardAi.java rename to forge-gui/src/main/java/forge/ai/ability/DiscardAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DrainManaAi.java b/forge-gui/src/main/java/forge/ai/ability/DrainManaAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/DrainManaAi.java rename to forge-gui/src/main/java/forge/ai/ability/DrainManaAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/DrawAi.java b/forge-gui/src/main/java/forge/ai/ability/DrawAi.java similarity index 97% rename from forge-game/src/main/java/forge/ai/ability/DrawAi.java rename to forge-gui/src/main/java/forge/ai/ability/DrawAi.java index 7697fdbe545..b1fedf3dbc3 100644 --- a/forge-game/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-gui/src/main/java/forge/ai/ability/DrawAi.java @@ -1,272 +1,272 @@ -/* - -* 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 . - */ -package forge.ai.ability; - -import forge.ai.AiCostDecision; -import forge.ai.ComputerUtil; -import forge.ai.ComputerUtilCost; -import forge.ai.ComputerUtilMana; -import forge.ai.SpellAbilityAi; -import forge.game.Game; -import forge.game.ability.AbilityUtils; -import forge.game.card.Card; -import forge.game.cost.Cost; -import forge.game.cost.CostDiscard; -import forge.game.cost.CostPart; -import forge.game.cost.PaymentDecision; -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; - -public class DrawAi extends SpellAbilityAi { - - @Override - public boolean chkAIDrawback(SpellAbility sa, Player ai) { - return targetAI(ai, sa, 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 ai, SpellAbility sa) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); - final Card source = sa.getSourceCard(); - final Cost abCost = sa.getPayCosts(); - final Game game = ai.getGame(); - - if (abCost != null) { - // AI currently disabled for these costs - if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, abCost, source)) { - return false; - } - - if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) { - return false; - } - - if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) { - AiCostDecision aiDecisions = new AiCostDecision(ai, sa); - for (final CostPart part : abCost.getCostParts()) { - if (part instanceof CostDiscard) { - PaymentDecision decision = part.accept(aiDecisions); - if ( null == decision ) - return false; - for (Card discard : decision.cards) { - if (!ComputerUtil.isWorseThanDraw(ai, discard)) { - return false; - } - } - } - } - } - - if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) { - return false; - } - - } - - if (!targetAI(ai, sa, false)) { - return false; - } - - if (tgt != null) { - final Player player = sa.getTargets().getFirstTargetedPlayer(); - if (player != null && player.isOpponentOf(ai)) { - return true; - } - } - - // prevent run-away activations - first time will always return true - if (ComputerUtil.preventRunAwayActivations(sa)) { - return false; - } - - if (ComputerUtil.playImmediately(ai, sa)) { - return true; - } - - // Don't use draw abilities before main 2 if possible - if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) - && !sa.hasParam("ActivationPhases") && !ComputerUtil.castSpellInMain1(ai, sa)) { - return false; - } - 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 - && !ComputerUtil.activateForCost(sa, ai)) { - return false; - } - - // Don't tap creatures that may be able to block - if (ComputerUtil.waitForBlocking(sa) && !sa.hasParam("ActivationPhases")) { - 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.getSourceCard(); - final boolean drawback = (sa instanceof AbilitySub); - final Game game = ai.getGame(); - Player opp = ai.getOpponent(); - - int computerHandSize = ai.getCardsIn(ZoneType.Hand).size(); - final int humanLibrarySize = opp.getCardsIn(ZoneType.Library).size(); - final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size(); - final int computerMaxHandSize = ai.getMaxHandSize(); - - //if a spell is used don't count the card - if (sa.isSpell() && source.isInZone(ZoneType.Hand)) { - computerHandSize -= 1; - } - - int numCards = 1; - if (sa.hasParam("NumCards")) { - numCards = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa); - } - - boolean xPaid = false; - final String num = sa.getParam("NumCards"); - 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("")) { - numCards = Integer.parseInt(source.getSVar("PayX")); - } else { - numCards = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(numCards)); - } - xPaid = true; - } - //if (n) - - // TODO: if xPaid and one of the below reasons would fail, instead of - // bailing - // reduce toPay amount to acceptable level - - if (tgt != null) { - // ability is targeted - sa.resetTargets(); - - final boolean canTgtHuman = sa.canTarget(opp); - final boolean canTgtComp = sa.canTarget(ai); - boolean tgtHuman = false; - - if (!canTgtHuman && !canTgtComp) { - return false; - } - - if (canTgtHuman && !opp.cantLose() && numCards >= humanLibrarySize) { - // Deck the Human? DO IT! - sa.getTargets().add(opp); - return true; - } - - if (numCards >= computerLibrarySize) { - if (xPaid) { - numCards = computerLibrarySize - 1; - source.setSVar("PayX", Integer.toString(numCards)); - } else { - // Don't deck your self - if (!mandatory) { - return false; - } - tgtHuman = true; - } - } - - if (computerHandSize + numCards > computerMaxHandSize && game.getPhaseHandler().isPlayerTurn(ai)) { - if (xPaid) { - numCards = computerMaxHandSize - computerHandSize; - source.setSVar("PayX", Integer.toString(numCards)); - } else { - // Don't draw too many cards and then risk discarding cards - // at EOT - // TODO: "NextUpkeep" is deprecated - if (!(sa.hasParam("NextUpkeep") || (sa instanceof AbilitySub)) && !mandatory) { - return false; - } - } - } - - if (numCards == 0 && !mandatory && !drawback) { - return false; - } - - if ((!tgtHuman || !canTgtHuman) && canTgtComp) { - sa.getTargets().add(ai); - } else if (mandatory && canTgtHuman) { - sa.getTargets().add(opp); - } else { - return false; - } - } else if (!mandatory) { - // TODO: consider if human is the defined player - - // ability is not targeted - if (numCards >= computerLibrarySize) { - if (ai.isCardInPlay("Laboratory Maniac")) { - return true; - } - // Don't deck yourself - return false; - } - - if (numCards == 0 && !drawback) { - return false; - } - - if ((computerHandSize + numCards > computerMaxHandSize) - && game.getPhaseHandler().isPlayerTurn(ai) - && !sa.isTrigger()) { - // Don't draw too many cards and then risk discarding cards at - // EOT - if (!sa.hasParam("NextUpkeep") && !drawback) { - return false; - } - } - } - return true; - } // drawTargetAI() - - - @Override - protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { - return targetAI(ai, sa, mandatory); - } - - - /* (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) { - int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa) : 1; - // AI shouldn't mill itself - return numCards < player.getZone(ZoneType.Library).size(); - } -} +/* + +* 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 . + */ +package forge.ai.ability; + +import forge.ai.AiCostDecision; +import forge.ai.ComputerUtil; +import forge.ai.ComputerUtilCost; +import forge.ai.ComputerUtilMana; +import forge.ai.SpellAbilityAi; +import forge.game.Game; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.cost.Cost; +import forge.game.cost.CostDiscard; +import forge.game.cost.CostPart; +import forge.game.cost.PaymentDecision; +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; + +public class DrawAi extends SpellAbilityAi { + + @Override + public boolean chkAIDrawback(SpellAbility sa, Player ai) { + return targetAI(ai, sa, 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 ai, SpellAbility sa) { + final TargetRestrictions tgt = sa.getTargetRestrictions(); + final Card source = sa.getSourceCard(); + final Cost abCost = sa.getPayCosts(); + final Game game = ai.getGame(); + + if (abCost != null) { + // AI currently disabled for these costs + if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, abCost, source)) { + return false; + } + + if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) { + return false; + } + + if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) { + AiCostDecision aiDecisions = new AiCostDecision(ai, sa); + for (final CostPart part : abCost.getCostParts()) { + if (part instanceof CostDiscard) { + PaymentDecision decision = part.accept(aiDecisions); + if ( null == decision ) + return false; + for (Card discard : decision.cards) { + if (!ComputerUtil.isWorseThanDraw(ai, discard)) { + return false; + } + } + } + } + } + + if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) { + return false; + } + + } + + if (!targetAI(ai, sa, false)) { + return false; + } + + if (tgt != null) { + final Player player = sa.getTargets().getFirstTargetedPlayer(); + if (player != null && player.isOpponentOf(ai)) { + return true; + } + } + + // prevent run-away activations - first time will always return true + if (ComputerUtil.preventRunAwayActivations(sa)) { + return false; + } + + if (ComputerUtil.playImmediately(ai, sa)) { + return true; + } + + // Don't use draw abilities before main 2 if possible + if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) + && !sa.hasParam("ActivationPhases") && !ComputerUtil.castSpellInMain1(ai, sa)) { + return false; + } + 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 + && !ComputerUtil.activateForCost(sa, ai)) { + return false; + } + + // Don't tap creatures that may be able to block + if (ComputerUtil.waitForBlocking(sa) && !sa.hasParam("ActivationPhases")) { + 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.getSourceCard(); + final boolean drawback = (sa instanceof AbilitySub); + final Game game = ai.getGame(); + Player opp = ai.getOpponent(); + + int computerHandSize = ai.getCardsIn(ZoneType.Hand).size(); + final int humanLibrarySize = opp.getCardsIn(ZoneType.Library).size(); + final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size(); + final int computerMaxHandSize = ai.getMaxHandSize(); + + //if a spell is used don't count the card + if (sa.isSpell() && source.isInZone(ZoneType.Hand)) { + computerHandSize -= 1; + } + + int numCards = 1; + if (sa.hasParam("NumCards")) { + numCards = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa); + } + + boolean xPaid = false; + final String num = sa.getParam("NumCards"); + 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("")) { + numCards = Integer.parseInt(source.getSVar("PayX")); + } else { + numCards = ComputerUtilMana.determineLeftoverMana(sa, ai); + source.setSVar("PayX", Integer.toString(numCards)); + } + xPaid = true; + } + //if (n) + + // TODO: if xPaid and one of the below reasons would fail, instead of + // bailing + // reduce toPay amount to acceptable level + + if (tgt != null) { + // ability is targeted + sa.resetTargets(); + + final boolean canTgtHuman = sa.canTarget(opp); + final boolean canTgtComp = sa.canTarget(ai); + boolean tgtHuman = false; + + if (!canTgtHuman && !canTgtComp) { + return false; + } + + if (canTgtHuman && !opp.cantLose() && numCards >= humanLibrarySize) { + // Deck the Human? DO IT! + sa.getTargets().add(opp); + return true; + } + + if (numCards >= computerLibrarySize) { + if (xPaid) { + numCards = computerLibrarySize - 1; + source.setSVar("PayX", Integer.toString(numCards)); + } else { + // Don't deck your self + if (!mandatory) { + return false; + } + tgtHuman = true; + } + } + + if (computerHandSize + numCards > computerMaxHandSize && game.getPhaseHandler().isPlayerTurn(ai)) { + if (xPaid) { + numCards = computerMaxHandSize - computerHandSize; + source.setSVar("PayX", Integer.toString(numCards)); + } else { + // Don't draw too many cards and then risk discarding cards + // at EOT + // TODO: "NextUpkeep" is deprecated + if (!(sa.hasParam("NextUpkeep") || (sa instanceof AbilitySub)) && !mandatory) { + return false; + } + } + } + + if (numCards == 0 && !mandatory && !drawback) { + return false; + } + + if ((!tgtHuman || !canTgtHuman) && canTgtComp) { + sa.getTargets().add(ai); + } else if (mandatory && canTgtHuman) { + sa.getTargets().add(opp); + } else { + return false; + } + } else if (!mandatory) { + // TODO: consider if human is the defined player + + // ability is not targeted + if (numCards >= computerLibrarySize) { + if (ai.isCardInPlay("Laboratory Maniac")) { + return true; + } + // Don't deck yourself + return false; + } + + if (numCards == 0 && !drawback) { + return false; + } + + if ((computerHandSize + numCards > computerMaxHandSize) + && game.getPhaseHandler().isPlayerTurn(ai) + && !sa.isTrigger()) { + // Don't draw too many cards and then risk discarding cards at + // EOT + if (!sa.hasParam("NextUpkeep") && !drawback) { + return false; + } + } + } + return true; + } // drawTargetAI() + + + @Override + protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { + return targetAI(ai, sa, mandatory); + } + + + /* (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) { + int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa) : 1; + // AI shouldn't mill itself + return numCards < player.getZone(ZoneType.Library).size(); + } +} diff --git a/forge-game/src/main/java/forge/ai/ability/EffectAi.java b/forge-gui/src/main/java/forge/ai/ability/EffectAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/EffectAi.java rename to forge-gui/src/main/java/forge/ai/ability/EffectAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/EncodeAi.java b/forge-gui/src/main/java/forge/ai/ability/EncodeAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/EncodeAi.java rename to forge-gui/src/main/java/forge/ai/ability/EncodeAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/EndTurnAi.java b/forge-gui/src/main/java/forge/ai/ability/EndTurnAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/EndTurnAi.java rename to forge-gui/src/main/java/forge/ai/ability/EndTurnAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/FightAi.java b/forge-gui/src/main/java/forge/ai/ability/FightAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/FightAi.java rename to forge-gui/src/main/java/forge/ai/ability/FightAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/FlipACoinAi.java b/forge-gui/src/main/java/forge/ai/ability/FlipACoinAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/FlipACoinAi.java rename to forge-gui/src/main/java/forge/ai/ability/FlipACoinAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/FogAi.java b/forge-gui/src/main/java/forge/ai/ability/FogAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/FogAi.java rename to forge-gui/src/main/java/forge/ai/ability/FogAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/GameLossAi.java b/forge-gui/src/main/java/forge/ai/ability/GameLossAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/GameLossAi.java rename to forge-gui/src/main/java/forge/ai/ability/GameLossAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/GameWinAi.java b/forge-gui/src/main/java/forge/ai/ability/GameWinAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/GameWinAi.java rename to forge-gui/src/main/java/forge/ai/ability/GameWinAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/HauntAi.java b/forge-gui/src/main/java/forge/ai/ability/HauntAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/HauntAi.java rename to forge-gui/src/main/java/forge/ai/ability/HauntAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/LegendaryRuleAi.java b/forge-gui/src/main/java/forge/ai/ability/LegendaryRuleAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/LegendaryRuleAi.java rename to forge-gui/src/main/java/forge/ai/ability/LegendaryRuleAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/LifeExchangeAi.java b/forge-gui/src/main/java/forge/ai/ability/LifeExchangeAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/LifeExchangeAi.java rename to forge-gui/src/main/java/forge/ai/ability/LifeExchangeAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/LifeGainAi.java b/forge-gui/src/main/java/forge/ai/ability/LifeGainAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/LifeGainAi.java rename to forge-gui/src/main/java/forge/ai/ability/LifeGainAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/LifeLoseAi.java b/forge-gui/src/main/java/forge/ai/ability/LifeLoseAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/LifeLoseAi.java rename to forge-gui/src/main/java/forge/ai/ability/LifeLoseAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/LifeSetAi.java b/forge-gui/src/main/java/forge/ai/ability/LifeSetAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/LifeSetAi.java rename to forge-gui/src/main/java/forge/ai/ability/LifeSetAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ManaEffectAi.java b/forge-gui/src/main/java/forge/ai/ability/ManaEffectAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ManaEffectAi.java rename to forge-gui/src/main/java/forge/ai/ability/ManaEffectAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/MillAi.java b/forge-gui/src/main/java/forge/ai/ability/MillAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/MillAi.java rename to forge-gui/src/main/java/forge/ai/ability/MillAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/MustAttackAi.java b/forge-gui/src/main/java/forge/ai/ability/MustAttackAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/MustAttackAi.java rename to forge-gui/src/main/java/forge/ai/ability/MustAttackAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/MustBlockAi.java b/forge-gui/src/main/java/forge/ai/ability/MustBlockAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/MustBlockAi.java rename to forge-gui/src/main/java/forge/ai/ability/MustBlockAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/PeekAndRevealAi.java b/forge-gui/src/main/java/forge/ai/ability/PeekAndRevealAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/PeekAndRevealAi.java rename to forge-gui/src/main/java/forge/ai/ability/PeekAndRevealAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/PermanentCreatureAi.java b/forge-gui/src/main/java/forge/ai/ability/PermanentCreatureAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/PermanentCreatureAi.java rename to forge-gui/src/main/java/forge/ai/ability/PermanentCreatureAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/PermanentNoncreatureAi.java b/forge-gui/src/main/java/forge/ai/ability/PermanentNoncreatureAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/PermanentNoncreatureAi.java rename to forge-gui/src/main/java/forge/ai/ability/PermanentNoncreatureAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/PhasesAi.java b/forge-gui/src/main/java/forge/ai/ability/PhasesAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/PhasesAi.java rename to forge-gui/src/main/java/forge/ai/ability/PhasesAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/PlayAi.java b/forge-gui/src/main/java/forge/ai/ability/PlayAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/PlayAi.java rename to forge-gui/src/main/java/forge/ai/ability/PlayAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/PoisonAi.java b/forge-gui/src/main/java/forge/ai/ability/PoisonAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/PoisonAi.java rename to forge-gui/src/main/java/forge/ai/ability/PoisonAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/PowerExchangeAi.java b/forge-gui/src/main/java/forge/ai/ability/PowerExchangeAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/PowerExchangeAi.java rename to forge-gui/src/main/java/forge/ai/ability/PowerExchangeAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ProtectAi.java b/forge-gui/src/main/java/forge/ai/ability/ProtectAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ProtectAi.java rename to forge-gui/src/main/java/forge/ai/ability/ProtectAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ProtectAllAi.java b/forge-gui/src/main/java/forge/ai/ability/ProtectAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ProtectAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/ProtectAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/PumpAi.java b/forge-gui/src/main/java/forge/ai/ability/PumpAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/PumpAi.java rename to forge-gui/src/main/java/forge/ai/ability/PumpAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/PumpAiBase.java b/forge-gui/src/main/java/forge/ai/ability/PumpAiBase.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/PumpAiBase.java rename to forge-gui/src/main/java/forge/ai/ability/PumpAiBase.java diff --git a/forge-game/src/main/java/forge/ai/ability/PumpAllAi.java b/forge-gui/src/main/java/forge/ai/ability/PumpAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/PumpAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/PumpAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java b/forge-gui/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java rename to forge-gui/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/RegenerateAi.java b/forge-gui/src/main/java/forge/ai/ability/RegenerateAi.java similarity index 96% rename from forge-game/src/main/java/forge/ai/ability/RegenerateAi.java rename to forge-gui/src/main/java/forge/ai/ability/RegenerateAi.java index 8c1cb92c5fb..aad710b6365 100644 --- a/forge-game/src/main/java/forge/ai/ability/RegenerateAi.java +++ b/forge-gui/src/main/java/forge/ai/ability/RegenerateAi.java @@ -1,244 +1,244 @@ -/* - * 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 . - */ -package forge.ai.ability; - -import java.util.ArrayList; -import java.util.List; - -import forge.ai.ComputerUtil; -import forge.ai.ComputerUtilCard; -import forge.ai.ComputerUtilCombat; -import forge.ai.ComputerUtilCost; -import forge.ai.SpellAbilityAi; -import forge.game.Game; -import forge.game.GameObject; -import forge.game.ability.AbilityUtils; -import forge.game.card.Card; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; -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.TargetRestrictions; -import forge.game.zone.ZoneType; - -/** - *

- * AbilityFactory_Regenerate class. - *

- * - * @author Forge - * @version $Id: RegenerateAi.java 24089 2013-12-28 05:08:39Z swordshine $ - */ -public class RegenerateAi extends SpellAbilityAi { - - // Ex: A:SP$Regenerate | Cost$W | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$Regenerate - // target creature. - // http://www.slightlymagic.net/wiki/Forge_AbilityFactory#Regenerate - - // ************************************************************** - // ********************* Regenerate **************************** - // ************************************************************** - - @Override - protected boolean canPlayAI(Player ai, SpellAbility sa) { - final Card hostCard = sa.getSourceCard(); - final Cost abCost = sa.getPayCosts(); - final Game game = ai.getGame(); - final Combat combat = game.getCombat(); - - boolean chance = false; - if (abCost != null) { - // AI currently disabled for these costs - if (!ComputerUtilCost.checkLifeCost(ai, abCost, hostCard, 4, null)) { - return false; - } - - if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, hostCard)) { - return false; - } - - if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, abCost, hostCard)) { - return false; - } - - if (!ComputerUtilCost.checkRemoveCounterCost(abCost, 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 list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa); - - if (!game.getStack().isEmpty()) { - final List objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); - - for (final Card c : list) { - if (objects.contains(c)) { - chance = true; - } - } - } else { - if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { - boolean flag = false; - - for (final Card c : list) { - if (c.getShield().isEmpty()) { - flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat); - } - } - - chance = flag; - } else { // if nothing on the stack, and it's not declare - // blockers. no need to regen - return false; - } - } - } else { - sa.resetTargets(); - // filter AIs battlefield by what I can target - List targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard); - targetables = CardLists.getTargetableCards(targetables, sa); - - if (targetables.size() == 0) { - return false; - } - - if (!game.getStack().isEmpty()) { - // check stack for something on the stack will kill anything i - // control - final List objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); - - final List threatenedTargets = new ArrayList(); - - for (final Card c : targetables) { - if (objects.contains(c) && c.getShield().isEmpty()) { - threatenedTargets.add(c); - } - } - - if (!threatenedTargets.isEmpty()) { - // Choose "best" of the remaining to regenerate - sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(threatenedTargets)); - chance = true; - } - } else { - if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { - final List combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES); - CardLists.sortByEvaluateCreature(combatants); - - for (final Card c : combatants) { - if (c.getShield().isEmpty() && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) { - sa.getTargets().add(c); - chance = true; - break; - } - } - } - } - if (sa.getTargets().isEmpty()) { - return false; - } - } - - return chance; - } // regenerateCanPlayAI - - @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 = regenMandatoryTarget(ai, sa, mandatory); - } - - return chance; - } - - private static boolean regenMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) { - final Card hostCard = sa.getSourceCard(); - final Game game = ai.getGame(); - final TargetRestrictions tgt = sa.getTargetRestrictions(); - sa.resetTargets(); - // filter AIs battlefield by what I can target - List targetables = game.getCardsIn(ZoneType.Battlefield); - targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard); - targetables = CardLists.getTargetableCards(targetables, sa); - final List compTargetables = CardLists.filterControlledBy(targetables, ai); - - if (targetables.size() == 0) { - return false; - } - - if (!mandatory && (compTargetables.size() == 0)) { - return false; - } - - if (compTargetables.size() > 0) { - final List combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES); - CardLists.sortByEvaluateCreature(combatants); - if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { - Combat combat = game.getCombat(); - for (final Card c : combatants) { - if (c.getShield().isEmpty() && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) { - sa.getTargets().add(c); - return true; - } - } - } - - // TODO see if something on the stack is about to kill something i - // can target - - // choose my best X without regen - if (CardLists.getNotType(compTargetables, "Creature").isEmpty()) { - for (final Card c : combatants) { - if (c.getShield().isEmpty()) { - sa.getTargets().add(c); - return true; - } - } - sa.getTargets().add(combatants.get(0)); - return true; - } else { - CardLists.sortByCmcDesc(compTargetables); - for (final Card c : compTargetables) { - if (c.getShield().isEmpty()) { - sa.getTargets().add(c); - return true; - } - } - sa.getTargets().add(compTargetables.get(0)); - return true; - } - } - - sa.getTargets().add(ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true)); - 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 . + */ +package forge.ai.ability; + +import java.util.ArrayList; +import java.util.List; + +import forge.ai.ComputerUtil; +import forge.ai.ComputerUtilCard; +import forge.ai.ComputerUtilCombat; +import forge.ai.ComputerUtilCost; +import forge.ai.SpellAbilityAi; +import forge.game.Game; +import forge.game.GameObject; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +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.TargetRestrictions; +import forge.game.zone.ZoneType; + +/** + *

+ * AbilityFactory_Regenerate class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class RegenerateAi extends SpellAbilityAi { + + // Ex: A:SP$Regenerate | Cost$W | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$Regenerate + // target creature. + // http://www.slightlymagic.net/wiki/Forge_AbilityFactory#Regenerate + + // ************************************************************** + // ********************* Regenerate **************************** + // ************************************************************** + + @Override + protected boolean canPlayAI(Player ai, SpellAbility sa) { + final Card hostCard = sa.getSourceCard(); + final Cost abCost = sa.getPayCosts(); + final Game game = ai.getGame(); + final Combat combat = game.getCombat(); + + boolean chance = false; + if (abCost != null) { + // AI currently disabled for these costs + if (!ComputerUtilCost.checkLifeCost(ai, abCost, hostCard, 4, null)) { + return false; + } + + if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, hostCard)) { + return false; + } + + if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, abCost, hostCard)) { + return false; + } + + if (!ComputerUtilCost.checkRemoveCounterCost(abCost, 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 list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa); + + if (!game.getStack().isEmpty()) { + final List objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); + + for (final Card c : list) { + if (objects.contains(c)) { + chance = true; + } + } + } else { + if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + boolean flag = false; + + for (final Card c : list) { + if (c.getShield().isEmpty()) { + flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat); + } + } + + chance = flag; + } else { // if nothing on the stack, and it's not declare + // blockers. no need to regen + return false; + } + } + } else { + sa.resetTargets(); + // filter AIs battlefield by what I can target + List targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard); + targetables = CardLists.getTargetableCards(targetables, sa); + + if (targetables.size() == 0) { + return false; + } + + if (!game.getStack().isEmpty()) { + // check stack for something on the stack will kill anything i + // control + final List objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); + + final List threatenedTargets = new ArrayList(); + + for (final Card c : targetables) { + if (objects.contains(c) && c.getShield().isEmpty()) { + threatenedTargets.add(c); + } + } + + if (!threatenedTargets.isEmpty()) { + // Choose "best" of the remaining to regenerate + sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(threatenedTargets)); + chance = true; + } + } else { + if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + final List combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES); + CardLists.sortByEvaluateCreature(combatants); + + for (final Card c : combatants) { + if (c.getShield().isEmpty() && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) { + sa.getTargets().add(c); + chance = true; + break; + } + } + } + } + if (sa.getTargets().isEmpty()) { + return false; + } + } + + return chance; + } // regenerateCanPlayAI + + @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 = regenMandatoryTarget(ai, sa, mandatory); + } + + return chance; + } + + private static boolean regenMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) { + final Card hostCard = sa.getSourceCard(); + final Game game = ai.getGame(); + final TargetRestrictions tgt = sa.getTargetRestrictions(); + sa.resetTargets(); + // filter AIs battlefield by what I can target + List targetables = game.getCardsIn(ZoneType.Battlefield); + targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard); + targetables = CardLists.getTargetableCards(targetables, sa); + final List compTargetables = CardLists.filterControlledBy(targetables, ai); + + if (targetables.size() == 0) { + return false; + } + + if (!mandatory && (compTargetables.size() == 0)) { + return false; + } + + if (compTargetables.size() > 0) { + final List combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES); + CardLists.sortByEvaluateCreature(combatants); + if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + Combat combat = game.getCombat(); + for (final Card c : combatants) { + if (c.getShield().isEmpty() && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) { + sa.getTargets().add(c); + return true; + } + } + } + + // TODO see if something on the stack is about to kill something i + // can target + + // choose my best X without regen + if (CardLists.getNotType(compTargetables, "Creature").isEmpty()) { + for (final Card c : combatants) { + if (c.getShield().isEmpty()) { + sa.getTargets().add(c); + return true; + } + } + sa.getTargets().add(combatants.get(0)); + return true; + } else { + CardLists.sortByCmcDesc(compTargetables); + for (final Card c : compTargetables) { + if (c.getShield().isEmpty()) { + sa.getTargets().add(c); + return true; + } + } + sa.getTargets().add(compTargetables.get(0)); + return true; + } + } + + sa.getTargets().add(ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true)); + return true; + } + +} diff --git a/forge-game/src/main/java/forge/ai/ability/RegenerateAllAi.java b/forge-gui/src/main/java/forge/ai/ability/RegenerateAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/RegenerateAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/RegenerateAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/RemoveFromCombatAi.java b/forge-gui/src/main/java/forge/ai/ability/RemoveFromCombatAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/RemoveFromCombatAi.java rename to forge-gui/src/main/java/forge/ai/ability/RemoveFromCombatAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/RepeatAi.java b/forge-gui/src/main/java/forge/ai/ability/RepeatAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/RepeatAi.java rename to forge-gui/src/main/java/forge/ai/ability/RepeatAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/RepeatEachAi.java b/forge-gui/src/main/java/forge/ai/ability/RepeatEachAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/RepeatEachAi.java rename to forge-gui/src/main/java/forge/ai/ability/RepeatEachAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/RestartGameAi.java b/forge-gui/src/main/java/forge/ai/ability/RestartGameAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/RestartGameAi.java rename to forge-gui/src/main/java/forge/ai/ability/RestartGameAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/RevealAi.java b/forge-gui/src/main/java/forge/ai/ability/RevealAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/RevealAi.java rename to forge-gui/src/main/java/forge/ai/ability/RevealAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/RevealAiBase.java b/forge-gui/src/main/java/forge/ai/ability/RevealAiBase.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/RevealAiBase.java rename to forge-gui/src/main/java/forge/ai/ability/RevealAiBase.java diff --git a/forge-game/src/main/java/forge/ai/ability/RevealHandAi.java b/forge-gui/src/main/java/forge/ai/ability/RevealHandAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/RevealHandAi.java rename to forge-gui/src/main/java/forge/ai/ability/RevealHandAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/RollPlanarDiceAi.java b/forge-gui/src/main/java/forge/ai/ability/RollPlanarDiceAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/RollPlanarDiceAi.java rename to forge-gui/src/main/java/forge/ai/ability/RollPlanarDiceAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/SacrificeAi.java b/forge-gui/src/main/java/forge/ai/ability/SacrificeAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/SacrificeAi.java rename to forge-gui/src/main/java/forge/ai/ability/SacrificeAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/SacrificeAllAi.java b/forge-gui/src/main/java/forge/ai/ability/SacrificeAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/SacrificeAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/SacrificeAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ScryAi.java b/forge-gui/src/main/java/forge/ai/ability/ScryAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ScryAi.java rename to forge-gui/src/main/java/forge/ai/ability/ScryAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/SetStateAi.java b/forge-gui/src/main/java/forge/ai/ability/SetStateAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/SetStateAi.java rename to forge-gui/src/main/java/forge/ai/ability/SetStateAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ShuffleAi.java b/forge-gui/src/main/java/forge/ai/ability/ShuffleAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ShuffleAi.java rename to forge-gui/src/main/java/forge/ai/ability/ShuffleAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/StoreSVarAi.java b/forge-gui/src/main/java/forge/ai/ability/StoreSVarAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/StoreSVarAi.java rename to forge-gui/src/main/java/forge/ai/ability/StoreSVarAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/TapAi.java b/forge-gui/src/main/java/forge/ai/ability/TapAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/TapAi.java rename to forge-gui/src/main/java/forge/ai/ability/TapAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/TapAiBase.java b/forge-gui/src/main/java/forge/ai/ability/TapAiBase.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/TapAiBase.java rename to forge-gui/src/main/java/forge/ai/ability/TapAiBase.java diff --git a/forge-game/src/main/java/forge/ai/ability/TapAllAi.java b/forge-gui/src/main/java/forge/ai/ability/TapAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/TapAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/TapAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/TapOrUntapAi.java b/forge-gui/src/main/java/forge/ai/ability/TapOrUntapAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/TapOrUntapAi.java rename to forge-gui/src/main/java/forge/ai/ability/TapOrUntapAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/TapOrUntapAllAi.java b/forge-gui/src/main/java/forge/ai/ability/TapOrUntapAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/TapOrUntapAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/TapOrUntapAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/TokenAi.java b/forge-gui/src/main/java/forge/ai/ability/TokenAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/TokenAi.java rename to forge-gui/src/main/java/forge/ai/ability/TokenAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/TwoPilesAi.java b/forge-gui/src/main/java/forge/ai/ability/TwoPilesAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/TwoPilesAi.java rename to forge-gui/src/main/java/forge/ai/ability/TwoPilesAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/UnattachAllAi.java b/forge-gui/src/main/java/forge/ai/ability/UnattachAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/UnattachAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/UnattachAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/UntapAi.java b/forge-gui/src/main/java/forge/ai/ability/UntapAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/UntapAi.java rename to forge-gui/src/main/java/forge/ai/ability/UntapAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/UntapAllAi.java b/forge-gui/src/main/java/forge/ai/ability/UntapAllAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/UntapAllAi.java rename to forge-gui/src/main/java/forge/ai/ability/UntapAllAi.java diff --git a/forge-game/src/main/java/forge/ai/ability/ZoneExchangeAi.java b/forge-gui/src/main/java/forge/ai/ability/ZoneExchangeAi.java similarity index 100% rename from forge-game/src/main/java/forge/ai/ability/ZoneExchangeAi.java rename to forge-gui/src/main/java/forge/ai/ability/ZoneExchangeAi.java diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-gui/src/main/java/forge/game/Game.java similarity index 95% rename from forge-game/src/main/java/forge/game/Game.java rename to forge-gui/src/main/java/forge/game/Game.java index 193260439f2..de73f1b3fc1 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-gui/src/main/java/forge/game/Game.java @@ -30,6 +30,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; import com.google.common.eventbus.EventBus; +import forge.FThreads; import forge.game.card.Card; import forge.game.card.CardLists; import forge.game.card.CardPredicates; @@ -497,14 +498,16 @@ public class Game { } + private final boolean LOG_EVENTS = false; + /** * Fire only the events after they became real for gamestate and won't get replaced.
* The events are sent to UI, log and sound system. Network listeners are under development. */ public void fireEvent(GameEvent event) { -// if (LOG_EVENTS) { -// System.out.println("GE: " + event.toString() + " \t\t " + FThreads.debugGetStackTraceItem(4, true)); -// } + if (LOG_EVENTS) { + System.out.println("GE: " + event.toString() + " \t\t " + FThreads.debugGetStackTraceItem(4, true)); + } events.post(event); } diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-gui/src/main/java/forge/game/GameAction.java similarity index 97% rename from forge-game/src/main/java/forge/game/GameAction.java rename to forge-gui/src/main/java/forge/game/GameAction.java index d1bda4f7931..4176d206743 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-gui/src/main/java/forge/game/GameAction.java @@ -1,1672 +1,1677 @@ -/* - * 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 . - */ -package forge.game; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Multimap; - -import forge.Command; -import forge.card.CardCharacteristicName; -import forge.card.CardType; -import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; -import forge.game.ability.effects.AttachEffect; -import forge.game.card.Card; -import forge.game.card.CardFactory; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; -import forge.game.card.CardUtil; -import forge.game.card.CounterType; -import forge.game.event.GameEventCardChangeZone; -import forge.game.event.GameEventCardDestroyed; -import forge.game.event.GameEventCardRegenerated; -import forge.game.event.GameEventCardSacrificed; -import forge.game.event.GameEventCardStatsChanged; -import forge.game.event.GameEventFlipCoin; -import forge.game.event.GameEventGameStarted; -import forge.game.player.GameLossReason; -import forge.game.player.Player; -import forge.game.replacement.ReplacementResult; -import forge.game.spellability.AbilitySub; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; -import forge.game.staticability.StaticAbility; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerType; -import forge.game.trigger.ZCTrigger; -import forge.game.zone.PlayerZone; -import forge.game.zone.PlayerZoneBattlefield; -import forge.game.zone.Zone; -import forge.game.zone.ZoneType; -import forge.item.PaperCard; -import forge.util.Aggregates; -import forge.util.CollectionSuppliers; -import forge.util.Expressions; -import forge.util.ThreadUtil; -import forge.util.maps.HashMapOfLists; -import forge.util.maps.MapOfLists; - -/** - * Methods for common actions performed during a game. - * - * @author Forge - * @version $Id: GameAction.java 24258 2014-01-14 12:15:24Z swordshine $ - */ -public class GameAction { - /** - *

- * resetActivationsPerTurn. - *

- */ - - private final Game game; - - public GameAction(Game game0) { - game = game0; - } - - public final void resetActivationsPerTurn() { - final List all = game.getCardsInGame(); - - // Reset Activations per Turn - for (final Card card : all) { - for (final SpellAbility sa : card.getAllSpellAbilities()) { - sa.getRestrictions().resetTurnActivations(); - } - } - } - - /** - *

- * changeZone. - *

- * - * @param zoneFrom - * a {@link forge.game.zone.PlayerZone} object. - * @param zoneTo - * a {@link forge.game.zone.PlayerZone} object. - * @param c - * a {@link forge.game.card.Card} object. - * @param position TODO - * @return a {@link forge.game.card.Card} object. - */ - public Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer position) { - if (c.isCopiedSpell()) { - if ((zoneFrom != null)) { - zoneFrom.remove(c); - } - return c; - } - if (zoneFrom == null && !c.isToken()) { - zoneTo.add(c, position); - checkStaticAbilities(); - game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo)); - return c; - } - - boolean toBattlefield = zoneTo.is(ZoneType.Battlefield); - boolean fromBattlefield = zoneFrom != null && zoneFrom.is(ZoneType.Battlefield); - - //Rule 110.5g: A token that has left the battlefield can't move to another zone - if (c.isToken() && zoneFrom != null && !fromBattlefield && !zoneFrom.is(ZoneType.Command)) { - return c; - } - - boolean suppress = !c.isToken() && zoneFrom.equals(zoneTo); - - Card copied = null; - Card lastKnownInfo = null; - - if (c.isSplitCard() && !zoneTo.is(ZoneType.Stack)) { - c.setState(CardCharacteristicName.Original); - } - - // Don't copy Tokens, copy only cards leaving the battlefield - if (suppress || !fromBattlefield) { - lastKnownInfo = c; - copied = c; - } else { - lastKnownInfo = CardUtil.getLKICopy(c); - - if (!c.isToken()) { - if (c.isCloned()) { - c.switchStates(CardCharacteristicName.Cloner, CardCharacteristicName.Original); - c.setState(CardCharacteristicName.Original); - c.clearStates(CardCharacteristicName.Cloner); - if (c.isFlipCard()) { - c.clearStates(CardCharacteristicName.Flipped); - } - } - - copied = CardFactory.copyCard(c, false); - copied.setUnearthed(c.isUnearthed()); - copied.setTapped(false); - for (final Trigger trigger : copied.getTriggers()) { - trigger.setHostCard(copied); - } - for (final TriggerReplacementBase repl : copied.getReplacementEffects()) { - repl.setHostCard(copied); - } - if (c.getName().equals("Skullbriar, the Walking Grave")) { - copied.setCounters(c.getCounters()); - } - } else { //Token - copied = c; - } - } - - if (!suppress) { - if (zoneFrom == null) { - copied.getOwner().addInboundToken(copied); - } - - HashMap repParams = new HashMap(); - repParams.put("Event", "Moved"); - repParams.put("Affected", copied); - repParams.put("Origin", zoneFrom != null ? zoneFrom.getZoneType() : null); - repParams.put("Destination", zoneTo.getZoneType()); - - ReplacementResult repres = game.getReplacementHandler().run(repParams); - if (repres != ReplacementResult.NotReplaced) { - if (game.getStack().isResolving(c) && !zoneTo.is(ZoneType.Graveyard) && repres == ReplacementResult.Prevented) { - copied.getOwner().removeInboundToken(copied); - return this.moveToGraveyard(c); - } - copied.getOwner().removeInboundToken(copied); - return c; - } - - if (c.isUnearthed() && (zoneTo.is(ZoneType.Graveyard) || zoneTo.is(ZoneType.Hand) || zoneTo.is(ZoneType.Library))) { - zoneTo = c.getOwner().getZone(ZoneType.Exile); - c.setUnearthed(false); - } - } - - copied.getOwner().removeInboundToken(copied); - - if (c.wasSuspendCast()) { - copied = GameAction.addSuspendTriggers(c); - } - - if (suppress) { - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - } - - if (zoneFrom != null) { - if (fromBattlefield && c.isCreature() && game.getCombat() != null) { - if (!toBattlefield) { - game.getCombat().saveLKI(lastKnownInfo); - } - game.getCombat().removeFromCombat(c); - } - if ((zoneFrom.is(ZoneType.Library) || zoneFrom.is(ZoneType.PlanarDeck) || zoneFrom.is(ZoneType.SchemeDeck)) - && zoneFrom == zoneTo && position.equals(zoneFrom.size()) && position != 0) { - position--; - } - zoneFrom.remove(c); - } - - // "enter the battlefield as a copy" - apply code here - // but how to query for input here and continue later while the callers assume synchronous result? - zoneTo.add(copied, position); - - if (fromBattlefield) { - c.setZone(zoneTo); - } - - // Need to apply any static effects to produce correct triggers - checkStaticAbilities(); - - // play the change zone sound - game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo)); - - final HashMap runParams = new HashMap(); - runParams.put("Card", lastKnownInfo); - runParams.put("Origin", zoneFrom != null ? zoneFrom.getZoneType().name() : null); - runParams.put("Destination", zoneTo.getZoneType().name()); - game.getTriggerHandler().runTrigger(TriggerType.ChangesZone, runParams, false); - // AllZone.getStack().chooseOrderOfSimultaneousStackEntryAll(); - - if (suppress) { - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); - } - - if (zoneFrom == null) { - return copied; - } - - // remove all counters from the card if destination is not the battlefield - // UNLESS we're dealing with Skullbriar, the Walking Grave - if (!c.isToken() && (zoneTo.is(ZoneType.Hand) || zoneTo.is(ZoneType.Library) || - (!toBattlefield && !c.getName().equals("Skullbriar, the Walking Grave")))) { - copied.clearCounters(); - } - - if (!c.isToken() && !toBattlefield) { - copied.getCharacteristics().resetCardColor(); - copied.clearDevoured(); - } - - if (fromBattlefield) { - if (!c.isToken()) { - copied.setSuspendCast(false); - copied.setState(CardCharacteristicName.Original); - } - // Soulbond unpairing - if (c.isPaired()) { - c.getPairedWith().setPairedWith(null); - if (!c.isToken()) { - c.setPairedWith(null); - } - } - unattachCardLeavingBattlefield(copied); - } else if (toBattlefield) { - copied.setTimestamp(game.getNextTimestamp()); - for (String s : copied.getKeyword()) { - if (s.startsWith("May be played") || s.startsWith("You may look at this card.") - || s.startsWith("Your opponent may look at this card.")) { - copied.removeAllExtrinsicKeyword(s); - copied.removeHiddenExtrinsicKeyword(s); - } - } - for (Player p : game.getPlayers()) { - copied.getDamageHistory().setNotAttackedSinceLastUpkeepOf(p); - copied.getDamageHistory().setNotBlockedSinceLastUpkeepOf(p); - copied.getDamageHistory().setNotBeenBlockedSinceLastUpkeepOf(p); - } - } else if (zoneTo.is(ZoneType.Graveyard) || zoneTo.is(ZoneType.Hand) || zoneTo.is(ZoneType.Library)) { - copied.setTimestamp(game.getNextTimestamp()); - for (String s : copied.getKeyword()) { - if (s.startsWith("May be played") || s.startsWith("You may look at this card.") - || s.startsWith("Your opponent may look at this card.")) { - copied.removeAllExtrinsicKeyword(s); - copied.removeHiddenExtrinsicKeyword(s); - } - } - copied.clearOptionalCostsPaid(); - if (copied.isFaceDown()) { - copied.setState(CardCharacteristicName.Original); - } - } - - return copied; - } - - /** - * TODO: Write javadoc for this method. - * @param c - * @param copied - */ - private void unattachCardLeavingBattlefield(Card copied) { - // Handle unequipping creatures - if (copied.isEquipped()) { - final List equipments = new ArrayList(copied.getEquippedBy()); - for (final Card equipment : equipments) { - if (equipment.isInPlay()) { - equipment.unEquipCard(copied); - } - } - } - // Handle unfortifying lands - if (copied.isFortified()) { - final List fortifications = new ArrayList(copied.getFortifiedBy()); - for (final Card f : fortifications) { - if (f.isInPlay()) { - f.unFortifyCard(copied); - } - } - } - // equipment moving off battlefield - if (copied.isEquipping()) { - final Card equippedCreature = copied.getEquipping().get(0); - if (equippedCreature.isInPlay()) { - copied.unEquipCard(equippedCreature); - } - } - // fortifications moving off battlefield - if (copied.isFortifying()) { - final Card fortifiedLand = copied.getFortifying().get(0); - if (fortifiedLand.isInPlay()) { - copied.unFortifyCard(fortifiedLand); - } - } - // remove enchantments from creatures - if (copied.isEnchanted()) { - final List auras = new ArrayList(copied.getEnchantedBy()); - for (final Card aura : auras) { - aura.unEnchantEntity(copied); - } - } - // unenchant creature if moving aura - if (copied.isEnchanting()) { - copied.unEnchantEntity(copied.getEnchanting()); - } - } - - /** - *

- * moveTo. - *

- * - * @param zoneTo - * a {@link forge.game.zone.PlayerZone} object. - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link forge.game.card.Card} object. - */ - public final Card moveTo(final Zone zoneTo, Card c) { - // FThreads.assertExecutedByEdt(false); // This code must never be executed from EDT, - // use FThreads.invokeInNewThread to run code in a pooled thread - - return moveTo(zoneTo, c, null); - } - - public final Card moveTo(final Zone zoneTo, Card c, Integer position) { - // Ideally move to should never be called without a prevZone - // Remove card from Current Zone, if it has one - final Zone zoneFrom = game.getZoneOf(c); - // String prevName = prev != null ? prev.getZoneName() : ""; - - if (c.hasKeyword("If CARDNAME would leave the battlefield, exile it instead of putting it anywhere else.") - && !zoneTo.is(ZoneType.Exile)) { - final PlayerZone removed = c.getOwner().getZone(ZoneType.Exile); - c.removeAllExtrinsicKeyword("If CARDNAME would leave the battlefield, " - + "exile it instead of putting it anywhere else."); - return this.moveTo(removed, c); - } - - // Card lastKnownInfo = c; - - c = changeZone(zoneFrom, zoneTo, c, position); - - if (zoneTo.is(ZoneType.Stack)) { - c.setCastFrom(zoneFrom.getZoneType()); - } else if (zoneFrom == null) { - c.setCastFrom(null); - } else if (!(zoneTo.is(ZoneType.Battlefield) && zoneFrom.is(ZoneType.Stack))) { - c.setCastFrom(null); - } - - if (c.isAura() && zoneTo.is(ZoneType.Battlefield) && ((zoneFrom == null) || !zoneFrom.is(ZoneType.Stack)) - && !c.isEnchanting()) { - // TODO Need a way to override this for Abilities that put Auras - // into play attached to things - AttachEffect.attachAuraOnIndirectEnterBattlefield(c); - } - - return c; - } - - /** - * Controller change zone correction. - * - * @param c - * a Card object - */ - public final void controllerChangeZoneCorrection(final Card c) { - System.out.println("Correcting zone for " + c.toString()); - final Zone oldBattlefield = game.getZoneOf(c); - if (oldBattlefield == null || oldBattlefield.getZoneType() == ZoneType.Stack) { - return; - } - final PlayerZone newBattlefield = c.getController().getZone(oldBattlefield.getZoneType()); - - if (newBattlefield == null || oldBattlefield.equals(newBattlefield)) { - return; - } - - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - for (Player p : game.getPlayers()) { - ((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(false); - } - - final int tiz = c.getTurnInZone(); - - oldBattlefield.remove(c); - newBattlefield.add(c); - c.setSickness(true); - if (c.hasStartOfKeyword("Echo")) { - c.addExtrinsicKeyword("(Echo unpaid)"); - } - if (game.getPhaseHandler().inCombat()) { - game.getCombat().removeFromCombat(c); - } - - c.setTurnInZone(tiz); - - final HashMap runParams = new HashMap(); - runParams.put("Card", c); - game.getTriggerHandler().runTrigger(TriggerType.ChangesController, runParams, false); - - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); - for (Player p : game.getPlayers()) { - ((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(true); - } - c.runChangeControllerCommands(); - } - - /** - *

- * moveToStack. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link forge.game.card.Card} object. - */ - public final Card moveToStack(final Card c) { - final Zone stack = game.getStackZone(); - return this.moveTo(stack, c); - } - - /** - *

- * moveToGraveyard. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link forge.game.card.Card} object. - */ - public final Card moveToGraveyard(Card c) { - final Player owner = c.getOwner(); - final PlayerZone grave = owner.getZone(ZoneType.Graveyard); - final PlayerZone exile = owner.getZone(ZoneType.Exile); - - if (c.hasKeyword("If CARDNAME would be put into a graveyard, exile it instead.")) { - return this.moveTo(exile, c); - } - - // must put card in OWNER's graveyard not controller's - c = this.moveTo(grave, c); - - return c; - } - - - /** - *

- * moveToHand. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link forge.game.card.Card} object. - */ - public final Card moveToHand(final Card c) { - final PlayerZone hand = c.getOwner().getZone(ZoneType.Hand); - return this.moveTo(hand, c); - } - - /** - *

- * moveToPlay. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link forge.game.card.Card} object. - */ - public final Card moveToPlay(final Card c) { - final PlayerZone play = c.getController().getZone(ZoneType.Battlefield); - return this.moveTo(play, c); - } - - /** - *

- * moveToPlay. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param p - * a {@link forge.game.player.Player} object. - * @return a {@link forge.game.card.Card} object. - */ - public final Card moveToPlay(final Card c, final Player p) { - // move to a specific player's Battlefield - final PlayerZone play = p.getZone(ZoneType.Battlefield); - return this.moveTo(play, c); - } - - /** - *

- * moveToBottomOfLibrary. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link forge.game.card.Card} object. - */ - public final Card moveToBottomOfLibrary(final Card c) { - return this.moveToLibrary(c, -1); - } - - /** - *

- * moveToLibrary. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link forge.game.card.Card} object. - */ - public final Card moveToLibrary(final Card c) { - return this.moveToLibrary(c, 0); - } - - /** - *

- * moveToLibrary. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param libPosition - * a int. - * @return a {@link forge.game.card.Card} object. - */ - public final Card moveToLibrary(Card c, int libPosition) { - final PlayerZone library = c.getOwner().getZone(ZoneType.Library); - - if (libPosition == -1 || libPosition > library.size()) { - libPosition = library.size(); - } - return this.changeZone(game.getZoneOf(c), library, c, libPosition); - } - - /** - *

- * moveToVariantDeck. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param zone - * a {@link forge.game.card.Card} object. - * @param libPosition - * a int. - * @return a {@link forge.game.card.Card} object. - */ - public final Card moveToVariantDeck(Card c, ZoneType zone, int deckPosition) { - final PlayerZone deck = c.getOwner().getZone(zone); - if (deckPosition == -1 || deckPosition > deck.size()) { - deckPosition = deck.size(); - } - return this.changeZone(game.getZoneOf(c), deck, c, deckPosition); - } - - /** - *

- * exile. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link forge.game.card.Card} object. - */ - public final Card exile(final Card c) { - if (game.isCardExiled(c)) { - return c; - } - final PlayerZone removed = c.getOwner().getZone(ZoneType.Exile); - return moveTo(removed, c); - } - - /** - * Move to. - * - * @param name - * the name - * @param c - * the c - * @return the card - */ - public final Card moveTo(final ZoneType name, final Card c) { - return this.moveTo(name, c, 0); - } - - /** - *

- * moveTo. - *

- * - * @param name - * a {@link java.lang.String} object. - * @param c - * a {@link forge.game.card.Card} object. - * @param libPosition - * a int. - * @return a {@link forge.game.card.Card} object. - */ - public final Card moveTo(final ZoneType name, final Card c, final int libPosition) { - // Call specific functions to set PlayerZone, then move onto moveTo - switch(name) { - case Hand: return this.moveToHand(c); - case Library: return this.moveToLibrary(c, libPosition); - case Battlefield: return this.moveToPlay(c); - case Graveyard: return this.moveToGraveyard(c); - case Exile: return this.exile(c); - case Stack: return this.moveToStack(c); - case PlanarDeck: return this.moveToVariantDeck(c, ZoneType.PlanarDeck, libPosition); - case SchemeDeck: return this.moveToVariantDeck(c, ZoneType.SchemeDeck, libPosition); - default: // sideboard will also get there - return this.moveTo(c.getOwner().getZone(name), c); - } - } - - /** */ - public final void checkStaticAbilities() { - if (game.isGameOver()) { - return; - } - - // remove old effects - Set affectedCards = game.getStaticEffects().clearStaticEffects(); - game.getTriggerHandler().cleanUpTemporaryTriggers(); - game.getReplacementHandler().cleanUpTemporaryReplacements(); - - // search for cards with static abilities - final List allCards = game.getCardsInGame(); - final ArrayList staticAbilities = new ArrayList(); - final List staticList = new ArrayList(); - for (final Card c : allCards) { - for (int i = 0; i < c.getStaticAbilities().size(); i++) { - StaticAbility stAb = c.getCharacteristics().getStaticAbilities().get(i); - if (stAb.getMapParams().get("Mode").equals("Continuous")) { - staticAbilities.add(stAb); - } - if (stAb.isTemporary()) { - c.getCharacteristics().getStaticAbilities().remove(i); - i--; - } - } - if (!c.getStaticCommandList().isEmpty()) { - staticList.add(c); - } - } - - final Comparator comp = new Comparator() { - @Override - public int compare(final StaticAbility a, final StaticAbility b) { - int layerDelta = a.getLayer() - b.getLayer(); - if (layerDelta != 0) return layerDelta; - - long tsDelta = a.getHostCard().getTimestamp() - b.getHostCard().getTimestamp(); - return tsDelta == 0 ? 0 : tsDelta > 0 ? 1 : -1; - } - }; - Collections.sort(staticAbilities, comp); - for (final StaticAbility stAb : staticAbilities) { - List affectedHere = stAb.applyAbility("Continuous"); - if (null != affectedHere) { - affectedCards.addAll(affectedHere); - } - } - - // card state effects like Glorious Anthem -// for (final String effect : game.getStaticEffects().getStateBasedMap().keySet()) { -// final Function com = GameActionUtil.getCommands().get(effect); -// com.apply(game); -// } - - List lands = game.getCardsIn(ZoneType.Battlefield); - GameActionUtil.grantBasicLandsManaAbilities(CardLists.filter(lands, CardPredicates.Presets.LANDS)); - - for (final Card c : staticList) { - for (int i = 0; i < c.getStaticCommandList().size(); i++) { - final Object[] staticCheck = c.getStaticCommandList().get(i); - final String leftVar = (String) staticCheck[0]; - final String rightVar = (String) staticCheck[1]; - final Card affected = (Card) staticCheck[2]; - // calculate the affected card - final int sVar = AbilityUtils.calculateAmount(affected, leftVar, null); - final String svarOperator = rightVar.substring(0, 2); - final String svarOperand = rightVar.substring(2); - final int operandValue = AbilityUtils.calculateAmount(c, svarOperand, null); - if (Expressions.compare(sVar, svarOperator, operandValue)) { - ((Command) staticCheck[3]).run(); - c.getStaticCommandList().remove(i); - i--; - affectedCards.add(c); - } - } - } - // Exclude cards in hidden zones from update - Iterator it = affectedCards.iterator(); - while (it.hasNext()) { - Card c = it.next(); - if (c.isInZone(ZoneType.Library)) { - it.remove(); - } - } - - if (!affectedCards.isEmpty()) { - game.fireEvent(new GameEventCardStatsChanged(affectedCards)); - } - - } - - /** - *

- * checkStateEffects. - *

- */ - public final void checkStateEffects() { - // sol(10/29) added for Phase updates, state effects shouldn't be - // checked during Spell Resolution (except when persist-returning - if (game.getStack().isResolving()) { - return; - } - - // final JFrame frame = Singletons.getView().getFrame(); - // if (!frame.isDisplayable()) { - // return; - // } - - if (game.isGameOver()) { - return; - } - - // Max: I don't know where to put this! - but since it's a state based action, it must be in check state effects - if (game.getType() == GameType.Archenemy) { - game.archenemy904_10(); - } - - final boolean refreeze = game.getStack().isFrozen(); - game.getStack().setFrozen(true); - - // do this twice, sometimes creatures/permanents will survive when they - // shouldn't - for (int q = 0; q < 9; q++) { - boolean checkAgain = false; - - this.checkStaticAbilities(); - - final HashMap runParams = new HashMap(); - game.getTriggerHandler().runTrigger(TriggerType.Always, runParams, false); - - for (Player p : game.getPlayers()) { - for (Card c : p.getCardsIn(ZoneType.Battlefield)) { - if (!c.getController().equals(p)) { // should not be here - controllerChangeZoneCorrection(c); - checkAgain = true; - } - } - for (ZoneType zt : ZoneType.values()) { - if (zt == ZoneType.Battlefield) { - continue; - } - for (Card c : p.getCardsIn(zt)) { - // If a token is in a zone other than the battlefield, it ceases to exist. - checkAgain |= stateBasedAction704_5d(c); - } - } - } - - for (Card c : game.getCardsIn(ZoneType.Battlefield)) { - if (c.isCreature() && c.isPaired()) { // Soulbond unpairing (702.93e) - should not be here - Card partner = c.getPairedWith(); - if (!partner.isCreature() || c.getController() != partner.getController() || !c.isInZone(ZoneType.Battlefield)) { - c.setPairedWith(null); - partner.setPairedWith(null); - } - } - - if (c.isCreature()) { - // Rule 704.5f - Destroy (no regeneration) for toughness <= 0 - if (c.getNetDefense() <= 0) { - this.destroyNoRegeneration(c, null); - checkAgain = true; - } else - - // Rule 704.5g - Destroy due to lethal damage - if (c.getNetDefense() <= c.getDamage()) { - this.destroy(c, null); - checkAgain = true; - } - } - - checkAgain |= stateBasedAction704_5n(c); // Auras attached to illegal or not attached go to graveyard - checkAgain |= stateBasedAction704_5p(c); // Equipment and Fortifications - - if (c.isCreature() && c.isEnchanting()) { // Rule 704.5q - Creature attached to an object or player, becomes unattached - c.unEnchantEntity(c.getEnchanting()); - checkAgain = true; - } - - checkAgain |= stateBasedAction704_5r(c); // annihilate +1/+1 counters with -1/-1 ones - - if (c.getCounters(CounterType.DREAM) > 7 && c.hasKeyword("CARDNAME can't have more than seven dream counters on it.")) { - c.subtractCounter(CounterType.DREAM, c.getCounters(CounterType.DREAM) - 7); - checkAgain = true; - } - } - - if (game.getTriggerHandler().runWaitingTriggers()) { - checkAgain = true; - // Place triggers on stack - game.getStack().chooseOrderOfSimultaneousStackEntryAll(); - } - boolean yamazaki = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Brothers Yamazaki")).size() == 2; - for (Player p : game.getPlayers()) { - if (this.handleLegendRule(p, yamazaki)) { - checkAgain = true; - } - - if (this.handlePlaneswalkerRule(p)) { - checkAgain = true; - } - } - - if (!checkAgain) { - break; // do not continue the loop - } - } // for q=0;q<2 - - checkGameOverCondition(); - - if (!refreeze) { - game.getStack().unfreezeStack(); - } - } // checkStateEffects() - - /** - * TODO: Write javadoc for this method. - * @param checkAgain - * @param c - * @return - */ - private boolean stateBasedAction704_5n(Card c) { - boolean checkAgain = false; - if (!c.isAura()) { - return false; - } - - // Check if Card Aura is attached to is a legal target - final GameEntity entity = c.getEnchanting(); - SpellAbility sa = c.getSpells().get(0); - if (c.isBestowed()) { - for (SpellAbility s : c.getSpellAbilities()) { - if (s.getApi() == ApiType.Attach && s.hasParam("Bestow")) { - sa = s; - break; - } - } - } - - TargetRestrictions tgt = null; - if (sa != null) { - tgt = sa.getTargetRestrictions(); - } - - if (entity instanceof Card) { - final Card perm = (Card) entity; - ZoneType tgtZone = tgt.getZone().get(0); - - if (!perm.isInZone(tgtZone) || !perm.canBeEnchantedBy(c) || (perm.isPhasedOut() && !c.isPhasedOut())) { - c.unEnchantEntity(perm); - if (c.isBestowed()) { - c.unanimateBestow(); - game.fireEvent(new GameEventCardStatsChanged(c)); - return true; - } - else { - this.moveToGraveyard(c); - } - checkAgain = true; - } - } else if (entity instanceof Player) { - final Player pl = (Player) entity; - boolean invalid = false; - - if (tgt.canOnlyTgtOpponent() && !c.getController().getOpponent().equals(pl)) { - invalid = true; - } - else if (pl.hasProtectionFrom(c)) { - invalid = true; - } - if (invalid) { - c.unEnchantEntity(pl); - this.moveToGraveyard(c); - checkAgain = true; - } - } - - if (c.isInPlay() && !c.isEnchanting()) { - if (c.isBestowed()) { - c.unanimateBestow(); - game.fireEvent(new GameEventCardStatsChanged(c)); - return true; - } - this.moveToGraveyard(c); - checkAgain = true; - } - return checkAgain; - } - - /** - * TODO: Write javadoc for this method. - * @param checkAgain - * @param c - * @return - */ - private boolean stateBasedAction704_5p(Card c) { - boolean checkAgain = false; - if (c.isEquipped()) { - final List equipments = new ArrayList(c.getEquippedBy()); - for (final Card equipment : equipments) { - if (!equipment.isInPlay()) { - equipment.unEquipCard(c); - checkAgain = true; - } - } - } // if isEquipped() - - if (c.isFortified()) { - final List fortifications = new ArrayList(c.getFortifiedBy()); - for (final Card f : fortifications) { - if (!f.isInPlay()) { - f.unFortifyCard(c); - checkAgain = true; - } - } - } // if isFortified() - - if (c.isEquipping()) { - final Card equippedCreature = c.getEquipping().get(0); - if (!equippedCreature.isCreature() || !equippedCreature.isInPlay() - || !equippedCreature.canBeEquippedBy(c) - || (equippedCreature.isPhasedOut() && !c.isPhasedOut()) - || !c.isEquipment()) { - c.unEquipCard(equippedCreature); - checkAgain = true; - } - // make sure any equipment that has become a creature stops equipping - if (c.isCreature()) { - c.unEquipCard(equippedCreature); - checkAgain = true; - } - } // if isEquipping() - - if (c.isFortifying()) { - final Card fortifiedLand = c.getFortifying().get(0); - if (!fortifiedLand.isLand() || !fortifiedLand.isInPlay() - || (fortifiedLand.isPhasedOut() && !c.isPhasedOut())) { - c.unFortifyCard(fortifiedLand); - checkAgain = true; - } - // make sure any fortification that has become a creature stops fortifying - if (c.isCreature()) { - c.unFortifyCard(fortifiedLand); - checkAgain = true; - } - } // if isFortifying() - return checkAgain; - } - - /** - * TODO: Write javadoc for this method. - * @param checkAgain - * @param c - * @return - */ - private boolean stateBasedAction704_5r(Card c) { - boolean checkAgain = false; - // +1/+1 counters should erase -1/-1 counters - if (c.getCounters(CounterType.P1P1) > 0 && c.getCounters(CounterType.M1M1) > 0) { - int plusOneCounters = c.getCounters(CounterType.P1P1); - int minusOneCounters = c.getCounters(CounterType.M1M1); - - if (plusOneCounters >= minusOneCounters) c.getCounters().remove(CounterType.M1M1); - if (plusOneCounters <= minusOneCounters) c.getCounters().remove(CounterType.P1P1); - - int diff = plusOneCounters - minusOneCounters; - if (diff != 0) { - CounterType ct = diff > 0 ? CounterType.P1P1 : CounterType.M1M1; - c.getCounters().put(ct, Math.abs(diff)); - } - checkAgain = true; - } - return checkAgain; - } - - // If a token is in a zone other than the battlefield, it ceases to exist. - private boolean stateBasedAction704_5d(Card c) { - boolean checkAgain = false; - if (c.isToken()) { - final Zone zoneFrom = game.getZoneOf(c); - if (!zoneFrom.is(ZoneType.Battlefield) && !zoneFrom.is(ZoneType.Command)) { - zoneFrom.remove(c); - checkAgain = true; - } - } - return checkAgain; - } - - public void checkGameOverCondition() { - // award loses as SBE - List losers = null; - for (Player p : this.game.getPlayers()) { - if (p.checkLoseCondition()) { // this will set appropriate outcomes - // Run triggers - if (losers == null) { - losers = new ArrayList(3); - } - losers.add(p); - } - } - - GameEndReason reason = null; - // Has anyone won by spelleffect? - for (Player p : this.game.getPlayers()) { - if (!p.hasWon()) { - continue; - } - - // then the rest have lost! - reason = GameEndReason.WinsGameSpellEffect; - for (Player pl : this.game.getPlayers()) { - if (pl.equals(p)) { - continue; - } - - if (!pl.loseConditionMet(GameLossReason.OpponentWon, p.getOutcome().altWinSourceName)) { - reason = null; // they cannot lose! - } else { - if (losers == null) { - losers = new ArrayList(3); - } - losers.add(p); - } - } - break; - } - - // need a separate loop here, otherwise ConcurrentModificationException is raised - if (losers != null) { - for (Player p : losers) { - this.game.onPlayerLost(p); - } - } - - if (reason == null) { - int cntNotLost = Iterables.size(Iterables.filter(game.getPlayers(), Player.Predicates.NOT_LOST)); - if (cntNotLost == 1) { - reason = GameEndReason.AllOpponentsLost; - } - else if (cntNotLost == 0) { - reason = GameEndReason.Draw; - } - else { - return; - } - } - - // Clear Simultaneous triggers at the end of the game - game.setGameOver(reason); - game.getStack().clearSimultaneousStack(); - } - - /** - *

- * destroyPlaneswalkers. - *

- */ - private boolean handlePlaneswalkerRule(Player p) { - // get all Planeswalkers - final List list = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS); - - boolean recheck = false; - final Multimap uniqueWalkers = ArrayListMultimap.create(); - for (Card c : list) { - if (c.getCounters(CounterType.LOYALTY) <= 0) { - moveToGraveyard(c); - // Play the Destroy sound - game.fireEvent(new GameEventCardDestroyed()); - recheck = true; - } - - - for (final String type : c.getType()) { - if (CardType.isAPlaneswalkerType(type)) { - uniqueWalkers.put(type, c); - } - } - } - - for (String key : uniqueWalkers.keySet()) { - Collection duplicates = uniqueWalkers.get(key); - if (duplicates.size() < 2) { - continue; - } - - recheck = true; - - Card toKeep = p.getController().chooseSingleEntityForEffect(duplicates, new AbilitySub(ApiType.InternalLegendaryRule, null, null, null), "You have multiple planeswalkers of type \""+key+"\"in play.\n\nChoose one to stay on battlefield (the rest will be moved to graveyard)"); - for (Card c: duplicates) { - if (c != toKeep) { - moveToGraveyard(c); - } - } - } - return recheck; - } - - /** - *

- * destroyLegendaryCreatures. - *

- */ - private boolean handleLegendRule(Player p, boolean yama) { - final List a = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), "Legendary"); - if (a.isEmpty() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) { - return false; - } - boolean recheck = false; - if (yama) { - List yamazaki = CardLists.filter(a, CardPredicates.nameEquals("Brothers Yamazaki")); - a.removeAll(yamazaki); - } - - Multimap uniqueLegends = ArrayListMultimap.create(); - for (Card c : a) { - if (!c.isFaceDown()) { - uniqueLegends.put(c.getName(), c); - } - } - - for (String name : uniqueLegends.keySet()) { - Collection cc = uniqueLegends.get(name); - if (cc.size() < 2) { - continue; - } - - recheck = true; - - Card toKeep = p.getController().chooseSingleEntityForEffect(cc, new AbilitySub(ApiType.InternalLegendaryRule, null, null, null), "You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)"); - for (Card c: cc) { - if (c != toKeep) { - sacrificeDestroy(c); - } - } - game.fireEvent(new GameEventCardDestroyed()); - } - - return recheck; - } // destroyLegendaryCreatures() - - - public final boolean sacrifice(final Card c, final SpellAbility source) { - if (!c.canBeSacrificedBy(source)) { - return false; - } - - this.sacrificeDestroy(c); - - // Play the Sacrifice sound - game.fireEvent(new GameEventCardSacrificed()); - - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("Card", c); - runParams.put("Cause", source); - game.getTriggerHandler().runTrigger(TriggerType.Sacrificed, runParams, false); - return true; - } - - /** - *

- * destroy. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean destroy(final Card c, final SpellAbility sa) { - if (!c.canBeDestroyed()) { - return false; - } - - if (c.canBeShielded() && (!c.isCreature() || c.getNetDefense() > 0) - && (!c.getShield().isEmpty() || c.hasKeyword("If CARDNAME would be destroyed, regenerate it."))) { - c.subtractShield(c.getController().getController().chooseRegenerationShield(c)); - c.setDamage(0); - c.tap(); - c.addRegeneratedThisTurn(); - if (game.getCombat() != null) { - game.getCombat().removeFromCombat(c); - } - - // Play the Regen sound - game.fireEvent(new GameEventCardRegenerated()); - - return false; - } - - return this.destroyNoRegeneration(c, sa); - } - - /** - *

- * destroyNoRegeneration. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean destroyNoRegeneration(final Card c, final SpellAbility sa) { - Player activator = null; - if (!c.canBeDestroyed()) { - return false; - } - - if (c.isEnchanted()) { - for (Card e : c.getEnchantedBy()) { - CardFactoryUtil.refreshTotemArmor(e); - } - } - - // Replacement effects - final HashMap repRunParams = new HashMap(); - repRunParams.put("Event", "Destroy"); - repRunParams.put("Source", sa); - repRunParams.put("Card", c); - repRunParams.put("Affected", c); - - if (game.getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) { - return false; - } - - - if (sa != null) { - activator = sa.getActivatingPlayer(); - } - - // Play the Destroy sound - game.fireEvent(new GameEventCardDestroyed()); - - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("Card", c); - runParams.put("Causer", activator); - game.getTriggerHandler().runTrigger(TriggerType.Destroyed, runParams, false); - - return this.sacrificeDestroy(c); - } - - /** - *

- * addSuspendTriggers. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link forge.game.card.Card} object. - */ - private static Card addSuspendTriggers(final Card c) { - if (c.getSVar("HasteFromSuspend").equals("True")) { - return c; - } - c.setSVar("HasteFromSuspend", "True"); - - final Command intoPlay = new Command() { - private static final long serialVersionUID = -4514610171270596654L; - - @Override - public void run() { - if (c.isInPlay() && c.isCreature()) { - c.addExtrinsicKeyword("Haste"); - } - } // execute() - }; - - c.addComesIntoPlayCommand(intoPlay); - - final Command loseControl = new Command() { - private static final long serialVersionUID = -4514610171270596654L; - - @Override - public void run() { - if (c.getSVar("HasteFromSuspend").equals("True")) { - c.setSVar("HasteFromSuspend", "False"); - c.removeExtrinsicKeyword("Haste"); - } - } // execute() - }; - - c.addChangeControllerCommand(loseControl); - c.addLeavesPlayCommand(loseControl); - return c; - } - - /** - *

- * sacrificeDestroy. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean sacrificeDestroy(final Card c) { - if (!c.isInPlay()) { - return false; - } - - boolean persist = (c.hasKeyword("Persist") && c.getCounters(CounterType.M1M1) == 0) && !c.isToken(); - boolean undying = (c.hasKeyword("Undying") && c.getCounters(CounterType.P1P1) == 0) && !c.isToken(); - - final Card newCard = this.moveToGraveyard(c); - - // don't trigger persist/undying if the dying has been replaced - if (newCard == null || !newCard.isInZone(ZoneType.Graveyard)) { - persist = false; - undying = false; - } - - // Destroy needs to be called with Last Known Information - c.executeTrigger(ZCTrigger.DESTROY); - - // System.out.println("Card " + c.getName() + - // " is getting sent to GY, and this turn it got damaged by: "); - - if (persist) { - final Card persistCard = newCard; - String effect = String.format("AB$ ChangeZone | Cost$ 0 | Defined$ CardUID_%d" + - " | Origin$ Graveyard | Destination$ Battlefield | WithCounters$ M1M1_1", - persistCard.getUniqueNumber()); - SpellAbility persistAb = AbilityFactory.getAbility(effect, c); - persistAb.setTrigger(true); - persistAb.setStackDescription(newCard.getName() + " - Returning from Persist"); - persistAb.setDescription(newCard.getName() + " - Returning from Persist"); - persistAb.setActivatingPlayer(c.getController()); - - game.getStack().addSimultaneousStackEntry(persistAb); - } - - if (undying) { - final Card undyingCard = newCard; - String effect = String.format("AB$ ChangeZone | Cost$ 0 | Defined$ CardUID_%d |" + - " Origin$ Graveyard | Destination$ Battlefield | WithCounters$ P1P1_1", - undyingCard.getUniqueNumber()); - SpellAbility undyingAb = AbilityFactory.getAbility(effect, c); - undyingAb.setTrigger(true); - undyingAb.setStackDescription(newCard.getName() + " - Returning from Undying"); - undyingAb.setDescription(newCard.getName() + " - Returning from Undying"); - undyingAb.setActivatingPlayer(c.getController()); - - game.getStack().addSimultaneousStackEntry(undyingAb); - } - return true; - } // sacrificeDestroy() - - public void reveal(Collection cards, Player cardOwner) { - reveal(cards, cardOwner, true); - } - - public void reveal(Collection cards, Player cardOwner, boolean dontRevealToOwner) { - reveal(cards, cardOwner, dontRevealToOwner, null); - } - - public void reveal(Collection cards, Player cardOwner, boolean dontRevealToOwner, String messagePrefix) { - Card firstCard = Iterables.getFirst(cards, null); - if (firstCard == null) { - return; - } - reveal(cards, game.getZoneOf(firstCard).getZoneType(), cardOwner, dontRevealToOwner, messagePrefix); - } - - public void reveal(Collection cards, ZoneType zt, Player cardOwner, boolean dontRevealToOwner, String messagePrefix) { - for (Player p : game.getPlayers()) { - if (dontRevealToOwner && cardOwner == p) { - continue; - } - p.getController().reveal(cards, zt, cardOwner, messagePrefix); - } - } - - public void revealAnte(String title, Multimap removedAnteCards) { - for (Player p : game.getPlayers()) { - p.getController().revealAnte(title, removedAnteCards); - } - } - - /** Delivers a message to all players. (use reveal to show Cards) */ - public void nofityOfValue(SpellAbility saSource, GameObject relatedTarget, String value, Player playerExcept) { - for (Player p : game.getPlayers()) { - if (playerExcept == p) continue; - p.getController().notifyOfValue(saSource, relatedTarget, value); - } - } - - public void startGame(GameOutcome lastGameOutcome) { - Player first = determineFirstTurnPlayer(lastGameOutcome); - - do { - if (game.isGameOver()) { break; } // conceded during "play or draw" - - // FControl should determine now if there are any human players. - // Where there are none, it should bring up speed controls - game.fireEvent(new GameEventGameStarted(game.getType(), first, game.getPlayers())); - - game.setAge(GameStage.Mulligan); - for (final Player p1 : game.getPlayers()) { - p1.drawCards(p1.getMaxHandSize()); - } - - performMulligans(first, game.getType() == GameType.Commander); - if (game.isGameOver()) { break; } // conceded during "mulligan" prompt - - game.setAge(GameStage.Play); - - // - if (game.getType() == GameType.Planechase) { - first.initPlane(); - } - - runOpeningHandActions(first); - checkStateEffects(); // why? - - // Run Trigger beginning of the game - final HashMap runParams = new HashMap(); - game.getTriggerHandler().runTrigger(TriggerType.NewGame, runParams, true); - // - - game.getPhaseHandler().startFirstTurn(first); - - first = game.getPhaseHandler().getPlayerTurn(); // needed only for restart - } while (game.getAge() == GameStage.RestartedByKarn); - } - - private Player determineFirstTurnPlayer(final GameOutcome lastGameOutcome) { - // Only cut/coin toss if it's the first game of the match - Player goesFirst = null; - - // 904.6: in Archenemy games the Archenemy goes first - if (game != null && game.getType() == GameType.Archenemy) { - for (Player p : game.getPlayers()) { - if (p.isArchenemy()) { - return p; - } - } - } - - boolean isFirstGame = lastGameOutcome == null; - if (isFirstGame) { - game.fireEvent(new GameEventFlipCoin()); // Play the Flip Coin sound - goesFirst = Aggregates.random(game.getPlayers()); - } else { - for (Player p : game.getPlayers()) { - if (!lastGameOutcome.isWinner(p.getLobbyPlayer())) { - goesFirst = p; - break; - } - } - } - - if (goesFirst == null) { - // This happens in hotseat matches when 2 equal lobbyplayers play. - // Noone of them has lost, so cannot decide who goes first . - goesFirst = game.getPlayers().get(0); // does not really matter who plays first - it's controlled from the same computer. - } - - boolean willPlay = goesFirst.getController().getWillPlayOnFirstTurn(isFirstGame); - goesFirst = willPlay ? goesFirst : goesFirst.getOpponent(); - return goesFirst; - } - - private void performMulligans(final Player firstPlayer, final boolean isCommander) { - List whoCanMulligan = Lists.newArrayList(game.getPlayers()); - int offset = whoCanMulligan.indexOf(firstPlayer); - - // Have to cycle-shift the list to get the first player on index 0 - for (int i = 0; i < offset; i++) { - whoCanMulligan.add(whoCanMulligan.remove(0)); - } - - boolean[] hasKept = new boolean[whoCanMulligan.size()]; - int[] handSize = new int[whoCanMulligan.size()]; - for (int i = 0; i < whoCanMulligan.size(); i++) { - hasKept[i] = false; - handSize[i] = whoCanMulligan.get(i).getZone(ZoneType.Hand).size(); - } - - MapOfLists exiledDuringMulligans = new HashMapOfLists(CollectionSuppliers.arrayLists()); - - // rule 103.4b - boolean isMultiPlayer = game.getPlayers().size() > 2; - int mulliganDelta = isMultiPlayer ? 0 : 1; - - boolean allKept; - do { - allKept = true; - for (int i = 0; i < whoCanMulligan.size(); i++) { - if (hasKept[i]) continue; - - Player p = whoCanMulligan.get(i); - List toMulligan = p.canMulligan() ? p.getController().getCardsToMulligan(isCommander, firstPlayer) : null; - - if (game.isGameOver()) // conceded on mulligan prompt - return; - - if (toMulligan != null && !toMulligan.isEmpty()) { - if (!isCommander) { - toMulligan = new ArrayList(p.getCardsIn(ZoneType.Hand)); - for (final Card c : toMulligan) { - moveToLibrary(c); - } - p.shuffle(null); - p.drawCards(handSize[i] - mulliganDelta); - } else { - List toExile = Lists.newArrayList(toMulligan); - for (Card c : toExile) { - exile(c); - } - exiledDuringMulligans.addAll(p, toExile); - p.drawCards(toExile.size() - 1); - } - - p.onMulliganned(); - allKept = false; - } else { - game.getGameLog().add(GameLogEntryType.MULLIGAN, p.getName() + " has kept a hand of " + p.getZone(ZoneType.Hand).size() + " cards"); - hasKept[i] = true; - } - } - mulliganDelta++; - } while (!allKept); - - if (isCommander) { - for (Entry> kv : exiledDuringMulligans.entrySet()) { - Player p = kv.getKey(); - Collection cc = kv.getValue(); - for (Card c : cc) { - moveToLibrary(c); - } - p.shuffle(null); - } - } - } - - private void runOpeningHandActions(final Player first) { - Player takesAction = first; - do { - List usableFromOpeningHand = new ArrayList(); - - // Select what can be activated from a given hand - for (final Card c : takesAction.getCardsIn(ZoneType.Hand)) { - for (String kw : c.getKeyword()) { - if (kw.startsWith("MayEffectFromOpeningHand")) { - String[] split = kw.split(":"); - final String effName = split[1]; - if (split.length > 2 && split[2].equalsIgnoreCase("!PlayFirst") && first == takesAction) { - continue; - } - - final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); - effect.setActivatingPlayer(takesAction); - - usableFromOpeningHand.add(effect); - } - } - } - - // Players are supposed to return the effects in an order they want those to be resolved (Rule 103.5) - if (!usableFromOpeningHand.isEmpty()) { - usableFromOpeningHand = takesAction.getController().chooseSaToActivateFromOpeningHand(usableFromOpeningHand); - } - - for (final SpellAbility sa : usableFromOpeningHand) { - if (!takesAction.getZone(ZoneType.Hand).contains(sa.getSourceCard())) { - continue; - } - - takesAction.getController().playSpellAbilityNoStack(sa, true); - } - takesAction = game.getNextPlayerAfter(takesAction); - } while (takesAction != first); - // state effects are checked only when someone gets priority - } - - // Invokes given runnable in Game thread pool - used to start game and perform actions from UI (when game-0 waits for input) - public void invoke(final Runnable proc) { - if (ThreadUtil.isGameThread()) { - proc.run(); - } - else { - ThreadUtil.invokeInGameThread(proc); - } - } -} +/* + * 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 . + */ +package forge.game; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; + +import forge.Command; +import forge.FThreads; +import forge.card.CardCharacteristicName; +import forge.card.CardType; +import forge.game.ability.AbilityFactory; +import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; +import forge.game.ability.effects.AttachEffect; +import forge.game.card.Card; +import forge.game.card.CardFactory; +import forge.game.card.CardFactoryUtil; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +import forge.game.card.CardUtil; +import forge.game.card.CounterType; +import forge.game.event.GameEventCardChangeZone; +import forge.game.event.GameEventCardDestroyed; +import forge.game.event.GameEventCardRegenerated; +import forge.game.event.GameEventCardSacrificed; +import forge.game.event.GameEventCardStatsChanged; +import forge.game.event.GameEventFlipCoin; +import forge.game.event.GameEventGameStarted; +import forge.game.player.GameLossReason; +import forge.game.player.Player; +import forge.game.replacement.ReplacementResult; +import forge.game.spellability.AbilitySub; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.TargetRestrictions; +import forge.game.staticability.StaticAbility; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerType; +import forge.game.trigger.ZCTrigger; +import forge.game.zone.PlayerZone; +import forge.game.zone.PlayerZoneBattlefield; +import forge.game.zone.Zone; +import forge.game.zone.ZoneType; +import forge.item.PaperCard; +import forge.util.Aggregates; +import forge.util.CollectionSuppliers; +import forge.util.Expressions; +import forge.util.ThreadUtil; +import forge.util.maps.HashMapOfLists; +import forge.util.maps.MapOfLists; + +/** + * Methods for common actions performed during a game. + * + * @author Forge + * @version $Id$ + */ +public class GameAction { + /** + *

+ * resetActivationsPerTurn. + *

+ */ + + private final Game game; + + public GameAction(Game game0) { + game = game0; + } + + public final void resetActivationsPerTurn() { + final List all = game.getCardsInGame(); + + // Reset Activations per Turn + for (final Card card : all) { + for (final SpellAbility sa : card.getAllSpellAbilities()) { + sa.getRestrictions().resetTurnActivations(); + } + } + } + + /** + *

+ * changeZone. + *

+ * + * @param zoneFrom + * a {@link forge.game.zone.PlayerZone} object. + * @param zoneTo + * a {@link forge.game.zone.PlayerZone} object. + * @param c + * a {@link forge.game.card.Card} object. + * @param position TODO + * @return a {@link forge.game.card.Card} object. + */ + public Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer position) { + if (c.isCopiedSpell()) { + if ((zoneFrom != null)) { + zoneFrom.remove(c); + } + return c; + } + if (zoneFrom == null && !c.isToken()) { + zoneTo.add(c, position); + checkStaticAbilities(); + game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo)); + return c; + } + + boolean toBattlefield = zoneTo.is(ZoneType.Battlefield); + boolean fromBattlefield = zoneFrom != null && zoneFrom.is(ZoneType.Battlefield); + + //Rule 110.5g: A token that has left the battlefield can't move to another zone + if (c.isToken() && zoneFrom != null && !fromBattlefield && !zoneFrom.is(ZoneType.Command)) { + return c; + } + + boolean suppress = !c.isToken() && zoneFrom.equals(zoneTo); + + Card copied = null; + Card lastKnownInfo = null; + + if (c.isSplitCard() && !zoneTo.is(ZoneType.Stack)) { + c.setState(CardCharacteristicName.Original); + } + + // Don't copy Tokens, copy only cards leaving the battlefield + if (suppress || !fromBattlefield) { + lastKnownInfo = c; + copied = c; + } else { + lastKnownInfo = CardUtil.getLKICopy(c); + + if (!c.isToken()) { + if (c.isCloned()) { + c.switchStates(CardCharacteristicName.Cloner, CardCharacteristicName.Original); + c.setState(CardCharacteristicName.Original); + c.clearStates(CardCharacteristicName.Cloner); + if (c.isFlipCard()) { + c.clearStates(CardCharacteristicName.Flipped); + } + } + + copied = CardFactory.copyCard(c, false); + copied.setUnearthed(c.isUnearthed()); + copied.setTapped(false); + for (final Trigger trigger : copied.getTriggers()) { + trigger.setHostCard(copied); + } + for (final TriggerReplacementBase repl : copied.getReplacementEffects()) { + repl.setHostCard(copied); + } + if (c.getName().equals("Skullbriar, the Walking Grave")) { + copied.setCounters(c.getCounters()); + } + } else { //Token + copied = c; + } + } + + if (!suppress) { + if (zoneFrom == null) { + copied.getOwner().addInboundToken(copied); + } + + HashMap repParams = new HashMap(); + repParams.put("Event", "Moved"); + repParams.put("Affected", copied); + repParams.put("Origin", zoneFrom != null ? zoneFrom.getZoneType() : null); + repParams.put("Destination", zoneTo.getZoneType()); + + ReplacementResult repres = game.getReplacementHandler().run(repParams); + if (repres != ReplacementResult.NotReplaced) { + if (game.getStack().isResolving(c) && !zoneTo.is(ZoneType.Graveyard) && repres == ReplacementResult.Prevented) { + copied.getOwner().removeInboundToken(copied); + return this.moveToGraveyard(c); + } + copied.getOwner().removeInboundToken(copied); + return c; + } + + if (c.isUnearthed() && (zoneTo.is(ZoneType.Graveyard) || zoneTo.is(ZoneType.Hand) || zoneTo.is(ZoneType.Library))) { + zoneTo = c.getOwner().getZone(ZoneType.Exile); + c.setUnearthed(false); + } + } + + copied.getOwner().removeInboundToken(copied); + + if (c.wasSuspendCast()) { + copied = GameAction.addSuspendTriggers(c); + } + + if (suppress) { + game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); + } + + if (zoneFrom != null) { + if (fromBattlefield && c.isCreature() && game.getCombat() != null) { + if (!toBattlefield) { + game.getCombat().saveLKI(lastKnownInfo); + } + game.getCombat().removeFromCombat(c); + } + if ((zoneFrom.is(ZoneType.Library) || zoneFrom.is(ZoneType.PlanarDeck) || zoneFrom.is(ZoneType.SchemeDeck)) + && zoneFrom == zoneTo && position.equals(zoneFrom.size()) && position != 0) { + position--; + } + zoneFrom.remove(c); + } + + // "enter the battlefield as a copy" - apply code here + // but how to query for input here and continue later while the callers assume synchronous result? + zoneTo.add(copied, position); + + if (fromBattlefield) { + c.setZone(zoneTo); + } + + // Need to apply any static effects to produce correct triggers + checkStaticAbilities(); + + // play the change zone sound + game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo)); + + final HashMap runParams = new HashMap(); + runParams.put("Card", lastKnownInfo); + runParams.put("Origin", zoneFrom != null ? zoneFrom.getZoneType().name() : null); + runParams.put("Destination", zoneTo.getZoneType().name()); + game.getTriggerHandler().runTrigger(TriggerType.ChangesZone, runParams, false); + // AllZone.getStack().chooseOrderOfSimultaneousStackEntryAll(); + + if (suppress) { + game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + } + + if (zoneFrom == null) { + return copied; + } + + // remove all counters from the card if destination is not the battlefield + // UNLESS we're dealing with Skullbriar, the Walking Grave + if (!c.isToken() && (zoneTo.is(ZoneType.Hand) || zoneTo.is(ZoneType.Library) || + (!toBattlefield && !c.getName().equals("Skullbriar, the Walking Grave")))) { + copied.clearCounters(); + } + + if (!c.isToken() && !toBattlefield) { + copied.getCharacteristics().resetCardColor(); + copied.clearDevoured(); + } + + if (fromBattlefield) { + if (!c.isToken()) { + copied.setSuspendCast(false); + copied.setState(CardCharacteristicName.Original); + } + // Soulbond unpairing + if (c.isPaired()) { + c.getPairedWith().setPairedWith(null); + if (!c.isToken()) { + c.setPairedWith(null); + } + } + unattachCardLeavingBattlefield(copied); + } else if (toBattlefield) { + copied.setTimestamp(game.getNextTimestamp()); + for (String s : copied.getKeyword()) { + if (s.startsWith("May be played") || s.startsWith("You may look at this card.") + || s.startsWith("Your opponent may look at this card.")) { + copied.removeAllExtrinsicKeyword(s); + copied.removeHiddenExtrinsicKeyword(s); + } + } + for (Player p : game.getPlayers()) { + copied.getDamageHistory().setNotAttackedSinceLastUpkeepOf(p); + copied.getDamageHistory().setNotBlockedSinceLastUpkeepOf(p); + copied.getDamageHistory().setNotBeenBlockedSinceLastUpkeepOf(p); + } + } else if (zoneTo.is(ZoneType.Graveyard) || zoneTo.is(ZoneType.Hand) || zoneTo.is(ZoneType.Library)) { + copied.setTimestamp(game.getNextTimestamp()); + for (String s : copied.getKeyword()) { + if (s.startsWith("May be played") || s.startsWith("You may look at this card.") + || s.startsWith("Your opponent may look at this card.")) { + copied.removeAllExtrinsicKeyword(s); + copied.removeHiddenExtrinsicKeyword(s); + } + } + copied.clearOptionalCostsPaid(); + if (copied.isFaceDown()) { + copied.setState(CardCharacteristicName.Original); + } + } + + return copied; + } + + /** + * TODO: Write javadoc for this method. + * @param c + * @param copied + */ + private void unattachCardLeavingBattlefield(Card copied) { + // Handle unequipping creatures + if (copied.isEquipped()) { + final List equipments = new ArrayList(copied.getEquippedBy()); + for (final Card equipment : equipments) { + if (equipment.isInPlay()) { + equipment.unEquipCard(copied); + } + } + } + // Handle unfortifying lands + if (copied.isFortified()) { + final List fortifications = new ArrayList(copied.getFortifiedBy()); + for (final Card f : fortifications) { + if (f.isInPlay()) { + f.unFortifyCard(copied); + } + } + } + // equipment moving off battlefield + if (copied.isEquipping()) { + final Card equippedCreature = copied.getEquipping().get(0); + if (equippedCreature.isInPlay()) { + copied.unEquipCard(equippedCreature); + } + } + // fortifications moving off battlefield + if (copied.isFortifying()) { + final Card fortifiedLand = copied.getFortifying().get(0); + if (fortifiedLand.isInPlay()) { + copied.unFortifyCard(fortifiedLand); + } + } + // remove enchantments from creatures + if (copied.isEnchanted()) { + final List auras = new ArrayList(copied.getEnchantedBy()); + for (final Card aura : auras) { + aura.unEnchantEntity(copied); + } + } + // unenchant creature if moving aura + if (copied.isEnchanting()) { + copied.unEnchantEntity(copied.getEnchanting()); + } + } + + /** + *

+ * moveTo. + *

+ * + * @param zoneTo + * a {@link forge.game.zone.PlayerZone} object. + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link forge.game.card.Card} object. + */ + public final Card moveTo(final Zone zoneTo, Card c) { + // FThreads.assertExecutedByEdt(false); // This code must never be executed from EDT, + // use FThreads.invokeInNewThread to run code in a pooled thread + + return moveTo(zoneTo, c, null); + } + + public final Card moveTo(final Zone zoneTo, Card c, Integer position) { + FThreads.assertExecutedByEdt(false); + + // Ideally move to should never be called without a prevZone + // Remove card from Current Zone, if it has one + final Zone zoneFrom = game.getZoneOf(c); + // String prevName = prev != null ? prev.getZoneName() : ""; + + if (c.hasKeyword("If CARDNAME would leave the battlefield, exile it instead of putting it anywhere else.") + && !zoneTo.is(ZoneType.Exile)) { + final PlayerZone removed = c.getOwner().getZone(ZoneType.Exile); + c.removeAllExtrinsicKeyword("If CARDNAME would leave the battlefield, " + + "exile it instead of putting it anywhere else."); + return this.moveTo(removed, c); + } + + // Card lastKnownInfo = c; + + c = changeZone(zoneFrom, zoneTo, c, position); + + if (zoneTo.is(ZoneType.Stack)) { + c.setCastFrom(zoneFrom.getZoneType()); + } else if (zoneFrom == null) { + c.setCastFrom(null); + } else if (!(zoneTo.is(ZoneType.Battlefield) && zoneFrom.is(ZoneType.Stack))) { + c.setCastFrom(null); + } + + if (c.isAura() && zoneTo.is(ZoneType.Battlefield) && ((zoneFrom == null) || !zoneFrom.is(ZoneType.Stack)) + && !c.isEnchanting()) { + // TODO Need a way to override this for Abilities that put Auras + // into play attached to things + AttachEffect.attachAuraOnIndirectEnterBattlefield(c); + } + + return c; + } + + /** + * Controller change zone correction. + * + * @param c + * a Card object + */ + public final void controllerChangeZoneCorrection(final Card c) { + System.out.println("Correcting zone for " + c.toString()); + final Zone oldBattlefield = game.getZoneOf(c); + if (oldBattlefield == null || oldBattlefield.getZoneType() == ZoneType.Stack) { + return; + } + final PlayerZone newBattlefield = c.getController().getZone(oldBattlefield.getZoneType()); + + if (newBattlefield == null || oldBattlefield.equals(newBattlefield)) { + return; + } + + game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); + for (Player p : game.getPlayers()) { + ((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(false); + } + + final int tiz = c.getTurnInZone(); + + oldBattlefield.remove(c); + newBattlefield.add(c); + c.setSickness(true); + if (c.hasStartOfKeyword("Echo")) { + c.addExtrinsicKeyword("(Echo unpaid)"); + } + if (game.getPhaseHandler().inCombat()) { + game.getCombat().removeFromCombat(c); + } + + c.setTurnInZone(tiz); + + final HashMap runParams = new HashMap(); + runParams.put("Card", c); + game.getTriggerHandler().runTrigger(TriggerType.ChangesController, runParams, false); + + game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + for (Player p : game.getPlayers()) { + ((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(true); + } + c.runChangeControllerCommands(); + } + + /** + *

+ * moveToStack. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link forge.game.card.Card} object. + */ + public final Card moveToStack(final Card c) { + final Zone stack = game.getStackZone(); + return this.moveTo(stack, c); + } + + /** + *

+ * moveToGraveyard. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link forge.game.card.Card} object. + */ + public final Card moveToGraveyard(Card c) { + final Player owner = c.getOwner(); + final PlayerZone grave = owner.getZone(ZoneType.Graveyard); + final PlayerZone exile = owner.getZone(ZoneType.Exile); + + if (c.hasKeyword("If CARDNAME would be put into a graveyard, exile it instead.")) { + return this.moveTo(exile, c); + } + + // must put card in OWNER's graveyard not controller's + c = this.moveTo(grave, c); + + return c; + } + + + /** + *

+ * moveToHand. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link forge.game.card.Card} object. + */ + public final Card moveToHand(final Card c) { + final PlayerZone hand = c.getOwner().getZone(ZoneType.Hand); + return this.moveTo(hand, c); + } + + /** + *

+ * moveToPlay. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link forge.game.card.Card} object. + */ + public final Card moveToPlay(final Card c) { + final PlayerZone play = c.getController().getZone(ZoneType.Battlefield); + return this.moveTo(play, c); + } + + /** + *

+ * moveToPlay. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param p + * a {@link forge.game.player.Player} object. + * @return a {@link forge.game.card.Card} object. + */ + public final Card moveToPlay(final Card c, final Player p) { + // move to a specific player's Battlefield + final PlayerZone play = p.getZone(ZoneType.Battlefield); + return this.moveTo(play, c); + } + + /** + *

+ * moveToBottomOfLibrary. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link forge.game.card.Card} object. + */ + public final Card moveToBottomOfLibrary(final Card c) { + return this.moveToLibrary(c, -1); + } + + /** + *

+ * moveToLibrary. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link forge.game.card.Card} object. + */ + public final Card moveToLibrary(final Card c) { + return this.moveToLibrary(c, 0); + } + + /** + *

+ * moveToLibrary. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param libPosition + * a int. + * @return a {@link forge.game.card.Card} object. + */ + public final Card moveToLibrary(Card c, int libPosition) { + final PlayerZone library = c.getOwner().getZone(ZoneType.Library); + + if (libPosition == -1 || libPosition > library.size()) { + libPosition = library.size(); + } + return this.changeZone(game.getZoneOf(c), library, c, libPosition); + } + + /** + *

+ * moveToVariantDeck. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param zone + * a {@link forge.game.card.Card} object. + * @param libPosition + * a int. + * @return a {@link forge.game.card.Card} object. + */ + public final Card moveToVariantDeck(Card c, ZoneType zone, int deckPosition) { + final PlayerZone deck = c.getOwner().getZone(zone); + if (deckPosition == -1 || deckPosition > deck.size()) { + deckPosition = deck.size(); + } + return this.changeZone(game.getZoneOf(c), deck, c, deckPosition); + } + + /** + *

+ * exile. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link forge.game.card.Card} object. + */ + public final Card exile(final Card c) { + if (game.isCardExiled(c)) { + return c; + } + final PlayerZone removed = c.getOwner().getZone(ZoneType.Exile); + return moveTo(removed, c); + } + + /** + * Move to. + * + * @param name + * the name + * @param c + * the c + * @return the card + */ + public final Card moveTo(final ZoneType name, final Card c) { + return this.moveTo(name, c, 0); + } + + /** + *

+ * moveTo. + *

+ * + * @param name + * a {@link java.lang.String} object. + * @param c + * a {@link forge.game.card.Card} object. + * @param libPosition + * a int. + * @return a {@link forge.game.card.Card} object. + */ + public final Card moveTo(final ZoneType name, final Card c, final int libPosition) { + // Call specific functions to set PlayerZone, then move onto moveTo + switch(name) { + case Hand: return this.moveToHand(c); + case Library: return this.moveToLibrary(c, libPosition); + case Battlefield: return this.moveToPlay(c); + case Graveyard: return this.moveToGraveyard(c); + case Exile: return this.exile(c); + case Stack: return this.moveToStack(c); + case PlanarDeck: return this.moveToVariantDeck(c, ZoneType.PlanarDeck, libPosition); + case SchemeDeck: return this.moveToVariantDeck(c, ZoneType.SchemeDeck, libPosition); + default: // sideboard will also get there + return this.moveTo(c.getOwner().getZone(name), c); + } + } + + /** */ + public final void checkStaticAbilities() { + FThreads.assertExecutedByEdt(false); + + if (game.isGameOver()) { + return; + } + + // remove old effects + Set affectedCards = game.getStaticEffects().clearStaticEffects(); + game.getTriggerHandler().cleanUpTemporaryTriggers(); + game.getReplacementHandler().cleanUpTemporaryReplacements(); + + // search for cards with static abilities + final List allCards = game.getCardsInGame(); + final ArrayList staticAbilities = new ArrayList(); + final List staticList = new ArrayList(); + for (final Card c : allCards) { + for (int i = 0; i < c.getStaticAbilities().size(); i++) { + StaticAbility stAb = c.getCharacteristics().getStaticAbilities().get(i); + if (stAb.getMapParams().get("Mode").equals("Continuous")) { + staticAbilities.add(stAb); + } + if (stAb.isTemporary()) { + c.getCharacteristics().getStaticAbilities().remove(i); + i--; + } + } + if (!c.getStaticCommandList().isEmpty()) { + staticList.add(c); + } + } + + final Comparator comp = new Comparator() { + @Override + public int compare(final StaticAbility a, final StaticAbility b) { + int layerDelta = a.getLayer() - b.getLayer(); + if (layerDelta != 0) return layerDelta; + + long tsDelta = a.getHostCard().getTimestamp() - b.getHostCard().getTimestamp(); + return tsDelta == 0 ? 0 : tsDelta > 0 ? 1 : -1; + } + }; + Collections.sort(staticAbilities, comp); + for (final StaticAbility stAb : staticAbilities) { + List affectedHere = stAb.applyAbility("Continuous"); + if (null != affectedHere) { + affectedCards.addAll(affectedHere); + } + } + + // card state effects like Glorious Anthem +// for (final String effect : game.getStaticEffects().getStateBasedMap().keySet()) { +// final Function com = GameActionUtil.getCommands().get(effect); +// com.apply(game); +// } + + List lands = game.getCardsIn(ZoneType.Battlefield); + GameActionUtil.grantBasicLandsManaAbilities(CardLists.filter(lands, CardPredicates.Presets.LANDS)); + + for (final Card c : staticList) { + for (int i = 0; i < c.getStaticCommandList().size(); i++) { + final Object[] staticCheck = c.getStaticCommandList().get(i); + final String leftVar = (String) staticCheck[0]; + final String rightVar = (String) staticCheck[1]; + final Card affected = (Card) staticCheck[2]; + // calculate the affected card + final int sVar = AbilityUtils.calculateAmount(affected, leftVar, null); + final String svarOperator = rightVar.substring(0, 2); + final String svarOperand = rightVar.substring(2); + final int operandValue = AbilityUtils.calculateAmount(c, svarOperand, null); + if (Expressions.compare(sVar, svarOperator, operandValue)) { + ((Command) staticCheck[3]).run(); + c.getStaticCommandList().remove(i); + i--; + affectedCards.add(c); + } + } + } + // Exclude cards in hidden zones from update + Iterator it = affectedCards.iterator(); + while (it.hasNext()) { + Card c = it.next(); + if (c.isInZone(ZoneType.Library)) { + it.remove(); + } + } + + if (!affectedCards.isEmpty()) { + game.fireEvent(new GameEventCardStatsChanged(affectedCards)); + } + + } + + /** + *

+ * checkStateEffects. + *

+ */ + public final void checkStateEffects() { + // sol(10/29) added for Phase updates, state effects shouldn't be + // checked during Spell Resolution (except when persist-returning + if (game.getStack().isResolving()) { + return; + } + + // final JFrame frame = Singletons.getView().getFrame(); + // if (!frame.isDisplayable()) { + // return; + // } + + if (game.isGameOver()) { + return; + } + + // Max: I don't know where to put this! - but since it's a state based action, it must be in check state effects + if (game.getType() == GameType.Archenemy) { + game.archenemy904_10(); + } + + final boolean refreeze = game.getStack().isFrozen(); + game.getStack().setFrozen(true); + + // do this twice, sometimes creatures/permanents will survive when they + // shouldn't + for (int q = 0; q < 9; q++) { + boolean checkAgain = false; + + this.checkStaticAbilities(); + + final HashMap runParams = new HashMap(); + game.getTriggerHandler().runTrigger(TriggerType.Always, runParams, false); + + for (Player p : game.getPlayers()) { + for (Card c : p.getCardsIn(ZoneType.Battlefield)) { + if (!c.getController().equals(p)) { // should not be here + controllerChangeZoneCorrection(c); + checkAgain = true; + } + } + for (ZoneType zt : ZoneType.values()) { + if (zt == ZoneType.Battlefield) { + continue; + } + for (Card c : p.getCardsIn(zt)) { + // If a token is in a zone other than the battlefield, it ceases to exist. + checkAgain |= stateBasedAction704_5d(c); + } + } + } + + for (Card c : game.getCardsIn(ZoneType.Battlefield)) { + if (c.isCreature() && c.isPaired()) { // Soulbond unpairing (702.93e) - should not be here + Card partner = c.getPairedWith(); + if (!partner.isCreature() || c.getController() != partner.getController() || !c.isInZone(ZoneType.Battlefield)) { + c.setPairedWith(null); + partner.setPairedWith(null); + } + } + + if (c.isCreature()) { + // Rule 704.5f - Destroy (no regeneration) for toughness <= 0 + if (c.getNetDefense() <= 0) { + this.destroyNoRegeneration(c, null); + checkAgain = true; + } else + + // Rule 704.5g - Destroy due to lethal damage + if (c.getNetDefense() <= c.getDamage()) { + this.destroy(c, null); + checkAgain = true; + } + } + + checkAgain |= stateBasedAction704_5n(c); // Auras attached to illegal or not attached go to graveyard + checkAgain |= stateBasedAction704_5p(c); // Equipment and Fortifications + + if (c.isCreature() && c.isEnchanting()) { // Rule 704.5q - Creature attached to an object or player, becomes unattached + c.unEnchantEntity(c.getEnchanting()); + checkAgain = true; + } + + checkAgain |= stateBasedAction704_5r(c); // annihilate +1/+1 counters with -1/-1 ones + + if (c.getCounters(CounterType.DREAM) > 7 && c.hasKeyword("CARDNAME can't have more than seven dream counters on it.")) { + c.subtractCounter(CounterType.DREAM, c.getCounters(CounterType.DREAM) - 7); + checkAgain = true; + } + } + + if (game.getTriggerHandler().runWaitingTriggers()) { + checkAgain = true; + // Place triggers on stack + game.getStack().chooseOrderOfSimultaneousStackEntryAll(); + } + boolean yamazaki = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Brothers Yamazaki")).size() == 2; + for (Player p : game.getPlayers()) { + if (this.handleLegendRule(p, yamazaki)) { + checkAgain = true; + } + + if (this.handlePlaneswalkerRule(p)) { + checkAgain = true; + } + } + + if (!checkAgain) { + break; // do not continue the loop + } + } // for q=0;q<2 + + checkGameOverCondition(); + + if (!refreeze) { + game.getStack().unfreezeStack(); + } + } // checkStateEffects() + + /** + * TODO: Write javadoc for this method. + * @param checkAgain + * @param c + * @return + */ + private boolean stateBasedAction704_5n(Card c) { + boolean checkAgain = false; + if (!c.isAura()) { + return false; + } + + // Check if Card Aura is attached to is a legal target + final GameEntity entity = c.getEnchanting(); + SpellAbility sa = c.getSpells().get(0); + if (c.isBestowed()) { + for (SpellAbility s : c.getSpellAbilities()) { + if (s.getApi() == ApiType.Attach && s.hasParam("Bestow")) { + sa = s; + break; + } + } + } + + TargetRestrictions tgt = null; + if (sa != null) { + tgt = sa.getTargetRestrictions(); + } + + if (entity instanceof Card) { + final Card perm = (Card) entity; + ZoneType tgtZone = tgt.getZone().get(0); + + if (!perm.isInZone(tgtZone) || !perm.canBeEnchantedBy(c) || (perm.isPhasedOut() && !c.isPhasedOut())) { + c.unEnchantEntity(perm); + if (c.isBestowed()) { + c.unanimateBestow(); + game.fireEvent(new GameEventCardStatsChanged(c)); + return true; + } + else { + this.moveToGraveyard(c); + } + checkAgain = true; + } + } else if (entity instanceof Player) { + final Player pl = (Player) entity; + boolean invalid = false; + + if (tgt.canOnlyTgtOpponent() && !c.getController().getOpponent().equals(pl)) { + invalid = true; + } + else if (pl.hasProtectionFrom(c)) { + invalid = true; + } + if (invalid) { + c.unEnchantEntity(pl); + this.moveToGraveyard(c); + checkAgain = true; + } + } + + if (c.isInPlay() && !c.isEnchanting()) { + if (c.isBestowed()) { + c.unanimateBestow(); + game.fireEvent(new GameEventCardStatsChanged(c)); + return true; + } + this.moveToGraveyard(c); + checkAgain = true; + } + return checkAgain; + } + + /** + * TODO: Write javadoc for this method. + * @param checkAgain + * @param c + * @return + */ + private boolean stateBasedAction704_5p(Card c) { + boolean checkAgain = false; + if (c.isEquipped()) { + final List equipments = new ArrayList(c.getEquippedBy()); + for (final Card equipment : equipments) { + if (!equipment.isInPlay()) { + equipment.unEquipCard(c); + checkAgain = true; + } + } + } // if isEquipped() + + if (c.isFortified()) { + final List fortifications = new ArrayList(c.getFortifiedBy()); + for (final Card f : fortifications) { + if (!f.isInPlay()) { + f.unFortifyCard(c); + checkAgain = true; + } + } + } // if isFortified() + + if (c.isEquipping()) { + final Card equippedCreature = c.getEquipping().get(0); + if (!equippedCreature.isCreature() || !equippedCreature.isInPlay() + || !equippedCreature.canBeEquippedBy(c) + || (equippedCreature.isPhasedOut() && !c.isPhasedOut()) + || !c.isEquipment()) { + c.unEquipCard(equippedCreature); + checkAgain = true; + } + // make sure any equipment that has become a creature stops equipping + if (c.isCreature()) { + c.unEquipCard(equippedCreature); + checkAgain = true; + } + } // if isEquipping() + + if (c.isFortifying()) { + final Card fortifiedLand = c.getFortifying().get(0); + if (!fortifiedLand.isLand() || !fortifiedLand.isInPlay() + || (fortifiedLand.isPhasedOut() && !c.isPhasedOut())) { + c.unFortifyCard(fortifiedLand); + checkAgain = true; + } + // make sure any fortification that has become a creature stops fortifying + if (c.isCreature()) { + c.unFortifyCard(fortifiedLand); + checkAgain = true; + } + } // if isFortifying() + return checkAgain; + } + + /** + * TODO: Write javadoc for this method. + * @param checkAgain + * @param c + * @return + */ + private boolean stateBasedAction704_5r(Card c) { + boolean checkAgain = false; + // +1/+1 counters should erase -1/-1 counters + if (c.getCounters(CounterType.P1P1) > 0 && c.getCounters(CounterType.M1M1) > 0) { + int plusOneCounters = c.getCounters(CounterType.P1P1); + int minusOneCounters = c.getCounters(CounterType.M1M1); + + if (plusOneCounters >= minusOneCounters) c.getCounters().remove(CounterType.M1M1); + if (plusOneCounters <= minusOneCounters) c.getCounters().remove(CounterType.P1P1); + + int diff = plusOneCounters - minusOneCounters; + if (diff != 0) { + CounterType ct = diff > 0 ? CounterType.P1P1 : CounterType.M1M1; + c.getCounters().put(ct, Math.abs(diff)); + } + checkAgain = true; + } + return checkAgain; + } + + // If a token is in a zone other than the battlefield, it ceases to exist. + private boolean stateBasedAction704_5d(Card c) { + boolean checkAgain = false; + if (c.isToken()) { + final Zone zoneFrom = game.getZoneOf(c); + if (!zoneFrom.is(ZoneType.Battlefield) && !zoneFrom.is(ZoneType.Command)) { + zoneFrom.remove(c); + checkAgain = true; + } + } + return checkAgain; + } + + public void checkGameOverCondition() { + // award loses as SBE + List losers = null; + for (Player p : this.game.getPlayers()) { + if (p.checkLoseCondition()) { // this will set appropriate outcomes + // Run triggers + if (losers == null) { + losers = new ArrayList(3); + } + losers.add(p); + } + } + + GameEndReason reason = null; + // Has anyone won by spelleffect? + for (Player p : this.game.getPlayers()) { + if (!p.hasWon()) { + continue; + } + + // then the rest have lost! + reason = GameEndReason.WinsGameSpellEffect; + for (Player pl : this.game.getPlayers()) { + if (pl.equals(p)) { + continue; + } + + if (!pl.loseConditionMet(GameLossReason.OpponentWon, p.getOutcome().altWinSourceName)) { + reason = null; // they cannot lose! + } else { + if (losers == null) { + losers = new ArrayList(3); + } + losers.add(p); + } + } + break; + } + + // need a separate loop here, otherwise ConcurrentModificationException is raised + if (losers != null) { + for (Player p : losers) { + this.game.onPlayerLost(p); + } + } + + if (reason == null) { + int cntNotLost = Iterables.size(Iterables.filter(game.getPlayers(), Player.Predicates.NOT_LOST)); + if (cntNotLost == 1) { + reason = GameEndReason.AllOpponentsLost; + } + else if (cntNotLost == 0) { + reason = GameEndReason.Draw; + } + else { + return; + } + } + + // Clear Simultaneous triggers at the end of the game + game.setGameOver(reason); + game.getStack().clearSimultaneousStack(); + } + + /** + *

+ * destroyPlaneswalkers. + *

+ */ + private boolean handlePlaneswalkerRule(Player p) { + // get all Planeswalkers + final List list = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS); + + boolean recheck = false; + final Multimap uniqueWalkers = ArrayListMultimap.create(); + for (Card c : list) { + if (c.getCounters(CounterType.LOYALTY) <= 0) { + moveToGraveyard(c); + // Play the Destroy sound + game.fireEvent(new GameEventCardDestroyed()); + recheck = true; + } + + + for (final String type : c.getType()) { + if (CardType.isAPlaneswalkerType(type)) { + uniqueWalkers.put(type, c); + } + } + } + + for (String key : uniqueWalkers.keySet()) { + Collection duplicates = uniqueWalkers.get(key); + if (duplicates.size() < 2) { + continue; + } + + recheck = true; + + Card toKeep = p.getController().chooseSingleEntityForEffect(duplicates, new AbilitySub(ApiType.InternalLegendaryRule, null, null, null), "You have multiple planeswalkers of type \""+key+"\"in play.\n\nChoose one to stay on battlefield (the rest will be moved to graveyard)"); + for (Card c: duplicates) { + if (c != toKeep) { + moveToGraveyard(c); + } + } + } + return recheck; + } + + /** + *

+ * destroyLegendaryCreatures. + *

+ */ + private boolean handleLegendRule(Player p, boolean yama) { + final List a = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), "Legendary"); + if (a.isEmpty() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) { + return false; + } + boolean recheck = false; + if (yama) { + List yamazaki = CardLists.filter(a, CardPredicates.nameEquals("Brothers Yamazaki")); + a.removeAll(yamazaki); + } + + Multimap uniqueLegends = ArrayListMultimap.create(); + for (Card c : a) { + if (!c.isFaceDown()) { + uniqueLegends.put(c.getName(), c); + } + } + + for (String name : uniqueLegends.keySet()) { + Collection cc = uniqueLegends.get(name); + if (cc.size() < 2) { + continue; + } + + recheck = true; + + Card toKeep = p.getController().chooseSingleEntityForEffect(cc, new AbilitySub(ApiType.InternalLegendaryRule, null, null, null), "You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)"); + for (Card c: cc) { + if (c != toKeep) { + sacrificeDestroy(c); + } + } + game.fireEvent(new GameEventCardDestroyed()); + } + + return recheck; + } // destroyLegendaryCreatures() + + + public final boolean sacrifice(final Card c, final SpellAbility source) { + if (!c.canBeSacrificedBy(source)) { + return false; + } + + this.sacrificeDestroy(c); + + // Play the Sacrifice sound + game.fireEvent(new GameEventCardSacrificed()); + + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Card", c); + runParams.put("Cause", source); + game.getTriggerHandler().runTrigger(TriggerType.Sacrificed, runParams, false); + return true; + } + + /** + *

+ * destroy. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean destroy(final Card c, final SpellAbility sa) { + if (!c.canBeDestroyed()) { + return false; + } + + if (c.canBeShielded() && (!c.isCreature() || c.getNetDefense() > 0) + && (!c.getShield().isEmpty() || c.hasKeyword("If CARDNAME would be destroyed, regenerate it."))) { + c.subtractShield(c.getController().getController().chooseRegenerationShield(c)); + c.setDamage(0); + c.tap(); + c.addRegeneratedThisTurn(); + if (game.getCombat() != null) { + game.getCombat().removeFromCombat(c); + } + + // Play the Regen sound + game.fireEvent(new GameEventCardRegenerated()); + + return false; + } + + return this.destroyNoRegeneration(c, sa); + } + + /** + *

+ * destroyNoRegeneration. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean destroyNoRegeneration(final Card c, final SpellAbility sa) { + Player activator = null; + if (!c.canBeDestroyed()) { + return false; + } + + if (c.isEnchanted()) { + for (Card e : c.getEnchantedBy()) { + CardFactoryUtil.refreshTotemArmor(e); + } + } + + // Replacement effects + final HashMap repRunParams = new HashMap(); + repRunParams.put("Event", "Destroy"); + repRunParams.put("Source", sa); + repRunParams.put("Card", c); + repRunParams.put("Affected", c); + + if (game.getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) { + return false; + } + + + if (sa != null) { + activator = sa.getActivatingPlayer(); + } + + // Play the Destroy sound + game.fireEvent(new GameEventCardDestroyed()); + + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Card", c); + runParams.put("Causer", activator); + game.getTriggerHandler().runTrigger(TriggerType.Destroyed, runParams, false); + + return this.sacrificeDestroy(c); + } + + /** + *

+ * addSuspendTriggers. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link forge.game.card.Card} object. + */ + private static Card addSuspendTriggers(final Card c) { + if (c.getSVar("HasteFromSuspend").equals("True")) { + return c; + } + c.setSVar("HasteFromSuspend", "True"); + + final Command intoPlay = new Command() { + private static final long serialVersionUID = -4514610171270596654L; + + @Override + public void run() { + if (c.isInPlay() && c.isCreature()) { + c.addExtrinsicKeyword("Haste"); + } + } // execute() + }; + + c.addComesIntoPlayCommand(intoPlay); + + final Command loseControl = new Command() { + private static final long serialVersionUID = -4514610171270596654L; + + @Override + public void run() { + if (c.getSVar("HasteFromSuspend").equals("True")) { + c.setSVar("HasteFromSuspend", "False"); + c.removeExtrinsicKeyword("Haste"); + } + } // execute() + }; + + c.addChangeControllerCommand(loseControl); + c.addLeavesPlayCommand(loseControl); + return c; + } + + /** + *

+ * sacrificeDestroy. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean sacrificeDestroy(final Card c) { + if (!c.isInPlay()) { + return false; + } + + boolean persist = (c.hasKeyword("Persist") && c.getCounters(CounterType.M1M1) == 0) && !c.isToken(); + boolean undying = (c.hasKeyword("Undying") && c.getCounters(CounterType.P1P1) == 0) && !c.isToken(); + + final Card newCard = this.moveToGraveyard(c); + + // don't trigger persist/undying if the dying has been replaced + if (newCard == null || !newCard.isInZone(ZoneType.Graveyard)) { + persist = false; + undying = false; + } + + // Destroy needs to be called with Last Known Information + c.executeTrigger(ZCTrigger.DESTROY); + + // System.out.println("Card " + c.getName() + + // " is getting sent to GY, and this turn it got damaged by: "); + + if (persist) { + final Card persistCard = newCard; + String effect = String.format("AB$ ChangeZone | Cost$ 0 | Defined$ CardUID_%d" + + " | Origin$ Graveyard | Destination$ Battlefield | WithCounters$ M1M1_1", + persistCard.getUniqueNumber()); + SpellAbility persistAb = AbilityFactory.getAbility(effect, c); + persistAb.setTrigger(true); + persistAb.setStackDescription(newCard.getName() + " - Returning from Persist"); + persistAb.setDescription(newCard.getName() + " - Returning from Persist"); + persistAb.setActivatingPlayer(c.getController()); + + game.getStack().addSimultaneousStackEntry(persistAb); + } + + if (undying) { + final Card undyingCard = newCard; + String effect = String.format("AB$ ChangeZone | Cost$ 0 | Defined$ CardUID_%d |" + + " Origin$ Graveyard | Destination$ Battlefield | WithCounters$ P1P1_1", + undyingCard.getUniqueNumber()); + SpellAbility undyingAb = AbilityFactory.getAbility(effect, c); + undyingAb.setTrigger(true); + undyingAb.setStackDescription(newCard.getName() + " - Returning from Undying"); + undyingAb.setDescription(newCard.getName() + " - Returning from Undying"); + undyingAb.setActivatingPlayer(c.getController()); + + game.getStack().addSimultaneousStackEntry(undyingAb); + } + return true; + } // sacrificeDestroy() + + public void reveal(Collection cards, Player cardOwner) { + reveal(cards, cardOwner, true); + } + + public void reveal(Collection cards, Player cardOwner, boolean dontRevealToOwner) { + reveal(cards, cardOwner, dontRevealToOwner, null); + } + + public void reveal(Collection cards, Player cardOwner, boolean dontRevealToOwner, String messagePrefix) { + Card firstCard = Iterables.getFirst(cards, null); + if (firstCard == null) { + return; + } + reveal(cards, game.getZoneOf(firstCard).getZoneType(), cardOwner, dontRevealToOwner, messagePrefix); + } + + public void reveal(Collection cards, ZoneType zt, Player cardOwner, boolean dontRevealToOwner, String messagePrefix) { + for (Player p : game.getPlayers()) { + if (dontRevealToOwner && cardOwner == p) { + continue; + } + p.getController().reveal(cards, zt, cardOwner, messagePrefix); + } + } + + public void revealAnte(String title, Multimap removedAnteCards) { + for (Player p : game.getPlayers()) { + p.getController().revealAnte(title, removedAnteCards); + } + } + + /** Delivers a message to all players. (use reveal to show Cards) */ + public void nofityOfValue(SpellAbility saSource, GameObject relatedTarget, String value, Player playerExcept) { + for (Player p : game.getPlayers()) { + if (playerExcept == p) continue; + p.getController().notifyOfValue(saSource, relatedTarget, value); + } + } + + public void startGame(GameOutcome lastGameOutcome) { + Player first = determineFirstTurnPlayer(lastGameOutcome); + + do { + if (game.isGameOver()) { break; } // conceded during "play or draw" + + // FControl should determine now if there are any human players. + // Where there are none, it should bring up speed controls + game.fireEvent(new GameEventGameStarted(game.getType(), first, game.getPlayers())); + + game.setAge(GameStage.Mulligan); + for (final Player p1 : game.getPlayers()) { + p1.drawCards(p1.getMaxHandSize()); + } + + performMulligans(first, game.getType() == GameType.Commander); + if (game.isGameOver()) { break; } // conceded during "mulligan" prompt + + game.setAge(GameStage.Play); + + // + if (game.getType() == GameType.Planechase) { + first.initPlane(); + } + + runOpeningHandActions(first); + checkStateEffects(); // why? + + // Run Trigger beginning of the game + final HashMap runParams = new HashMap(); + game.getTriggerHandler().runTrigger(TriggerType.NewGame, runParams, true); + // + + game.getPhaseHandler().startFirstTurn(first); + + first = game.getPhaseHandler().getPlayerTurn(); // needed only for restart + } while (game.getAge() == GameStage.RestartedByKarn); + } + + private Player determineFirstTurnPlayer(final GameOutcome lastGameOutcome) { + // Only cut/coin toss if it's the first game of the match + Player goesFirst = null; + + // 904.6: in Archenemy games the Archenemy goes first + if (game != null && game.getType() == GameType.Archenemy) { + for (Player p : game.getPlayers()) { + if (p.isArchenemy()) { + return p; + } + } + } + + boolean isFirstGame = lastGameOutcome == null; + if (isFirstGame) { + game.fireEvent(new GameEventFlipCoin()); // Play the Flip Coin sound + goesFirst = Aggregates.random(game.getPlayers()); + } else { + for (Player p : game.getPlayers()) { + if (!lastGameOutcome.isWinner(p.getLobbyPlayer())) { + goesFirst = p; + break; + } + } + } + + if (goesFirst == null) { + // This happens in hotseat matches when 2 equal lobbyplayers play. + // Noone of them has lost, so cannot decide who goes first . + goesFirst = game.getPlayers().get(0); // does not really matter who plays first - it's controlled from the same computer. + } + + boolean willPlay = goesFirst.getController().getWillPlayOnFirstTurn(isFirstGame); + goesFirst = willPlay ? goesFirst : goesFirst.getOpponent(); + return goesFirst; + } + + private void performMulligans(final Player firstPlayer, final boolean isCommander) { + List whoCanMulligan = Lists.newArrayList(game.getPlayers()); + int offset = whoCanMulligan.indexOf(firstPlayer); + + // Have to cycle-shift the list to get the first player on index 0 + for (int i = 0; i < offset; i++) { + whoCanMulligan.add(whoCanMulligan.remove(0)); + } + + boolean[] hasKept = new boolean[whoCanMulligan.size()]; + int[] handSize = new int[whoCanMulligan.size()]; + for (int i = 0; i < whoCanMulligan.size(); i++) { + hasKept[i] = false; + handSize[i] = whoCanMulligan.get(i).getZone(ZoneType.Hand).size(); + } + + MapOfLists exiledDuringMulligans = new HashMapOfLists(CollectionSuppliers.arrayLists()); + + // rule 103.4b + boolean isMultiPlayer = game.getPlayers().size() > 2; + int mulliganDelta = isMultiPlayer ? 0 : 1; + + boolean allKept; + do { + allKept = true; + for (int i = 0; i < whoCanMulligan.size(); i++) { + if (hasKept[i]) continue; + + Player p = whoCanMulligan.get(i); + List toMulligan = p.canMulligan() ? p.getController().getCardsToMulligan(isCommander, firstPlayer) : null; + + if (game.isGameOver()) // conceded on mulligan prompt + return; + + if (toMulligan != null && !toMulligan.isEmpty()) { + if (!isCommander) { + toMulligan = new ArrayList(p.getCardsIn(ZoneType.Hand)); + for (final Card c : toMulligan) { + moveToLibrary(c); + } + p.shuffle(null); + p.drawCards(handSize[i] - mulliganDelta); + } else { + List toExile = Lists.newArrayList(toMulligan); + for (Card c : toExile) { + exile(c); + } + exiledDuringMulligans.addAll(p, toExile); + p.drawCards(toExile.size() - 1); + } + + p.onMulliganned(); + allKept = false; + } else { + game.getGameLog().add(GameLogEntryType.MULLIGAN, p.getName() + " has kept a hand of " + p.getZone(ZoneType.Hand).size() + " cards"); + hasKept[i] = true; + } + } + mulliganDelta++; + } while (!allKept); + + if (isCommander) { + for (Entry> kv : exiledDuringMulligans.entrySet()) { + Player p = kv.getKey(); + Collection cc = kv.getValue(); + for (Card c : cc) { + moveToLibrary(c); + } + p.shuffle(null); + } + } + } + + private void runOpeningHandActions(final Player first) { + Player takesAction = first; + do { + List usableFromOpeningHand = new ArrayList(); + + // Select what can be activated from a given hand + for (final Card c : takesAction.getCardsIn(ZoneType.Hand)) { + for (String kw : c.getKeyword()) { + if (kw.startsWith("MayEffectFromOpeningHand")) { + String[] split = kw.split(":"); + final String effName = split[1]; + if (split.length > 2 && split[2].equalsIgnoreCase("!PlayFirst") && first == takesAction) { + continue; + } + + final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); + effect.setActivatingPlayer(takesAction); + + usableFromOpeningHand.add(effect); + } + } + } + + // Players are supposed to return the effects in an order they want those to be resolved (Rule 103.5) + if (!usableFromOpeningHand.isEmpty()) { + usableFromOpeningHand = takesAction.getController().chooseSaToActivateFromOpeningHand(usableFromOpeningHand); + } + + for (final SpellAbility sa : usableFromOpeningHand) { + if (!takesAction.getZone(ZoneType.Hand).contains(sa.getSourceCard())) { + continue; + } + + takesAction.getController().playSpellAbilityNoStack(sa, true); + } + takesAction = game.getNextPlayerAfter(takesAction); + } while (takesAction != first); + // state effects are checked only when someone gets priority + } + + // Invokes given runnable in Game thread pool - used to start game and perform actions from UI (when game-0 waits for input) + public void invoke(final Runnable proc) { + if (ThreadUtil.isGameThread()) { + proc.run(); + } + else { + ThreadUtil.invokeInGameThread(proc); + } + } +} diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-gui/src/main/java/forge/game/GameActionUtil.java similarity index 97% rename from forge-game/src/main/java/forge/game/GameActionUtil.java rename to forge-gui/src/main/java/forge/game/GameActionUtil.java index 130366944d3..2fc23885285 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-gui/src/main/java/forge/game/GameActionUtil.java @@ -1,575 +1,575 @@ -/* - * 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 . - */ -package forge.game; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; - -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - -import forge.card.MagicColor; -import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; -import forge.game.ability.AbilityFactory.AbilityRecordType; -import forge.game.card.Card; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; -import forge.game.cost.Cost; -import forge.game.mana.ManaCostBeingPaid; -import forge.game.player.Player; -import forge.game.spellability.AbilityActivated; -import forge.game.spellability.AbilityManaPart; -import forge.game.spellability.AbilitySub; -import forge.game.spellability.OptionalCost; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.SpellAbilityRestriction; -import forge.game.zone.ZoneType; -import forge.util.TextUtil; - - -/** - *

- * GameActionUtil class. - *

- * - * @author Forge - * @version $Id: GameActionUtil.java 24193 2014-01-09 13:46:14Z swordshine $ - */ -public final class GameActionUtil { - - - private GameActionUtil() { - throw new AssertionError(); - } - - // restricted to combat damage, restricted to players - /** - *

- * executeCombatDamageToPlayerEffects. - *

- * - * @param player - * a {@link forge.game.player.Player} object. - * @param c - * a {@link forge.game.card.Card} object. - * @param damage - * a int. - */ - public static void executeCombatDamageToPlayerEffects(final Player player, final Card c, final int damage) { - - if (damage <= 0) { - return; - } - - for (final String key : c.getKeyword()) { - if (!key.startsWith("Poisonous ")) continue; - final String[] k = key.split(" ", 2); - final int poison = Integer.parseInt(k[1]); - // Now can be copied by Strionic Resonator - String effect = "AB$ Poison | Cost$ 0 | Defined$ PlayerNamed_" + player.getName() + " | Num$ " + k[1]; - SpellAbility ability = AbilityFactory.getAbility(effect, c); - - final StringBuilder sb = new StringBuilder(); - sb.append(c); - sb.append(" - Poisonous: "); - sb.append(player); - sb.append(" gets ").append(poison).append(" poison counter"); - if (poison != 1) { - sb.append("s"); - } - sb.append("."); - - ability.setActivatingPlayer(c.getController()); - ability.setDescription(sb.toString()); - ability.setStackDescription(sb.toString()); - ability.setTrigger(true); - - player.getGame().getStack().addSimultaneousStackEntry(ability); - - } - - c.getDamageHistory().registerCombatDamage(player); - } // executeCombatDamageToPlayerEffects - - /** - * Gets the st land mana abilities. - * @param game - * - * @return the stLandManaAbilities - */ - public static void grantBasicLandsManaAbilities(List lands) { - // remove all abilities granted by this Command - for (final Card land : lands) { - List origManaAbs = Lists.newArrayList(land.getManaAbility()); - List manaAbs = land.getCharacteristics().getManaAbility(); - // will get comodification exception without a different list - for (final SpellAbility sa : origManaAbs) { - if (sa.isBasicLandAbility()) { - manaAbs.remove(sa); - } - } - } - - // add all appropriate mana abilities based on current types - for (int i = 0; i < MagicColor.WUBRG.length; i++ ) { - String landType = MagicColor.Constant.BASIC_LANDS.get(i); - String color = MagicColor.toShortString(MagicColor.WUBRG[i]); - String abString = "AB$ Mana | Cost$ T | Produced$ " + color + " | SpellDescription$ Add {" + color + "} to your mana pool."; - for (final Card land : lands) { - if (land.isType(landType)) { - final SpellAbility sa = AbilityFactory.getAbility(abString, land); - sa.setBasicLandAbility(true); - land.getCharacteristics().getManaAbility().add(sa); - } - } - } - } // stLandManaAbilities - - /** - *

- * getAlternativeCosts. - *

- * - * @param sa - * a SpellAbility. - * @return an ArrayList. - * get alternative costs as additional spell abilities - */ - public static final ArrayList getAlternativeCosts(SpellAbility sa) { - ArrayList alternatives = new ArrayList(); - Card source = sa.getSourceCard(); - if (!sa.isBasicSpell()) { - return alternatives; - } - for (final String keyword : source.getKeyword()) { - if (sa.isSpell() && keyword.startsWith("Flashback")) { - final SpellAbility flashback = sa.copy(); - flashback.setFlashBackAbility(true); - SpellAbilityRestriction sar = new SpellAbilityRestriction(); - sar.setVariables(sa.getRestrictions()); - sar.setZone(ZoneType.Graveyard); - flashback.setRestrictions(sar); - - // there is a flashback cost (and not the cards cost) - if (!keyword.equals("Flashback")) { - flashback.setPayCosts(new Cost(keyword.substring(10), false)); - } - alternatives.add(flashback); - } - if (sa.isSpell() && keyword.equals("May be played without paying its mana cost")) { - final SpellAbility newSA = sa.copy(); - SpellAbilityRestriction sar = new SpellAbilityRestriction(); - sar.setVariables(sa.getRestrictions()); - sar.setZone(null); - newSA.setRestrictions(sar); - newSA.setBasicSpell(false); - newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana()); - newSA.setDescription(sa.getDescription() + " (without paying its mana cost)"); - alternatives.add(newSA); - } - if (sa.isSpell() && keyword.equals("May be played by your opponent without paying its mana cost")) { - final SpellAbility newSA = sa.copy(); - SpellAbilityRestriction sar = new SpellAbilityRestriction(); - sar.setVariables(sa.getRestrictions()); - sar.setZone(null); - sar.setOpponentOnly(true); - newSA.setRestrictions(sar); - newSA.setBasicSpell(false); - newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana()); - newSA.setDescription(sa.getDescription() + " (without paying its mana cost)"); - alternatives.add(newSA); - } - if (sa.isSpell() && keyword.startsWith("May be played without paying its mana cost and as though it has flash")) { - final SpellAbility newSA = sa.copy(); - SpellAbilityRestriction sar = new SpellAbilityRestriction(); - sar.setVariables(sa.getRestrictions()); - sar.setInstantSpeed(true); - newSA.setRestrictions(sar); - newSA.setBasicSpell(false); - newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana()); - newSA.setDescription(sa.getDescription() + " (without paying its mana cost and as though it has flash)"); - alternatives.add(newSA); - } - if (sa.isSpell() && keyword.startsWith("Alternative Cost")) { - final SpellAbility newSA = sa.copy(); - newSA.setBasicSpell(false); - String kw = keyword; - if (keyword.contains("ConvertedManaCost")) { - final String cmc = Integer.toString(sa.getSourceCard().getCMC()); - kw = keyword.replace("ConvertedManaCost", cmc); - } - final Cost cost = new Cost(kw.substring(17), false).add(newSA.getPayCosts().copyWithNoMana()); - newSA.setPayCosts(cost); - newSA.setDescription(sa.getDescription() + " (by paying " + cost.toSimpleString() + " instead of its mana cost)"); - alternatives.add(newSA); - } - if (sa.isSpell() && keyword.equals("You may cast CARDNAME any time you could cast an instant if you pay 2 more to cast it.")) { - final SpellAbility newSA = sa.copy(); - newSA.setBasicSpell(false); - ManaCostBeingPaid newCost = new ManaCostBeingPaid(source.getManaCost()); - newCost.increaseColorlessMana(2); - final Cost actualcost = new Cost(newCost.toManaCost(), false); - newSA.setPayCosts(actualcost); - SpellAbilityRestriction sar = new SpellAbilityRestriction(); - sar.setVariables(sa.getRestrictions()); - sar.setInstantSpeed(true); - newSA.setRestrictions(sar); - newSA.setDescription(sa.getDescription() + " (by paying " + actualcost.toSimpleString() + " instead of its mana cost)"); - alternatives.add(newSA); - } - if (sa.isSpell() && keyword.endsWith(" offering")) { - final String offeringType = keyword.split(" ")[0]; - List canOffer = CardLists.filter(sa.getSourceCard().getController().getCardsIn(ZoneType.Battlefield), - CardPredicates.isType(offeringType)); - if (source.getController().hasKeyword("You can't sacrifice creatures to cast spells or activate abilities.")) { - canOffer = CardLists.getNotType(canOffer, "Creature"); - } - if (!canOffer.isEmpty()) { - final SpellAbility newSA = sa.copy(); - SpellAbilityRestriction sar = new SpellAbilityRestriction(); - sar.setVariables(sa.getRestrictions()); - sar.setInstantSpeed(true); - newSA.setRestrictions(sar); - newSA.setBasicSpell(false); - newSA.setIsOffering(true); - newSA.setPayCosts(sa.getPayCosts()); - newSA.setDescription(sa.getDescription() + " (" + offeringType + " offering)"); - alternatives.add(newSA); - } - } - if (sa.hasParam("Equip") && sa instanceof AbilityActivated && keyword.equals("EquipInstantSpeed")) { - final SpellAbility newSA = ((AbilityActivated) sa).getCopy(); - SpellAbilityRestriction sar = new SpellAbilityRestriction(); - sar.setVariables(sa.getRestrictions()); - sar.setSorcerySpeed(false); - sar.setInstantSpeed(true); - newSA.setRestrictions(sar); - newSA.setDescription(sa.getDescription() + " (you may activate any time you could cast an instant )"); - alternatives.add(newSA); - } - } - return alternatives; - } - - /** - * get optional additional costs. - * - * @param original - * the original sa - * @return an ArrayList. - */ - public static List getOptionalCosts(final SpellAbility original) { - final List abilities = new ArrayList(); - - final Card source = original.getSourceCard(); - abilities.add(original); - if (!original.isSpell()) { - return abilities; - } - - // Buyback, Kicker - for (String keyword : source.getKeyword()) { - if (keyword.startsWith("AlternateAdditionalCost")) { - final List newAbilities = new ArrayList(); - String[] costs = TextUtil.split(keyword, ':'); - for (SpellAbility sa : abilities) { - final SpellAbility newSA = sa.copy(); - newSA.setBasicSpell(false); - - final Cost cost1 = new Cost(costs[1], false); - newSA.setDescription(sa.getDescription() + " (Additional cost " + cost1.toSimpleString() + ")"); - newSA.setPayCosts(cost1.add(sa.getPayCosts())); - if (newSA.canPlay()) { - newAbilities.add(newSA); - } - - //second option - final SpellAbility newSA2 = sa.copy(); - newSA2.setBasicSpell(false); - - final Cost cost2 = new Cost(costs[2], false); - newSA2.setDescription(sa.getDescription() + " (Additional cost " + cost2.toSimpleString() + ")"); - newSA2.setPayCosts(cost2.add(sa.getPayCosts())); - if (newSA2.canPlay()) { - newAbilities.add(newAbilities.size(), newSA2); - } - } - abilities.clear(); - abilities.addAll(newAbilities); - } else if (keyword.startsWith("Buyback")) { - for (int i = 0; i < abilities.size(); i++) { - final SpellAbility newSA = abilities.get(i).copy(); - newSA.setBasicSpell(false); - newSA.setPayCosts(new Cost(keyword.substring(8), false).add(newSA.getPayCosts())); - newSA.setDescription(newSA.getDescription() + " (with Buyback)"); - newSA.addOptionalCost(OptionalCost.Buyback); - if (newSA.canPlay()) { - abilities.add(i, newSA); - i++; - } - } - } else if (keyword.startsWith("Entwine")) { - for (int i = 0; i < abilities.size(); i++) { - final SpellAbility newSA = abilities.get(i).copy(); - SpellAbility entwine = AbilityFactory.buildEntwineAbility(newSA); - entwine.setPayCosts(new Cost(keyword.substring(8), false).add(newSA.getPayCosts())); - entwine.addOptionalCost(OptionalCost.Entwine); - if (newSA.canPlay()) { - abilities.add(i, entwine); - i++; - } - } - } else if (keyword.startsWith("Kicker")) { - for (int i = 0; i < abilities.size(); i++) { - String[] sCosts = TextUtil.split(keyword.substring(7), ':'); - int iUnKicked = i; - for (int j = 0; j < sCosts.length; j++) { - final SpellAbility newSA = abilities.get(iUnKicked).copy(); - newSA.setBasicSpell(false); - final Cost cost = new Cost(sCosts[j], false); - newSA.setDescription(newSA.getDescription() + " (Kicker " + cost.toSimpleString() + ")"); - newSA.setPayCosts(cost.add(newSA.getPayCosts())); - newSA.addOptionalCost(j == 0 ? OptionalCost.Kicker1 : OptionalCost.Kicker2); - if (newSA.canPlay()) { - abilities.add(i, newSA); - i++; - iUnKicked++; - } - } - if (sCosts.length == 2) { // case for both kickers - it's hardcoded since they never have more than 2 kickers - final SpellAbility newSA = abilities.get(iUnKicked).copy(); - newSA.setBasicSpell(false); - final Cost cost1 = new Cost(sCosts[0], false); - final Cost cost2 = new Cost(sCosts[1], false); - newSA.setDescription(newSA.getDescription() + String.format(" (Both kickers: %s and %s)", cost1.toSimpleString(), cost2.toSimpleString())); - newSA.setPayCosts(cost2.add(cost1.add(newSA.getPayCosts()))); - newSA.addOptionalCost(OptionalCost.Kicker1); - newSA.addOptionalCost(OptionalCost.Kicker2); - if (newSA.canPlay()) { - abilities.add(i, newSA); - i++; - } - } - } - } - } - - if (source.hasKeyword("Conspire")) { - int amount = source.getAmountOfKeyword("Conspire"); - for (int kwInstance = 1; kwInstance <= amount; kwInstance++) { - for (int i = 0; i < abilities.size(); i++) { - final SpellAbility newSA = abilities.get(i).copy(); - newSA.setBasicSpell(false); - final String conspireCost = "tapXType<2/Creature.SharesColorWith/untapped creature you control that shares a color with " + source.getName() + ">"; - newSA.setPayCosts(new Cost(conspireCost, false).add(newSA.getPayCosts())); - final String tag = kwInstance > 1 ? " (Conspire " + kwInstance + ")" : " (Conspire)"; - newSA.setDescription(newSA.getDescription() + tag); - newSA.addOptionalCost(OptionalCost.Conspire); - newSA.addConspireInstance(); - if (newSA.canPlay()) { - abilities.add(++i, newSA); - } - } - } - } - - // Splice - final List newAbilities = new ArrayList(); - for (SpellAbility sa : abilities) { - if (sa.isSpell() && sa.getSourceCard().isType("Arcane") && sa.getApi() != null ) { - newAbilities.addAll(GameActionUtil.getSpliceAbilities(sa)); - } - } - abilities.addAll(newAbilities); - return abilities; - } - - /** - *

- * getSpliceAbilities. - *

- * - * @param sa - * a SpellAbility. - * @return an ArrayList. - * get abilities with all Splice options - */ - private static final ArrayList getSpliceAbilities(SpellAbility sa) { - ArrayList newSAs = new ArrayList(); - ArrayList allSaCombinations = new ArrayList(); - allSaCombinations.add(sa); - Card source = sa.getSourceCard(); - - for (Card c : sa.getActivatingPlayer().getCardsIn(ZoneType.Hand)) { - if (c.equals(source)) { - continue; - } - - String spliceKwCost = null; - for (String keyword : c.getKeyword()) { - if (keyword.startsWith("Splice")) { - spliceKwCost = keyword.substring(19); - break; - } - } - - if (spliceKwCost == null) - continue; - - Map params = AbilityFactory.getMapParams(c.getCharacteristics().getUnparsedAbilities().get(0)); - AbilityRecordType rc = AbilityRecordType.getRecordType(params); - ApiType api = rc.getApiTypeOf(params); - AbilitySub subAbility = (AbilitySub) AbilityFactory.getAbility(AbilityRecordType.SubAbility, api, params, null, c); - - // Add the subability to all existing variants - for (int i = 0; i < allSaCombinations.size(); ++i) { - //create a new spell copy - final SpellAbility newSA = allSaCombinations.get(i).copy(); - newSA.setBasicSpell(false); - newSA.setPayCosts(new Cost(spliceKwCost, false).add(newSA.getPayCosts())); - newSA.setDescription(newSA.getDescription() + " (Splicing " + c + " onto it)"); - newSA.addSplicedCards(c); - - // copy all subAbilities - SpellAbility child = newSA; - while (child.getSubAbility() != null) { - AbilitySub newChild = child.getSubAbility().getCopy(); - child.setSubAbility(newChild); - child.setActivatingPlayer(newSA.getActivatingPlayer()); - child = newChild; - } - - //add the spliced ability to the end of the chain - child.setSubAbility(subAbility); - - //set correct source and activating player to all the spliced abilities - child = subAbility; - while (child != null) { - child.setSourceCard(source); - child.setActivatingPlayer(newSA.getActivatingPlayer()); - child = child.getSubAbility(); - } - newSAs.add(newSA); - allSaCombinations.add(++i, newSA); - } - } - return newSAs; - } - - /** - *

- * hasUrzaLands. - *

- * - * @param p - * a {@link forge.game.player.Player} object. - * @return a boolean. - */ - private static boolean hasUrzaLands(final Player p) { - final List landsControlled = p.getCardsIn(ZoneType.Battlefield); - return Iterables.any(landsControlled, CardPredicates.nameEquals("Urza's Mine")) - && Iterables.any(landsControlled, CardPredicates.nameEquals("Urza's Tower")) - && Iterables.any(landsControlled, CardPredicates.nameEquals("Urza's Power Plant")); - } - - /** - *

- * generatedMana. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a {@link java.lang.String} object. - */ - public static String generatedMana(final SpellAbility sa) { - // Calculate generated mana here for stack description and resolving - - int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("Amount"), sa) : 1; - - AbilityManaPart abMana = sa.getManaPart(); - String baseMana; - if (abMana.isComboMana()) { - baseMana = abMana.getExpressChoice(); - if (baseMana.isEmpty()) { - baseMana = abMana.getOrigProduced(); - } - } else if (abMana.isAnyMana()) { - baseMana = abMana.getExpressChoice(); - if (baseMana.isEmpty()) { - baseMana = "Any"; - } - } else if (sa.getApi() == ApiType.ManaReflected) { - baseMana = abMana.getExpressChoice(); - } else if (abMana.isSpecialMana()) { - baseMana = abMana.getExpressChoice(); - } else { - baseMana = abMana.mana(); - } - - if (sa.hasParam("Bonus")) { - // For mana abilities that get a bonus - // Bonus currently MULTIPLIES the base amount. Base Amounts should - // ALWAYS be Base - int bonus = 0; - if (sa.getParam("Bonus").equals("UrzaLands")) { - if (hasUrzaLands(sa.getActivatingPlayer())) { - bonus = Integer.parseInt(sa.getParam("BonusProduced")); - } - } - - amount += bonus; - } - - if (sa.getSubAbility() != null) { - // Mark SAs with subAbilities as undoable. These are generally things like damage, and other stuff - // that's hard to track and remove - sa.setUndoable(false); - } else { - try { - if ((sa.getParam("Amount") != null) && (amount != Integer.parseInt(sa.getParam("Amount")))) { - sa.setUndoable(false); - } - } catch (final NumberFormatException n) { - sa.setUndoable(false); - } - } - - final StringBuilder sb = new StringBuilder(); - if (amount == 0) { - sb.append("0"); - } else if (abMana.isComboMana()) { - // amount is already taken care of in resolve method for combination mana, just append baseMana - sb.append(baseMana); - } else { - if (StringUtils.isNumeric(baseMana)) { - sb.append(amount * Integer.parseInt(baseMana)); - } else { - sb.append(baseMana); - for (int i = 1; i < amount; i++) { - sb.append(" ").append(baseMana); - } - } - } - return sb.toString(); - } -} // end class GameActionUtil +/* + * 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 . + */ +package forge.game; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import forge.card.MagicColor; +import forge.game.ability.AbilityFactory; +import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; +import forge.game.ability.AbilityFactory.AbilityRecordType; +import forge.game.card.Card; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +import forge.game.cost.Cost; +import forge.game.mana.ManaCostBeingPaid; +import forge.game.player.Player; +import forge.game.spellability.AbilityActivated; +import forge.game.spellability.AbilityManaPart; +import forge.game.spellability.AbilitySub; +import forge.game.spellability.OptionalCost; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityRestriction; +import forge.game.zone.ZoneType; +import forge.util.TextUtil; + + +/** + *

+ * GameActionUtil class. + *

+ * + * @author Forge + * @version $Id$ + */ +public final class GameActionUtil { + + + private GameActionUtil() { + throw new AssertionError(); + } + + // restricted to combat damage, restricted to players + /** + *

+ * executeCombatDamageToPlayerEffects. + *

+ * + * @param player + * a {@link forge.game.player.Player} object. + * @param c + * a {@link forge.game.card.Card} object. + * @param damage + * a int. + */ + public static void executeCombatDamageToPlayerEffects(final Player player, final Card c, final int damage) { + + if (damage <= 0) { + return; + } + + for (final String key : c.getKeyword()) { + if (!key.startsWith("Poisonous ")) continue; + final String[] k = key.split(" ", 2); + final int poison = Integer.parseInt(k[1]); + // Now can be copied by Strionic Resonator + String effect = "AB$ Poison | Cost$ 0 | Defined$ PlayerNamed_" + player.getName() + " | Num$ " + k[1]; + SpellAbility ability = AbilityFactory.getAbility(effect, c); + + final StringBuilder sb = new StringBuilder(); + sb.append(c); + sb.append(" - Poisonous: "); + sb.append(player); + sb.append(" gets ").append(poison).append(" poison counter"); + if (poison != 1) { + sb.append("s"); + } + sb.append("."); + + ability.setActivatingPlayer(c.getController()); + ability.setDescription(sb.toString()); + ability.setStackDescription(sb.toString()); + ability.setTrigger(true); + + player.getGame().getStack().addSimultaneousStackEntry(ability); + + } + + c.getDamageHistory().registerCombatDamage(player); + } // executeCombatDamageToPlayerEffects + + /** + * Gets the st land mana abilities. + * @param game + * + * @return the stLandManaAbilities + */ + public static void grantBasicLandsManaAbilities(List lands) { + // remove all abilities granted by this Command + for (final Card land : lands) { + List origManaAbs = Lists.newArrayList(land.getManaAbility()); + List manaAbs = land.getCharacteristics().getManaAbility(); + // will get comodification exception without a different list + for (final SpellAbility sa : origManaAbs) { + if (sa.isBasicLandAbility()) { + manaAbs.remove(sa); + } + } + } + + // add all appropriate mana abilities based on current types + for (int i = 0; i < MagicColor.WUBRG.length; i++ ) { + String landType = MagicColor.Constant.BASIC_LANDS.get(i); + String color = MagicColor.toShortString(MagicColor.WUBRG[i]); + String abString = "AB$ Mana | Cost$ T | Produced$ " + color + " | SpellDescription$ Add {" + color + "} to your mana pool."; + for (final Card land : lands) { + if (land.isType(landType)) { + final SpellAbility sa = AbilityFactory.getAbility(abString, land); + sa.setBasicLandAbility(true); + land.getCharacteristics().getManaAbility().add(sa); + } + } + } + } // stLandManaAbilities + + /** + *

+ * getAlternativeCosts. + *

+ * + * @param sa + * a SpellAbility. + * @return an ArrayList. + * get alternative costs as additional spell abilities + */ + public static final ArrayList getAlternativeCosts(SpellAbility sa) { + ArrayList alternatives = new ArrayList(); + Card source = sa.getSourceCard(); + if (!sa.isBasicSpell()) { + return alternatives; + } + for (final String keyword : source.getKeyword()) { + if (sa.isSpell() && keyword.startsWith("Flashback")) { + final SpellAbility flashback = sa.copy(); + flashback.setFlashBackAbility(true); + SpellAbilityRestriction sar = new SpellAbilityRestriction(); + sar.setVariables(sa.getRestrictions()); + sar.setZone(ZoneType.Graveyard); + flashback.setRestrictions(sar); + + // there is a flashback cost (and not the cards cost) + if (!keyword.equals("Flashback")) { + flashback.setPayCosts(new Cost(keyword.substring(10), false)); + } + alternatives.add(flashback); + } + if (sa.isSpell() && keyword.equals("May be played without paying its mana cost")) { + final SpellAbility newSA = sa.copy(); + SpellAbilityRestriction sar = new SpellAbilityRestriction(); + sar.setVariables(sa.getRestrictions()); + sar.setZone(null); + newSA.setRestrictions(sar); + newSA.setBasicSpell(false); + newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana()); + newSA.setDescription(sa.getDescription() + " (without paying its mana cost)"); + alternatives.add(newSA); + } + if (sa.isSpell() && keyword.equals("May be played by your opponent without paying its mana cost")) { + final SpellAbility newSA = sa.copy(); + SpellAbilityRestriction sar = new SpellAbilityRestriction(); + sar.setVariables(sa.getRestrictions()); + sar.setZone(null); + sar.setOpponentOnly(true); + newSA.setRestrictions(sar); + newSA.setBasicSpell(false); + newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana()); + newSA.setDescription(sa.getDescription() + " (without paying its mana cost)"); + alternatives.add(newSA); + } + if (sa.isSpell() && keyword.startsWith("May be played without paying its mana cost and as though it has flash")) { + final SpellAbility newSA = sa.copy(); + SpellAbilityRestriction sar = new SpellAbilityRestriction(); + sar.setVariables(sa.getRestrictions()); + sar.setInstantSpeed(true); + newSA.setRestrictions(sar); + newSA.setBasicSpell(false); + newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana()); + newSA.setDescription(sa.getDescription() + " (without paying its mana cost and as though it has flash)"); + alternatives.add(newSA); + } + if (sa.isSpell() && keyword.startsWith("Alternative Cost")) { + final SpellAbility newSA = sa.copy(); + newSA.setBasicSpell(false); + String kw = keyword; + if (keyword.contains("ConvertedManaCost")) { + final String cmc = Integer.toString(sa.getSourceCard().getCMC()); + kw = keyword.replace("ConvertedManaCost", cmc); + } + final Cost cost = new Cost(kw.substring(17), false).add(newSA.getPayCosts().copyWithNoMana()); + newSA.setPayCosts(cost); + newSA.setDescription(sa.getDescription() + " (by paying " + cost.toSimpleString() + " instead of its mana cost)"); + alternatives.add(newSA); + } + if (sa.isSpell() && keyword.equals("You may cast CARDNAME any time you could cast an instant if you pay 2 more to cast it.")) { + final SpellAbility newSA = sa.copy(); + newSA.setBasicSpell(false); + ManaCostBeingPaid newCost = new ManaCostBeingPaid(source.getManaCost()); + newCost.increaseColorlessMana(2); + final Cost actualcost = new Cost(newCost.toManaCost(), false); + newSA.setPayCosts(actualcost); + SpellAbilityRestriction sar = new SpellAbilityRestriction(); + sar.setVariables(sa.getRestrictions()); + sar.setInstantSpeed(true); + newSA.setRestrictions(sar); + newSA.setDescription(sa.getDescription() + " (by paying " + actualcost.toSimpleString() + " instead of its mana cost)"); + alternatives.add(newSA); + } + if (sa.isSpell() && keyword.endsWith(" offering")) { + final String offeringType = keyword.split(" ")[0]; + List canOffer = CardLists.filter(sa.getSourceCard().getController().getCardsIn(ZoneType.Battlefield), + CardPredicates.isType(offeringType)); + if (source.getController().hasKeyword("You can't sacrifice creatures to cast spells or activate abilities.")) { + canOffer = CardLists.getNotType(canOffer, "Creature"); + } + if (!canOffer.isEmpty()) { + final SpellAbility newSA = sa.copy(); + SpellAbilityRestriction sar = new SpellAbilityRestriction(); + sar.setVariables(sa.getRestrictions()); + sar.setInstantSpeed(true); + newSA.setRestrictions(sar); + newSA.setBasicSpell(false); + newSA.setIsOffering(true); + newSA.setPayCosts(sa.getPayCosts()); + newSA.setDescription(sa.getDescription() + " (" + offeringType + " offering)"); + alternatives.add(newSA); + } + } + if (sa.hasParam("Equip") && sa instanceof AbilityActivated && keyword.equals("EquipInstantSpeed")) { + final SpellAbility newSA = ((AbilityActivated) sa).getCopy(); + SpellAbilityRestriction sar = new SpellAbilityRestriction(); + sar.setVariables(sa.getRestrictions()); + sar.setSorcerySpeed(false); + sar.setInstantSpeed(true); + newSA.setRestrictions(sar); + newSA.setDescription(sa.getDescription() + " (you may activate any time you could cast an instant )"); + alternatives.add(newSA); + } + } + return alternatives; + } + + /** + * get optional additional costs. + * + * @param original + * the original sa + * @return an ArrayList. + */ + public static List getOptionalCosts(final SpellAbility original) { + final List abilities = new ArrayList(); + + final Card source = original.getSourceCard(); + abilities.add(original); + if (!original.isSpell()) { + return abilities; + } + + // Buyback, Kicker + for (String keyword : source.getKeyword()) { + if (keyword.startsWith("AlternateAdditionalCost")) { + final List newAbilities = new ArrayList(); + String[] costs = TextUtil.split(keyword, ':'); + for (SpellAbility sa : abilities) { + final SpellAbility newSA = sa.copy(); + newSA.setBasicSpell(false); + + final Cost cost1 = new Cost(costs[1], false); + newSA.setDescription(sa.getDescription() + " (Additional cost " + cost1.toSimpleString() + ")"); + newSA.setPayCosts(cost1.add(sa.getPayCosts())); + if (newSA.canPlay()) { + newAbilities.add(newSA); + } + + //second option + final SpellAbility newSA2 = sa.copy(); + newSA2.setBasicSpell(false); + + final Cost cost2 = new Cost(costs[2], false); + newSA2.setDescription(sa.getDescription() + " (Additional cost " + cost2.toSimpleString() + ")"); + newSA2.setPayCosts(cost2.add(sa.getPayCosts())); + if (newSA2.canPlay()) { + newAbilities.add(newAbilities.size(), newSA2); + } + } + abilities.clear(); + abilities.addAll(newAbilities); + } else if (keyword.startsWith("Buyback")) { + for (int i = 0; i < abilities.size(); i++) { + final SpellAbility newSA = abilities.get(i).copy(); + newSA.setBasicSpell(false); + newSA.setPayCosts(new Cost(keyword.substring(8), false).add(newSA.getPayCosts())); + newSA.setDescription(newSA.getDescription() + " (with Buyback)"); + newSA.addOptionalCost(OptionalCost.Buyback); + if (newSA.canPlay()) { + abilities.add(i, newSA); + i++; + } + } + } else if (keyword.startsWith("Entwine")) { + for (int i = 0; i < abilities.size(); i++) { + final SpellAbility newSA = abilities.get(i).copy(); + SpellAbility entwine = AbilityFactory.buildEntwineAbility(newSA); + entwine.setPayCosts(new Cost(keyword.substring(8), false).add(newSA.getPayCosts())); + entwine.addOptionalCost(OptionalCost.Entwine); + if (newSA.canPlay()) { + abilities.add(i, entwine); + i++; + } + } + } else if (keyword.startsWith("Kicker")) { + for (int i = 0; i < abilities.size(); i++) { + String[] sCosts = TextUtil.split(keyword.substring(7), ':'); + int iUnKicked = i; + for (int j = 0; j < sCosts.length; j++) { + final SpellAbility newSA = abilities.get(iUnKicked).copy(); + newSA.setBasicSpell(false); + final Cost cost = new Cost(sCosts[j], false); + newSA.setDescription(newSA.getDescription() + " (Kicker " + cost.toSimpleString() + ")"); + newSA.setPayCosts(cost.add(newSA.getPayCosts())); + newSA.addOptionalCost(j == 0 ? OptionalCost.Kicker1 : OptionalCost.Kicker2); + if (newSA.canPlay()) { + abilities.add(i, newSA); + i++; + iUnKicked++; + } + } + if (sCosts.length == 2) { // case for both kickers - it's hardcoded since they never have more than 2 kickers + final SpellAbility newSA = abilities.get(iUnKicked).copy(); + newSA.setBasicSpell(false); + final Cost cost1 = new Cost(sCosts[0], false); + final Cost cost2 = new Cost(sCosts[1], false); + newSA.setDescription(newSA.getDescription() + String.format(" (Both kickers: %s and %s)", cost1.toSimpleString(), cost2.toSimpleString())); + newSA.setPayCosts(cost2.add(cost1.add(newSA.getPayCosts()))); + newSA.addOptionalCost(OptionalCost.Kicker1); + newSA.addOptionalCost(OptionalCost.Kicker2); + if (newSA.canPlay()) { + abilities.add(i, newSA); + i++; + } + } + } + } + } + + if (source.hasKeyword("Conspire")) { + int amount = source.getAmountOfKeyword("Conspire"); + for (int kwInstance = 1; kwInstance <= amount; kwInstance++) { + for (int i = 0; i < abilities.size(); i++) { + final SpellAbility newSA = abilities.get(i).copy(); + newSA.setBasicSpell(false); + final String conspireCost = "tapXType<2/Creature.SharesColorWith/untapped creature you control that shares a color with " + source.getName() + ">"; + newSA.setPayCosts(new Cost(conspireCost, false).add(newSA.getPayCosts())); + final String tag = kwInstance > 1 ? " (Conspire " + kwInstance + ")" : " (Conspire)"; + newSA.setDescription(newSA.getDescription() + tag); + newSA.addOptionalCost(OptionalCost.Conspire); + newSA.addConspireInstance(); + if (newSA.canPlay()) { + abilities.add(++i, newSA); + } + } + } + } + + // Splice + final List newAbilities = new ArrayList(); + for (SpellAbility sa : abilities) { + if (sa.isSpell() && sa.getSourceCard().isType("Arcane") && sa.getApi() != null ) { + newAbilities.addAll(GameActionUtil.getSpliceAbilities(sa)); + } + } + abilities.addAll(newAbilities); + return abilities; + } + + /** + *

+ * getSpliceAbilities. + *

+ * + * @param sa + * a SpellAbility. + * @return an ArrayList. + * get abilities with all Splice options + */ + private static final ArrayList getSpliceAbilities(SpellAbility sa) { + ArrayList newSAs = new ArrayList(); + ArrayList allSaCombinations = new ArrayList(); + allSaCombinations.add(sa); + Card source = sa.getSourceCard(); + + for (Card c : sa.getActivatingPlayer().getCardsIn(ZoneType.Hand)) { + if (c.equals(source)) { + continue; + } + + String spliceKwCost = null; + for (String keyword : c.getKeyword()) { + if (keyword.startsWith("Splice")) { + spliceKwCost = keyword.substring(19); + break; + } + } + + if (spliceKwCost == null) + continue; + + Map params = AbilityFactory.getMapParams(c.getCharacteristics().getUnparsedAbilities().get(0)); + AbilityRecordType rc = AbilityRecordType.getRecordType(params); + ApiType api = rc.getApiTypeOf(params); + AbilitySub subAbility = (AbilitySub) AbilityFactory.getAbility(AbilityRecordType.SubAbility, api, params, null, c); + + // Add the subability to all existing variants + for (int i = 0; i < allSaCombinations.size(); ++i) { + //create a new spell copy + final SpellAbility newSA = allSaCombinations.get(i).copy(); + newSA.setBasicSpell(false); + newSA.setPayCosts(new Cost(spliceKwCost, false).add(newSA.getPayCosts())); + newSA.setDescription(newSA.getDescription() + " (Splicing " + c + " onto it)"); + newSA.addSplicedCards(c); + + // copy all subAbilities + SpellAbility child = newSA; + while (child.getSubAbility() != null) { + AbilitySub newChild = child.getSubAbility().getCopy(); + child.setSubAbility(newChild); + child.setActivatingPlayer(newSA.getActivatingPlayer()); + child = newChild; + } + + //add the spliced ability to the end of the chain + child.setSubAbility(subAbility); + + //set correct source and activating player to all the spliced abilities + child = subAbility; + while (child != null) { + child.setSourceCard(source); + child.setActivatingPlayer(newSA.getActivatingPlayer()); + child = child.getSubAbility(); + } + newSAs.add(newSA); + allSaCombinations.add(++i, newSA); + } + } + return newSAs; + } + + /** + *

+ * hasUrzaLands. + *

+ * + * @param p + * a {@link forge.game.player.Player} object. + * @return a boolean. + */ + private static boolean hasUrzaLands(final Player p) { + final List landsControlled = p.getCardsIn(ZoneType.Battlefield); + return Iterables.any(landsControlled, CardPredicates.nameEquals("Urza's Mine")) + && Iterables.any(landsControlled, CardPredicates.nameEquals("Urza's Tower")) + && Iterables.any(landsControlled, CardPredicates.nameEquals("Urza's Power Plant")); + } + + /** + *

+ * generatedMana. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a {@link java.lang.String} object. + */ + public static String generatedMana(final SpellAbility sa) { + // Calculate generated mana here for stack description and resolving + + int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("Amount"), sa) : 1; + + AbilityManaPart abMana = sa.getManaPart(); + String baseMana; + if (abMana.isComboMana()) { + baseMana = abMana.getExpressChoice(); + if (baseMana.isEmpty()) { + baseMana = abMana.getOrigProduced(); + } + } else if (abMana.isAnyMana()) { + baseMana = abMana.getExpressChoice(); + if (baseMana.isEmpty()) { + baseMana = "Any"; + } + } else if (sa.getApi() == ApiType.ManaReflected) { + baseMana = abMana.getExpressChoice(); + } else if (abMana.isSpecialMana()) { + baseMana = abMana.getExpressChoice(); + } else { + baseMana = abMana.mana(); + } + + if (sa.hasParam("Bonus")) { + // For mana abilities that get a bonus + // Bonus currently MULTIPLIES the base amount. Base Amounts should + // ALWAYS be Base + int bonus = 0; + if (sa.getParam("Bonus").equals("UrzaLands")) { + if (hasUrzaLands(sa.getActivatingPlayer())) { + bonus = Integer.parseInt(sa.getParam("BonusProduced")); + } + } + + amount += bonus; + } + + if (sa.getSubAbility() != null) { + // Mark SAs with subAbilities as undoable. These are generally things like damage, and other stuff + // that's hard to track and remove + sa.setUndoable(false); + } else { + try { + if ((sa.getParam("Amount") != null) && (amount != Integer.parseInt(sa.getParam("Amount")))) { + sa.setUndoable(false); + } + } catch (final NumberFormatException n) { + sa.setUndoable(false); + } + } + + final StringBuilder sb = new StringBuilder(); + if (amount == 0) { + sb.append("0"); + } else if (abMana.isComboMana()) { + // amount is already taken care of in resolve method for combination mana, just append baseMana + sb.append(baseMana); + } else { + if (StringUtils.isNumeric(baseMana)) { + sb.append(amount * Integer.parseInt(baseMana)); + } else { + sb.append(baseMana); + for (int i = 1; i < amount; i++) { + sb.append(" ").append(baseMana); + } + } + } + return sb.toString(); + } +} // end class GameActionUtil diff --git a/forge-game/src/main/java/forge/game/GameEndReason.java b/forge-gui/src/main/java/forge/game/GameEndReason.java similarity index 100% rename from forge-game/src/main/java/forge/game/GameEndReason.java rename to forge-gui/src/main/java/forge/game/GameEndReason.java diff --git a/forge-game/src/main/java/forge/game/GameEntity.java b/forge-gui/src/main/java/forge/game/GameEntity.java similarity index 100% rename from forge-game/src/main/java/forge/game/GameEntity.java rename to forge-gui/src/main/java/forge/game/GameEntity.java diff --git a/forge-game/src/main/java/forge/game/GameLog.java b/forge-gui/src/main/java/forge/game/GameLog.java similarity index 94% rename from forge-game/src/main/java/forge/game/GameLog.java rename to forge-gui/src/main/java/forge/game/GameLog.java index 88142d6816b..f199f9aeb1a 100644 --- a/forge-game/src/main/java/forge/game/GameLog.java +++ b/forge-gui/src/main/java/forge/game/GameLog.java @@ -24,8 +24,9 @@ import java.util.Observable; import org.apache.commons.lang3.StringUtils; -import forge.PreferencesBridge; +import forge.Singletons; import forge.game.event.IGameEventVisitor; +import forge.properties.ForgePreferences.FPref; /** @@ -107,7 +108,7 @@ public class GameLog extends Observable { } public GameLogEntryType getGameLogEntryTypeSetting() { - String logEntryType = PreferencesBridge.Instance.getLogEntryType(); + String logEntryType = Singletons.getModel().getPreferences().getPref(FPref.DEV_LOG_ENTRY_TYPE); return GameLogEntryType.valueOf(logEntryType); } diff --git a/forge-game/src/main/java/forge/game/GameLogEntry.java b/forge-gui/src/main/java/forge/game/GameLogEntry.java similarity index 100% rename from forge-game/src/main/java/forge/game/GameLogEntry.java rename to forge-gui/src/main/java/forge/game/GameLogEntry.java diff --git a/forge-game/src/main/java/forge/game/GameLogEntryType.java b/forge-gui/src/main/java/forge/game/GameLogEntryType.java similarity index 100% rename from forge-game/src/main/java/forge/game/GameLogEntryType.java rename to forge-gui/src/main/java/forge/game/GameLogEntryType.java diff --git a/forge-game/src/main/java/forge/game/GameLogFormatter.java b/forge-gui/src/main/java/forge/game/GameLogFormatter.java similarity index 100% rename from forge-game/src/main/java/forge/game/GameLogFormatter.java rename to forge-gui/src/main/java/forge/game/GameLogFormatter.java diff --git a/forge-game/src/main/java/forge/game/GameObject.java b/forge-gui/src/main/java/forge/game/GameObject.java similarity index 100% rename from forge-game/src/main/java/forge/game/GameObject.java rename to forge-gui/src/main/java/forge/game/GameObject.java diff --git a/forge-game/src/main/java/forge/game/GameOutcome.java b/forge-gui/src/main/java/forge/game/GameOutcome.java similarity index 100% rename from forge-game/src/main/java/forge/game/GameOutcome.java rename to forge-gui/src/main/java/forge/game/GameOutcome.java diff --git a/forge-game/src/main/java/forge/game/GameStage.java b/forge-gui/src/main/java/forge/game/GameStage.java similarity index 100% rename from forge-game/src/main/java/forge/game/GameStage.java rename to forge-gui/src/main/java/forge/game/GameStage.java diff --git a/forge-game/src/main/java/forge/game/GameType.java b/forge-gui/src/main/java/forge/game/GameType.java similarity index 100% rename from forge-game/src/main/java/forge/game/GameType.java rename to forge-gui/src/main/java/forge/game/GameType.java diff --git a/forge-game/src/main/java/forge/game/GlobalRuleChange.java b/forge-gui/src/main/java/forge/game/GlobalRuleChange.java similarity index 100% rename from forge-game/src/main/java/forge/game/GlobalRuleChange.java rename to forge-gui/src/main/java/forge/game/GlobalRuleChange.java diff --git a/forge-game/src/main/java/forge/game/Match.java b/forge-gui/src/main/java/forge/game/Match.java similarity index 98% rename from forge-game/src/main/java/forge/game/Match.java rename to forge-gui/src/main/java/forge/game/Match.java index c188ab74c6d..b477363f8a2 100644 --- a/forge-game/src/main/java/forge/game/Match.java +++ b/forge-gui/src/main/java/forge/game/Match.java @@ -16,7 +16,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; -import forge.PreferencesBridge; +import forge.Singletons; import forge.deck.CardPool; import forge.deck.Deck; import forge.deck.DeckSection; @@ -30,6 +30,7 @@ import forge.game.trigger.Trigger; import forge.game.zone.PlayerZone; import forge.game.zone.ZoneType; import forge.item.PaperCard; +import forge.properties.ForgePreferences.FPref; import forge.util.MyRandom; public class Match { @@ -97,7 +98,7 @@ public class Match { * TODO: Write javadoc for this method. */ public void startGame(final Game game, final CountDownLatch latch) { - final boolean canRandomFoil = PreferencesBridge.Instance.canRandomFoil() && gameType == GameType.Constructed; + final boolean canRandomFoil = Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_RANDOM_FOIL) && gameType == GameType.Constructed; // This code could be run run from EDT. game.getAction().invoke(new Runnable() { diff --git a/forge-game/src/main/java/forge/game/PlanarDice.java b/forge-gui/src/main/java/forge/game/PlanarDice.java similarity index 100% rename from forge-game/src/main/java/forge/game/PlanarDice.java rename to forge-gui/src/main/java/forge/game/PlanarDice.java diff --git a/forge-game/src/main/java/forge/game/StaticEffect.java b/forge-gui/src/main/java/forge/game/StaticEffect.java similarity index 95% rename from forge-game/src/main/java/forge/game/StaticEffect.java rename to forge-gui/src/main/java/forge/game/StaticEffect.java index c4fa2f02c55..786ce288e1b 100644 --- a/forge-game/src/main/java/forge/game/StaticEffect.java +++ b/forge-gui/src/main/java/forge/game/StaticEffect.java @@ -1,880 +1,880 @@ -/* - * 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 . - */ -package forge.game; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import forge.game.card.Card; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; - -/** - *

- * StaticEffect class. - *

- * - * @author Forge - * @version $Id: StaticEffect.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class StaticEffect { - private final Card source; - private int keywordNumber = 0; - private List affectedCards = new ArrayList(); - private ArrayList affectedPlayers = new ArrayList(); - private int xValue = 0; - private int yValue = 0; - private long timestamp = -1; - private HashMap xValueMap = new HashMap(); - - private String chosenType; - private HashMap mapParams = new HashMap(); - - // for P/T - private final HashMap originalPT = new HashMap(); - - // for types - private boolean overwriteTypes = false; - private boolean keepSupertype = false; - private boolean removeSubTypes = false; - private final HashMap> types = new HashMap>(); - private final HashMap> originalTypes = new HashMap>(); - - // keywords - private boolean overwriteKeywords = false; - private final HashMap> originalKeywords = new HashMap>(); - - // for abilities - private boolean overwriteAbilities = false; - private final HashMap> originalAbilities = new HashMap>(); - - // for colors - private String colorDesc = ""; - private boolean overwriteColors = false; - private final HashMap timestamps = new HashMap(); - - public StaticEffect(Card source) { - this.source = source; - } - - /** - * setTimestamp TODO Write javadoc for this method. - * - * @param t - * a long - */ - public final void setTimestamp(final long t) { - this.timestamp = t; - } - - /** - * getTimestamp. TODO Write javadoc for this method. - * - * @return a long - */ - public final long getTimestamp() { - return this.timestamp; - } - - // overwrite SAs - /** - *

- * isOverwriteAbilities. - *

- * - * @return a boolean. - */ - public final boolean isOverwriteAbilities() { - return this.overwriteAbilities; - } - - /** - *

- * Setter for the field overwriteAbilities. - *

- * - * @param overwriteAbilitiesIn - * a boolean. - */ - public final void setOverwriteAbilities(final boolean overwriteAbilitiesIn) { - this.overwriteAbilities = overwriteAbilitiesIn; - } - - // original SAs - /** - *

- * addOriginalAbilities. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - */ - public final void addOriginalAbilities(final Card c, final SpellAbility sa) { - if (!this.originalAbilities.containsKey(c)) { - final ArrayList list = new ArrayList(); - list.add(sa); - this.originalAbilities.put(c, list); - } else { - this.originalAbilities.get(c).add(sa); - } - } - - /** - *

- * addOriginalAbilities. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param s - * a {@link java.util.ArrayList} object. - */ - public final void addOriginalAbilities(final Card c, final ArrayList s) { - final ArrayList list = new ArrayList(s); - if (!this.originalAbilities.containsKey(c)) { - this.originalAbilities.put(c, list); - } else { - this.originalAbilities.remove(c); - this.originalAbilities.put(c, list); - } - } - - /** - *

- * Getter for the field originalAbilities. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getOriginalAbilities(final Card c) { - final ArrayList returnList = new ArrayList(); - if (this.originalAbilities.containsKey(c)) { - returnList.addAll(this.originalAbilities.get(c)); - } - return returnList; - } - - /** - *

- * clearOriginalAbilities. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - */ - public final void clearOriginalAbilities(final Card c) { - if (this.originalAbilities.containsKey(c)) { - this.originalAbilities.get(c).clear(); - } - } - - /** - *

- * clearAllOriginalAbilities. - *

- */ - public final void clearAllOriginalAbilities() { - this.originalAbilities.clear(); - } - - // overwrite keywords - /** - *

- * isOverwriteKeywords. - *

- * - * @return a boolean. - */ - public final boolean isOverwriteKeywords() { - return this.overwriteKeywords; - } - - /** - *

- * Setter for the field overwriteKeywords. - *

- * - * @param overwriteKeywordsIn - * a boolean. - */ - public final void setOverwriteKeywords(final boolean overwriteKeywordsIn) { - this.overwriteKeywords = overwriteKeywordsIn; - } - - // original keywords - /** - *

- * addOriginalKeyword. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param s - * a {@link java.lang.String} object. - */ - public final void addOriginalKeyword(final Card c, final String s) { - if (!this.originalKeywords.containsKey(c)) { - final ArrayList list = new ArrayList(); - list.add(s); - this.originalKeywords.put(c, list); - } else { - this.originalKeywords.get(c).add(s); - } - } - - /** - *

- * addOriginalKeywords. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param s - * a {@link java.util.ArrayList} object. - */ - public final void addOriginalKeywords(final Card c, final ArrayList s) { - final ArrayList list = new ArrayList(s); - if (!this.originalKeywords.containsKey(c)) { - this.originalKeywords.put(c, list); - } else { - this.originalKeywords.remove(c); - this.originalKeywords.put(c, list); - } - } - - /** - *

- * Getter for the field originalKeywords. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getOriginalKeywords(final Card c) { - final ArrayList returnList = new ArrayList(); - if (this.originalKeywords.containsKey(c)) { - returnList.addAll(this.originalKeywords.get(c)); - } - return returnList; - } - - /** - *

- * clearOriginalKeywords. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - */ - public final void clearOriginalKeywords(final Card c) { - if (this.originalKeywords.containsKey(c)) { - this.originalKeywords.get(c).clear(); - } - } - - /** - *

- * clearAllOriginalKeywords. - *

- */ - public final void clearAllOriginalKeywords() { - this.originalKeywords.clear(); - } - - // original power/toughness - /** - *

- * addOriginalPT. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param power - * a int. - * @param toughness - * a int. - */ - public final void addOriginalPT(final Card c, final int power, final int toughness) { - final String pt = power + "/" + toughness; - if (!this.originalPT.containsKey(c)) { - this.originalPT.put(c, pt); - } - } - - /** - *

- * getOriginalPower. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a int. - */ - public final int getOriginalPower(final Card c) { - int power = -1; - if (this.originalPT.containsKey(c)) { - power = Integer.parseInt(this.originalPT.get(c).split("/")[0]); - } - return power; - } - - /** - *

- * getOriginalToughness. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a int. - */ - public final int getOriginalToughness(final Card c) { - int tough = -1; - if (this.originalPT.containsKey(c)) { - tough = Integer.parseInt(this.originalPT.get(c).split("/")[1]); - } - return tough; - } - - /** - *

- * clearAllOriginalPTs. - *

- */ - public final void clearAllOriginalPTs() { - this.originalPT.clear(); - } - - // should we overwrite types? - /** - *

- * isOverwriteTypes. - *

- * - * @return a boolean. - */ - public final boolean isOverwriteTypes() { - return this.overwriteTypes; - } - - /** - *

- * Setter for the field overwriteTypes. - *

- * - * @param overwriteTypesIn - * a boolean. - */ - public final void setOverwriteTypes(final boolean overwriteTypesIn) { - this.overwriteTypes = overwriteTypesIn; - } - - /** - *

- * isKeepSupertype. - *

- * - * @return a boolean. - */ - public final boolean isKeepSupertype() { - return this.keepSupertype; - } - - /** - *

- * Setter for the field keepSupertype. - *

- * - * @param keepSupertypeIn - * a boolean. - */ - public final void setKeepSupertype(final boolean keepSupertypeIn) { - this.keepSupertype = keepSupertypeIn; - } - - // should we overwrite land types? - /** - *

- * isRemoveSubTypes. - *

- * - * @return a boolean. - */ - public final boolean isRemoveSubTypes() { - return this.removeSubTypes; - } - - /** - *

- * Setter for the field removeSubTypes. - *

- * - * @param removeSubTypesIn - * a boolean. - */ - public final void setRemoveSubTypes(final boolean removeSubTypesIn) { - this.removeSubTypes = removeSubTypesIn; - } - - // original types - /** - *

- * addOriginalType. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param s - * a {@link java.lang.String} object. - */ - public final void addOriginalType(final Card c, final String s) { - if (!this.originalTypes.containsKey(c)) { - final ArrayList list = new ArrayList(); - list.add(s); - this.originalTypes.put(c, list); - } else { - this.originalTypes.get(c).add(s); - } - } - - /** - *

- * addOriginalTypes. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param s - * a {@link java.util.ArrayList} object. - */ - public final void addOriginalTypes(final Card c, final ArrayList s) { - final ArrayList list = new ArrayList(s); - if (!this.originalTypes.containsKey(c)) { - this.originalTypes.put(c, list); - } else { - this.originalTypes.remove(c); - this.originalTypes.put(c, list); - } - } - - /** - *

- * Getter for the field originalTypes. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getOriginalTypes(final Card c) { - final ArrayList returnList = new ArrayList(); - if (this.originalTypes.containsKey(c)) { - returnList.addAll(this.originalTypes.get(c)); - } - return returnList; - } - - /** - *

- * clearOriginalTypes. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - */ - public final void clearOriginalTypes(final Card c) { - if (this.originalTypes.containsKey(c)) { - this.originalTypes.get(c).clear(); - } - } - - /** - *

- * clearAllOriginalTypes. - *

- */ - public final void clearAllOriginalTypes() { - this.originalTypes.clear(); - } - - // statically assigned types - /** - *

- * addType. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param s - * a {@link java.lang.String} object. - */ - public final void addType(final Card c, final String s) { - if (!this.types.containsKey(c)) { - final ArrayList list = new ArrayList(); - list.add(s); - this.types.put(c, list); - } else { - this.types.get(c).add(s); - } - } - - /** - *

- * Getter for the field types. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getTypes(final Card c) { - final ArrayList returnList = new ArrayList(); - if (this.types.containsKey(c)) { - returnList.addAll(this.types.get(c)); - } - return returnList; - } - - /** - *

- * removeType. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param type - * a {@link java.lang.String} object. - */ - public final void removeType(final Card c, final String type) { - if (this.types.containsKey(c)) { - this.types.get(c).remove(type); - } - } - - /** - *

- * clearTypes. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - */ - public final void clearTypes(final Card c) { - if (this.types.containsKey(c)) { - this.types.get(c).clear(); - } - } - - /** - *

- * clearAllTypes. - *

- */ - public final void clearAllTypes() { - this.types.clear(); - } - - /** - *

- * Getter for the field colorDesc. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getColorDesc() { - return this.colorDesc; - } - - /** - *

- * Setter for the field colorDesc. - *

- * - * @param colorDesc - * a {@link java.lang.String} object. - */ - public final void setColorDesc(final String colorDesc) { - this.colorDesc = colorDesc; - } - - // overwrite color - /** - *

- * isOverwriteColors. - *

- * - * @return a boolean. - */ - public final boolean isOverwriteColors() { - return this.overwriteColors; - } - - /** - *

- * Setter for the field overwriteColors. - *

- * - * @param overwriteColors - * a boolean. - */ - public final void setOverwriteColors(final boolean overwriteColors) { - this.overwriteColors = overwriteColors; - } - - /** - *

- * Getter for the field timestamps. - *

- * - * @return a {@link java.util.HashMap} object. - */ - public final HashMap getTimestamps() { - return this.timestamps; - } - - /** - *

- * getTimestamp. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a long. - */ - public final long getTimestamp(final Card c) { - long stamp = -1; - final Long l = this.timestamps.get(c); - if (null != l) { - stamp = l.longValue(); - } - return stamp; - } - - /** - *

- * addTimestamp. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param timestamp - * a long. - */ - public final void addTimestamp(final Card c, final long timestamp) { - this.timestamps.put(c, Long.valueOf(timestamp)); - } - - /** - *

- * clearTimestamps. - *

- */ - public final void clearTimestamps() { - this.timestamps.clear(); - } - - - /** - *

- * Getter for the field source. - *

- * - * @return a {@link forge.game.card.Card} object. - */ - public final Card getSource() { - return this.source; - } - - /** - *

- * Setter for the field keywordNumber. - *

- * - * @param i - * a int. - */ - public final void setKeywordNumber(final int i) { - this.keywordNumber = i; - } - - /** - *

- * Getter for the field keywordNumber. - *

- * - * @return a int. - */ - public final int getKeywordNumber() { - return this.keywordNumber; - } - - /** - *

- * Getter for the field affectedCards. - *

- * - * @return a {@link forge.CardList} object. - */ - public final List getAffectedCards() { - return this.affectedCards; - } - - /** - *

- * Setter for the field affectedCards. - *

- * - * @param list - * a {@link forge.CardList} object. - */ - public final void setAffectedCards(final List list) { - this.affectedCards = list; - } - - /** - * Gets the affected players. - * - * @return the affected players - */ - public final ArrayList getAffectedPlayers() { - return this.affectedPlayers; - } - - /** - * Sets the affected players. - * - * @param list - * the new affected players - */ - public final void setAffectedPlayers(final ArrayList list) { - this.affectedPlayers = list; - } - - /** - *

- * Setter for the field xValue. - *

- * - * @param x - * a int. - */ - public final void setXValue(final int x) { - this.xValue = x; - } - - /** - *

- * Getter for the field xValue. - *

- * - * @return a int. - */ - public final int getXValue() { - return this.xValue; - } - - /** - *

- * Setter for the field yValue. - *

- * - * @param y - * a int. - */ - public final void setYValue(final int y) { - this.yValue = y; - } - - /** - *

- * Getter for the field yValue. - *

- * - * @return a int. - */ - public final int getYValue() { - return this.yValue; - } - - /** - * Store xValue relative to a specific card. - * @param affectedCard the card affected - * @param xValue the xValue - */ - public final void addXMapValue(final Card affectedCard, final Integer xValue) { - if (this.xValueMap.containsKey(affectedCard)) { - if (!this.xValueMap.get(affectedCard).equals(xValue)) { - this.xValueMap.remove(affectedCard); - } - } - this.xValueMap.put(affectedCard, xValue); - } - - /** - * Get the xValue for specific card. - * @param affectedCard the affected card - * @return an int. - */ - public int getXMapValue(Card affectedCard) { - return this.xValueMap.get(affectedCard); - } - - /** - * setParams. TODO Write javadoc for this method. - * - * @param params - * a HashMap - */ - public final void setParams(final HashMap params) { - this.mapParams = params; - } - - /** - * Gets the params. - * - * @return the params - */ - public final HashMap getParams() { - return this.mapParams; - } - - /** - * Sets the chosen type. - * - * @param type - * the new chosen type - */ - public final void setChosenType(final String type) { - this.chosenType = type; - } - - /** - * getChosenType. TODO Write javadoc for this method. - * - * @return the chosen type - */ - public final String getChosenType() { - return this.chosenType; - } - -} // end class StaticEffect +/* + * 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 . + */ +package forge.game; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; + +/** + *

+ * StaticEffect class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class StaticEffect { + private final Card source; + private int keywordNumber = 0; + private List affectedCards = new ArrayList(); + private ArrayList affectedPlayers = new ArrayList(); + private int xValue = 0; + private int yValue = 0; + private long timestamp = -1; + private HashMap xValueMap = new HashMap(); + + private String chosenType; + private HashMap mapParams = new HashMap(); + + // for P/T + private final HashMap originalPT = new HashMap(); + + // for types + private boolean overwriteTypes = false; + private boolean keepSupertype = false; + private boolean removeSubTypes = false; + private final HashMap> types = new HashMap>(); + private final HashMap> originalTypes = new HashMap>(); + + // keywords + private boolean overwriteKeywords = false; + private final HashMap> originalKeywords = new HashMap>(); + + // for abilities + private boolean overwriteAbilities = false; + private final HashMap> originalAbilities = new HashMap>(); + + // for colors + private String colorDesc = ""; + private boolean overwriteColors = false; + private final HashMap timestamps = new HashMap(); + + public StaticEffect(Card source) { + this.source = source; + } + + /** + * setTimestamp TODO Write javadoc for this method. + * + * @param t + * a long + */ + public final void setTimestamp(final long t) { + this.timestamp = t; + } + + /** + * getTimestamp. TODO Write javadoc for this method. + * + * @return a long + */ + public final long getTimestamp() { + return this.timestamp; + } + + // overwrite SAs + /** + *

+ * isOverwriteAbilities. + *

+ * + * @return a boolean. + */ + public final boolean isOverwriteAbilities() { + return this.overwriteAbilities; + } + + /** + *

+ * Setter for the field overwriteAbilities. + *

+ * + * @param overwriteAbilitiesIn + * a boolean. + */ + public final void setOverwriteAbilities(final boolean overwriteAbilitiesIn) { + this.overwriteAbilities = overwriteAbilitiesIn; + } + + // original SAs + /** + *

+ * addOriginalAbilities. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + */ + public final void addOriginalAbilities(final Card c, final SpellAbility sa) { + if (!this.originalAbilities.containsKey(c)) { + final ArrayList list = new ArrayList(); + list.add(sa); + this.originalAbilities.put(c, list); + } else { + this.originalAbilities.get(c).add(sa); + } + } + + /** + *

+ * addOriginalAbilities. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param s + * a {@link java.util.ArrayList} object. + */ + public final void addOriginalAbilities(final Card c, final ArrayList s) { + final ArrayList list = new ArrayList(s); + if (!this.originalAbilities.containsKey(c)) { + this.originalAbilities.put(c, list); + } else { + this.originalAbilities.remove(c); + this.originalAbilities.put(c, list); + } + } + + /** + *

+ * Getter for the field originalAbilities. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getOriginalAbilities(final Card c) { + final ArrayList returnList = new ArrayList(); + if (this.originalAbilities.containsKey(c)) { + returnList.addAll(this.originalAbilities.get(c)); + } + return returnList; + } + + /** + *

+ * clearOriginalAbilities. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + */ + public final void clearOriginalAbilities(final Card c) { + if (this.originalAbilities.containsKey(c)) { + this.originalAbilities.get(c).clear(); + } + } + + /** + *

+ * clearAllOriginalAbilities. + *

+ */ + public final void clearAllOriginalAbilities() { + this.originalAbilities.clear(); + } + + // overwrite keywords + /** + *

+ * isOverwriteKeywords. + *

+ * + * @return a boolean. + */ + public final boolean isOverwriteKeywords() { + return this.overwriteKeywords; + } + + /** + *

+ * Setter for the field overwriteKeywords. + *

+ * + * @param overwriteKeywordsIn + * a boolean. + */ + public final void setOverwriteKeywords(final boolean overwriteKeywordsIn) { + this.overwriteKeywords = overwriteKeywordsIn; + } + + // original keywords + /** + *

+ * addOriginalKeyword. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param s + * a {@link java.lang.String} object. + */ + public final void addOriginalKeyword(final Card c, final String s) { + if (!this.originalKeywords.containsKey(c)) { + final ArrayList list = new ArrayList(); + list.add(s); + this.originalKeywords.put(c, list); + } else { + this.originalKeywords.get(c).add(s); + } + } + + /** + *

+ * addOriginalKeywords. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param s + * a {@link java.util.ArrayList} object. + */ + public final void addOriginalKeywords(final Card c, final ArrayList s) { + final ArrayList list = new ArrayList(s); + if (!this.originalKeywords.containsKey(c)) { + this.originalKeywords.put(c, list); + } else { + this.originalKeywords.remove(c); + this.originalKeywords.put(c, list); + } + } + + /** + *

+ * Getter for the field originalKeywords. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getOriginalKeywords(final Card c) { + final ArrayList returnList = new ArrayList(); + if (this.originalKeywords.containsKey(c)) { + returnList.addAll(this.originalKeywords.get(c)); + } + return returnList; + } + + /** + *

+ * clearOriginalKeywords. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + */ + public final void clearOriginalKeywords(final Card c) { + if (this.originalKeywords.containsKey(c)) { + this.originalKeywords.get(c).clear(); + } + } + + /** + *

+ * clearAllOriginalKeywords. + *

+ */ + public final void clearAllOriginalKeywords() { + this.originalKeywords.clear(); + } + + // original power/toughness + /** + *

+ * addOriginalPT. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param power + * a int. + * @param toughness + * a int. + */ + public final void addOriginalPT(final Card c, final int power, final int toughness) { + final String pt = power + "/" + toughness; + if (!this.originalPT.containsKey(c)) { + this.originalPT.put(c, pt); + } + } + + /** + *

+ * getOriginalPower. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a int. + */ + public final int getOriginalPower(final Card c) { + int power = -1; + if (this.originalPT.containsKey(c)) { + power = Integer.parseInt(this.originalPT.get(c).split("/")[0]); + } + return power; + } + + /** + *

+ * getOriginalToughness. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a int. + */ + public final int getOriginalToughness(final Card c) { + int tough = -1; + if (this.originalPT.containsKey(c)) { + tough = Integer.parseInt(this.originalPT.get(c).split("/")[1]); + } + return tough; + } + + /** + *

+ * clearAllOriginalPTs. + *

+ */ + public final void clearAllOriginalPTs() { + this.originalPT.clear(); + } + + // should we overwrite types? + /** + *

+ * isOverwriteTypes. + *

+ * + * @return a boolean. + */ + public final boolean isOverwriteTypes() { + return this.overwriteTypes; + } + + /** + *

+ * Setter for the field overwriteTypes. + *

+ * + * @param overwriteTypesIn + * a boolean. + */ + public final void setOverwriteTypes(final boolean overwriteTypesIn) { + this.overwriteTypes = overwriteTypesIn; + } + + /** + *

+ * isKeepSupertype. + *

+ * + * @return a boolean. + */ + public final boolean isKeepSupertype() { + return this.keepSupertype; + } + + /** + *

+ * Setter for the field keepSupertype. + *

+ * + * @param keepSupertypeIn + * a boolean. + */ + public final void setKeepSupertype(final boolean keepSupertypeIn) { + this.keepSupertype = keepSupertypeIn; + } + + // should we overwrite land types? + /** + *

+ * isRemoveSubTypes. + *

+ * + * @return a boolean. + */ + public final boolean isRemoveSubTypes() { + return this.removeSubTypes; + } + + /** + *

+ * Setter for the field removeSubTypes. + *

+ * + * @param removeSubTypesIn + * a boolean. + */ + public final void setRemoveSubTypes(final boolean removeSubTypesIn) { + this.removeSubTypes = removeSubTypesIn; + } + + // original types + /** + *

+ * addOriginalType. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param s + * a {@link java.lang.String} object. + */ + public final void addOriginalType(final Card c, final String s) { + if (!this.originalTypes.containsKey(c)) { + final ArrayList list = new ArrayList(); + list.add(s); + this.originalTypes.put(c, list); + } else { + this.originalTypes.get(c).add(s); + } + } + + /** + *

+ * addOriginalTypes. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param s + * a {@link java.util.ArrayList} object. + */ + public final void addOriginalTypes(final Card c, final ArrayList s) { + final ArrayList list = new ArrayList(s); + if (!this.originalTypes.containsKey(c)) { + this.originalTypes.put(c, list); + } else { + this.originalTypes.remove(c); + this.originalTypes.put(c, list); + } + } + + /** + *

+ * Getter for the field originalTypes. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getOriginalTypes(final Card c) { + final ArrayList returnList = new ArrayList(); + if (this.originalTypes.containsKey(c)) { + returnList.addAll(this.originalTypes.get(c)); + } + return returnList; + } + + /** + *

+ * clearOriginalTypes. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + */ + public final void clearOriginalTypes(final Card c) { + if (this.originalTypes.containsKey(c)) { + this.originalTypes.get(c).clear(); + } + } + + /** + *

+ * clearAllOriginalTypes. + *

+ */ + public final void clearAllOriginalTypes() { + this.originalTypes.clear(); + } + + // statically assigned types + /** + *

+ * addType. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param s + * a {@link java.lang.String} object. + */ + public final void addType(final Card c, final String s) { + if (!this.types.containsKey(c)) { + final ArrayList list = new ArrayList(); + list.add(s); + this.types.put(c, list); + } else { + this.types.get(c).add(s); + } + } + + /** + *

+ * Getter for the field types. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getTypes(final Card c) { + final ArrayList returnList = new ArrayList(); + if (this.types.containsKey(c)) { + returnList.addAll(this.types.get(c)); + } + return returnList; + } + + /** + *

+ * removeType. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param type + * a {@link java.lang.String} object. + */ + public final void removeType(final Card c, final String type) { + if (this.types.containsKey(c)) { + this.types.get(c).remove(type); + } + } + + /** + *

+ * clearTypes. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + */ + public final void clearTypes(final Card c) { + if (this.types.containsKey(c)) { + this.types.get(c).clear(); + } + } + + /** + *

+ * clearAllTypes. + *

+ */ + public final void clearAllTypes() { + this.types.clear(); + } + + /** + *

+ * Getter for the field colorDesc. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getColorDesc() { + return this.colorDesc; + } + + /** + *

+ * Setter for the field colorDesc. + *

+ * + * @param colorDesc + * a {@link java.lang.String} object. + */ + public final void setColorDesc(final String colorDesc) { + this.colorDesc = colorDesc; + } + + // overwrite color + /** + *

+ * isOverwriteColors. + *

+ * + * @return a boolean. + */ + public final boolean isOverwriteColors() { + return this.overwriteColors; + } + + /** + *

+ * Setter for the field overwriteColors. + *

+ * + * @param overwriteColors + * a boolean. + */ + public final void setOverwriteColors(final boolean overwriteColors) { + this.overwriteColors = overwriteColors; + } + + /** + *

+ * Getter for the field timestamps. + *

+ * + * @return a {@link java.util.HashMap} object. + */ + public final HashMap getTimestamps() { + return this.timestamps; + } + + /** + *

+ * getTimestamp. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a long. + */ + public final long getTimestamp(final Card c) { + long stamp = -1; + final Long l = this.timestamps.get(c); + if (null != l) { + stamp = l.longValue(); + } + return stamp; + } + + /** + *

+ * addTimestamp. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param timestamp + * a long. + */ + public final void addTimestamp(final Card c, final long timestamp) { + this.timestamps.put(c, Long.valueOf(timestamp)); + } + + /** + *

+ * clearTimestamps. + *

+ */ + public final void clearTimestamps() { + this.timestamps.clear(); + } + + + /** + *

+ * Getter for the field source. + *

+ * + * @return a {@link forge.game.card.Card} object. + */ + public final Card getSource() { + return this.source; + } + + /** + *

+ * Setter for the field keywordNumber. + *

+ * + * @param i + * a int. + */ + public final void setKeywordNumber(final int i) { + this.keywordNumber = i; + } + + /** + *

+ * Getter for the field keywordNumber. + *

+ * + * @return a int. + */ + public final int getKeywordNumber() { + return this.keywordNumber; + } + + /** + *

+ * Getter for the field affectedCards. + *

+ * + * @return a {@link forge.CardList} object. + */ + public final List getAffectedCards() { + return this.affectedCards; + } + + /** + *

+ * Setter for the field affectedCards. + *

+ * + * @param list + * a {@link forge.CardList} object. + */ + public final void setAffectedCards(final List list) { + this.affectedCards = list; + } + + /** + * Gets the affected players. + * + * @return the affected players + */ + public final ArrayList getAffectedPlayers() { + return this.affectedPlayers; + } + + /** + * Sets the affected players. + * + * @param list + * the new affected players + */ + public final void setAffectedPlayers(final ArrayList list) { + this.affectedPlayers = list; + } + + /** + *

+ * Setter for the field xValue. + *

+ * + * @param x + * a int. + */ + public final void setXValue(final int x) { + this.xValue = x; + } + + /** + *

+ * Getter for the field xValue. + *

+ * + * @return a int. + */ + public final int getXValue() { + return this.xValue; + } + + /** + *

+ * Setter for the field yValue. + *

+ * + * @param y + * a int. + */ + public final void setYValue(final int y) { + this.yValue = y; + } + + /** + *

+ * Getter for the field yValue. + *

+ * + * @return a int. + */ + public final int getYValue() { + return this.yValue; + } + + /** + * Store xValue relative to a specific card. + * @param affectedCard the card affected + * @param xValue the xValue + */ + public final void addXMapValue(final Card affectedCard, final Integer xValue) { + if (this.xValueMap.containsKey(affectedCard)) { + if (!this.xValueMap.get(affectedCard).equals(xValue)) { + this.xValueMap.remove(affectedCard); + } + } + this.xValueMap.put(affectedCard, xValue); + } + + /** + * Get the xValue for specific card. + * @param affectedCard the affected card + * @return an int. + */ + public int getXMapValue(Card affectedCard) { + return this.xValueMap.get(affectedCard); + } + + /** + * setParams. TODO Write javadoc for this method. + * + * @param params + * a HashMap + */ + public final void setParams(final HashMap params) { + this.mapParams = params; + } + + /** + * Gets the params. + * + * @return the params + */ + public final HashMap getParams() { + return this.mapParams; + } + + /** + * Sets the chosen type. + * + * @param type + * the new chosen type + */ + public final void setChosenType(final String type) { + this.chosenType = type; + } + + /** + * getChosenType. TODO Write javadoc for this method. + * + * @return the chosen type + */ + public final String getChosenType() { + return this.chosenType; + } + +} // end class StaticEffect diff --git a/forge-game/src/main/java/forge/game/StaticEffects.java b/forge-gui/src/main/java/forge/game/StaticEffects.java similarity index 96% rename from forge-game/src/main/java/forge/game/StaticEffects.java rename to forge-gui/src/main/java/forge/game/StaticEffects.java index c5df430943f..ed333a8ed23 100644 --- a/forge-game/src/main/java/forge/game/StaticEffects.java +++ b/forge-gui/src/main/java/forge/game/StaticEffects.java @@ -1,255 +1,255 @@ -/* - * 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 . - */ -package forge.game; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import forge.game.card.Card; -import forge.game.card.CardUtil; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; -import forge.game.staticability.StaticAbility; - -/** - *

- * StaticEffects class. - *

- * - * @author Forge - * @version $Id: StaticEffects.java 24193 2014-01-09 13:46:14Z swordshine $ - */ -public class StaticEffects { - - // **************** StaticAbility system ************************** - private final ArrayList staticEffects = new ArrayList(); - //Global rule changes - private final EnumSet ruleChanges = EnumSet.noneOf(GlobalRuleChange.class); - - public final Set clearStaticEffects() { - ruleChanges.clear(); - Set clearedCards = new HashSet(); - - // remove all static effects - for (StaticEffect se : staticEffects) { - clearedCards.addAll(this.removeStaticEffect(se)); - } - this.staticEffects.clear(); - - return clearedCards; - } - - public void setGlobalRuleChange(GlobalRuleChange change) { - this.ruleChanges.add(change); - } - - public boolean getGlobalRuleChange(GlobalRuleChange change) { - return this.ruleChanges.contains(change); - } - - /** - * addStaticEffect. TODO Write javadoc for this method. - * - * @param staticEffect - * a StaticEffect - */ - public final void addStaticEffect(final StaticEffect staticEffect) { - this.staticEffects.add(staticEffect); - } - - /** - * removeStaticEffect TODO Write javadoc for this method. - * - * @param se - * a StaticEffect - */ - private final List removeStaticEffect(final StaticEffect se) { - final List affectedCards = se.getAffectedCards(); - final ArrayList affectedPlayers = se.getAffectedPlayers(); - final HashMap params = se.getParams(); - - int powerBonus = 0; - String addP = ""; - int toughnessBonus = 0; - String addT = ""; - int keywordMultiplier = 1; - boolean setPT = false; - String[] addKeywords = null; - String[] addHiddenKeywords = null; - String addColors = null; - - if (params.containsKey("SetPower") || params.containsKey("SetToughness")) { - setPT = true; - } - - if (params.containsKey("AddPower")) { - addP = params.get("AddPower"); - if (addP.matches("[0-9][0-9]?")) { - powerBonus = Integer.valueOf(addP); - } else if (addP.equals("AffectedX")) { - // gets calculated at runtime - } else { - powerBonus = se.getXValue(); - } - } - - if (params.containsKey("AddToughness")) { - addT = params.get("AddToughness"); - if (addT.matches("[0-9][0-9]?")) { - toughnessBonus = Integer.valueOf(addT); - } else if (addT.equals("AffectedX")) { - // gets calculated at runtime - } else { - toughnessBonus = se.getYValue(); - } - } - - if (params.containsKey("KeywordMultiplier")) { - String multiplier = params.get("KeywordMultiplier"); - if (multiplier.equals("X")) { - keywordMultiplier = se.getXValue(); - } else { - keywordMultiplier = Integer.valueOf(multiplier); - } - } - - if (params.containsKey("AddHiddenKeyword")) { - addHiddenKeywords = params.get("AddHiddenKeyword").split(" & "); - } - - if (params.containsKey("AddColor")) { - final String colors = params.get("AddColor"); - if (colors.equals("ChosenColor")) { - addColors = CardUtil.getShortColorsString(se.getSource().getChosenColor()); - } else { - addColors = CardUtil.getShortColorsString(new ArrayList(Arrays.asList(colors.split(" & ")))); - } - } - - if (params.containsKey("SetColor")) { - final String colors = params.get("SetColor"); - if (colors.equals("ChosenColor")) { - addColors = CardUtil.getShortColorsString(se.getSource().getChosenColor()); - } else { - addColors = CardUtil.getShortColorsString(new ArrayList(Arrays.asList(colors.split(" & ")))); - } - } - - // modify players - for (final Player p : affectedPlayers) { - p.setUnlimitedHandSize(false); - p.setMaxHandSize(p.getStartingHandSize()); - - if (params.containsKey("AddKeyword")) { - addKeywords = params.get("AddKeyword").split(" & "); - } - - // add keywords - if (addKeywords != null) { - for (final String keyword : addKeywords) { - for (int i = 0; i < keywordMultiplier; i++) { - p.removeKeyword(keyword); - } - } - } - } - - // modify the affected card - for (final Card affectedCard : affectedCards) { - // Gain control - if (params.containsKey("GainControl")) { - affectedCard.removeTempController(se.getTimestamp()); - } - - // remove set P/T - if (!params.containsKey("CharacteristicDefining") && setPT) { - affectedCard.removeNewPT(se.getTimestamp()); - } - - // remove P/T bonus - if (addP.startsWith("AffectedX")) { - powerBonus = se.getXMapValue(affectedCard); - } - if (addT.startsWith("AffectedX")) { - toughnessBonus = se.getXMapValue(affectedCard); - } - affectedCard.addSemiPermanentAttackBoost(powerBonus * -1); - affectedCard.addSemiPermanentDefenseBoost(toughnessBonus * -1); - - // remove keywords - // TODO regular keywords currently don't try to use keyword multiplier - // (Although nothing uses it at this time) - if (params.containsKey("AddKeyword") || params.containsKey("RemoveKeyword") - || params.containsKey("RemoveAllAbilities")) { - affectedCard.removeChangedCardKeywords(se.getTimestamp()); - } - - // remove abilities - if (params.containsKey("AddAbility") || params.containsKey("GainsAbilitiesOf")) { - for (final SpellAbility s : affectedCard.getSpellAbilities()) { - if (s.isTemporary()) { - affectedCard.removeSpellAbility(s); - } - } - } - - if (addHiddenKeywords != null) { - for (final String k : addHiddenKeywords) { - for (int j = 0; j < keywordMultiplier; j++) { - affectedCard.removeHiddenExtrinsicKeyword(k); - } - } - } - - // remove abilities - if (params.containsKey("RemoveAllAbilities")) { - for (final SpellAbility ab : affectedCard.getSpellAbilities()) { - ab.setTemporarilySuppressed(false); - } - for (final StaticAbility stA : affectedCard.getStaticAbilities()) { - stA.setTemporarilySuppressed(false); - } - for (final TriggerReplacementBase rE : affectedCard.getReplacementEffects()) { - rE.setTemporarilySuppressed(false); - } - } - - // remove Types - if (params.containsKey("AddType") || params.containsKey("RemoveType")) { - affectedCard.removeChangedCardTypes(se.getTimestamp()); - } - - // remove colors - if (addColors != null) { - affectedCard.removeColor(addColors, affectedCard, !se.isOverwriteColors(), - se.getTimestamp(affectedCard)); - } - } - se.clearTimestamps(); - return affectedCards; - } - - // **************** End StaticAbility system ************************** - - -} // end class StaticEffects +/* + * 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 . + */ +package forge.game; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import forge.game.card.Card; +import forge.game.card.CardUtil; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.staticability.StaticAbility; + +/** + *

+ * StaticEffects class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class StaticEffects { + + // **************** StaticAbility system ************************** + private final ArrayList staticEffects = new ArrayList(); + //Global rule changes + private final EnumSet ruleChanges = EnumSet.noneOf(GlobalRuleChange.class); + + public final Set clearStaticEffects() { + ruleChanges.clear(); + Set clearedCards = new HashSet(); + + // remove all static effects + for (StaticEffect se : staticEffects) { + clearedCards.addAll(this.removeStaticEffect(se)); + } + this.staticEffects.clear(); + + return clearedCards; + } + + public void setGlobalRuleChange(GlobalRuleChange change) { + this.ruleChanges.add(change); + } + + public boolean getGlobalRuleChange(GlobalRuleChange change) { + return this.ruleChanges.contains(change); + } + + /** + * addStaticEffect. TODO Write javadoc for this method. + * + * @param staticEffect + * a StaticEffect + */ + public final void addStaticEffect(final StaticEffect staticEffect) { + this.staticEffects.add(staticEffect); + } + + /** + * removeStaticEffect TODO Write javadoc for this method. + * + * @param se + * a StaticEffect + */ + private final List removeStaticEffect(final StaticEffect se) { + final List affectedCards = se.getAffectedCards(); + final ArrayList affectedPlayers = se.getAffectedPlayers(); + final HashMap params = se.getParams(); + + int powerBonus = 0; + String addP = ""; + int toughnessBonus = 0; + String addT = ""; + int keywordMultiplier = 1; + boolean setPT = false; + String[] addKeywords = null; + String[] addHiddenKeywords = null; + String addColors = null; + + if (params.containsKey("SetPower") || params.containsKey("SetToughness")) { + setPT = true; + } + + if (params.containsKey("AddPower")) { + addP = params.get("AddPower"); + if (addP.matches("[0-9][0-9]?")) { + powerBonus = Integer.valueOf(addP); + } else if (addP.equals("AffectedX")) { + // gets calculated at runtime + } else { + powerBonus = se.getXValue(); + } + } + + if (params.containsKey("AddToughness")) { + addT = params.get("AddToughness"); + if (addT.matches("[0-9][0-9]?")) { + toughnessBonus = Integer.valueOf(addT); + } else if (addT.equals("AffectedX")) { + // gets calculated at runtime + } else { + toughnessBonus = se.getYValue(); + } + } + + if (params.containsKey("KeywordMultiplier")) { + String multiplier = params.get("KeywordMultiplier"); + if (multiplier.equals("X")) { + keywordMultiplier = se.getXValue(); + } else { + keywordMultiplier = Integer.valueOf(multiplier); + } + } + + if (params.containsKey("AddHiddenKeyword")) { + addHiddenKeywords = params.get("AddHiddenKeyword").split(" & "); + } + + if (params.containsKey("AddColor")) { + final String colors = params.get("AddColor"); + if (colors.equals("ChosenColor")) { + addColors = CardUtil.getShortColorsString(se.getSource().getChosenColor()); + } else { + addColors = CardUtil.getShortColorsString(new ArrayList(Arrays.asList(colors.split(" & ")))); + } + } + + if (params.containsKey("SetColor")) { + final String colors = params.get("SetColor"); + if (colors.equals("ChosenColor")) { + addColors = CardUtil.getShortColorsString(se.getSource().getChosenColor()); + } else { + addColors = CardUtil.getShortColorsString(new ArrayList(Arrays.asList(colors.split(" & ")))); + } + } + + // modify players + for (final Player p : affectedPlayers) { + p.setUnlimitedHandSize(false); + p.setMaxHandSize(p.getStartingHandSize()); + + if (params.containsKey("AddKeyword")) { + addKeywords = params.get("AddKeyword").split(" & "); + } + + // add keywords + if (addKeywords != null) { + for (final String keyword : addKeywords) { + for (int i = 0; i < keywordMultiplier; i++) { + p.removeKeyword(keyword); + } + } + } + } + + // modify the affected card + for (final Card affectedCard : affectedCards) { + // Gain control + if (params.containsKey("GainControl")) { + affectedCard.removeTempController(se.getTimestamp()); + } + + // remove set P/T + if (!params.containsKey("CharacteristicDefining") && setPT) { + affectedCard.removeNewPT(se.getTimestamp()); + } + + // remove P/T bonus + if (addP.startsWith("AffectedX")) { + powerBonus = se.getXMapValue(affectedCard); + } + if (addT.startsWith("AffectedX")) { + toughnessBonus = se.getXMapValue(affectedCard); + } + affectedCard.addSemiPermanentAttackBoost(powerBonus * -1); + affectedCard.addSemiPermanentDefenseBoost(toughnessBonus * -1); + + // remove keywords + // TODO regular keywords currently don't try to use keyword multiplier + // (Although nothing uses it at this time) + if (params.containsKey("AddKeyword") || params.containsKey("RemoveKeyword") + || params.containsKey("RemoveAllAbilities")) { + affectedCard.removeChangedCardKeywords(se.getTimestamp()); + } + + // remove abilities + if (params.containsKey("AddAbility") || params.containsKey("GainsAbilitiesOf")) { + for (final SpellAbility s : affectedCard.getSpellAbilities()) { + if (s.isTemporary()) { + affectedCard.removeSpellAbility(s); + } + } + } + + if (addHiddenKeywords != null) { + for (final String k : addHiddenKeywords) { + for (int j = 0; j < keywordMultiplier; j++) { + affectedCard.removeHiddenExtrinsicKeyword(k); + } + } + } + + // remove abilities + if (params.containsKey("RemoveAllAbilities")) { + for (final SpellAbility ab : affectedCard.getSpellAbilities()) { + ab.setTemporarilySuppressed(false); + } + for (final StaticAbility stA : affectedCard.getStaticAbilities()) { + stA.setTemporarilySuppressed(false); + } + for (final TriggerReplacementBase rE : affectedCard.getReplacementEffects()) { + rE.setTemporarilySuppressed(false); + } + } + + // remove Types + if (params.containsKey("AddType") || params.containsKey("RemoveType")) { + affectedCard.removeChangedCardTypes(se.getTimestamp()); + } + + // remove colors + if (addColors != null) { + affectedCard.removeColor(addColors, affectedCard, !se.isOverwriteColors(), + se.getTimestamp(affectedCard)); + } + } + se.clearTimestamps(); + return affectedCards; + } + + // **************** End StaticAbility system ************************** + + +} // end class StaticEffects diff --git a/forge-game/src/main/java/forge/game/TriggerReplacementBase.java b/forge-gui/src/main/java/forge/game/TriggerReplacementBase.java similarity index 100% rename from forge-game/src/main/java/forge/game/TriggerReplacementBase.java rename to forge-gui/src/main/java/forge/game/TriggerReplacementBase.java diff --git a/forge-game/src/main/java/forge/game/ability/AbilityApiBased.java b/forge-gui/src/main/java/forge/game/ability/AbilityApiBased.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/AbilityApiBased.java rename to forge-gui/src/main/java/forge/game/ability/AbilityApiBased.java diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-gui/src/main/java/forge/game/ability/AbilityFactory.java similarity index 97% rename from forge-game/src/main/java/forge/game/ability/AbilityFactory.java rename to forge-gui/src/main/java/forge/game/ability/AbilityFactory.java index 98b696c2f01..85bf819b0fd 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-gui/src/main/java/forge/game/ability/AbilityFactory.java @@ -1,396 +1,396 @@ -/* - * 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 . - */ -package forge.game.ability; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import forge.card.CardCharacteristicName; -import forge.game.card.Card; -import forge.game.cost.Cost; -import forge.game.spellability.AbilitySub; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.SpellAbilityCondition; -import forge.game.spellability.SpellAbilityRestriction; -import forge.game.spellability.TargetRestrictions; -import forge.game.zone.ZoneType; -import forge.util.FileSection; - -/** - *

- * AbilityFactory class. - *

- * - * @author Forge - * @version $Id: AbilityFactory.java 24043 2013-12-24 02:06:22Z swordshine $ - */ -public final class AbilityFactory { - - public enum AbilityRecordType { - Ability("AB"), - Spell("SP"), - StaticAbility("ST"), - SubAbility("DB"); - - private final String prefix; - private AbilityRecordType(String prefix) { - this.prefix = prefix; - } - public String getPrefix() { - return prefix; - } - - public SpellAbility buildSpellAbility(ApiType api, Card hostCard, Cost abCost, TargetRestrictions abTgt, Map mapParams ) { - switch(this) { - case Ability: return new AbilityApiBased(api, hostCard, abCost, abTgt, mapParams); - case Spell: return new SpellApiBased(api, hostCard, abCost, abTgt, mapParams); - case StaticAbility: return new StaticAbilityApiBased(api, hostCard, abCost, abTgt, mapParams); - case SubAbility: return new AbilitySub(api, hostCard, abTgt, mapParams); - } - return null; // exception here would be fine! - } - - public ApiType getApiTypeOf(Map abParams) { - return ApiType.smartValueOf(abParams.get(this.getPrefix())); - } - - public static AbilityRecordType getRecordType(Map abParams) { - if (abParams.containsKey(AbilityRecordType.Ability.getPrefix())) { - return AbilityRecordType.Ability; - } else if (abParams.containsKey(AbilityRecordType.Spell.getPrefix())) { - return AbilityRecordType.Spell; - } else if (abParams.containsKey(AbilityRecordType.StaticAbility.getPrefix())) { - return AbilityRecordType.StaticAbility; - } else if (abParams.containsKey(AbilityRecordType.SubAbility.getPrefix())) { - return AbilityRecordType.SubAbility; - } else { - return null; - } - } - } - /** - *

- * getAbility. - *

- * - * @param abString - * a {@link java.lang.String} object. - * @param hostCard - * a {@link forge.game.card.Card} object. - * @return a {@link forge.game.spellability.SpellAbility} object. - */ - public static final SpellAbility getAbility(final String abString, final Card hostCard) { - - - Map mapParams; - try { - mapParams = AbilityFactory.getMapParams(abString); - } - catch (RuntimeException ex) { - throw new RuntimeException(hostCard.getName() + ": " + ex.getMessage()); - } - - // parse universal parameters - AbilityRecordType type = AbilityRecordType.getRecordType(mapParams); - if( null == type ) - throw new RuntimeException("AbilityFactory : getAbility -- no API in " + hostCard.getName()); - - return getAbility(type, type.getApiTypeOf(mapParams), mapParams, parseAbilityCost(hostCard, mapParams, type), hostCard); - } - - public static Cost parseAbilityCost(final Card hostCard, Map mapParams, AbilityRecordType type) { - Cost abCost = null; - if (type != AbilityRecordType.SubAbility) { - if (!mapParams.containsKey("Cost")) { - throw new RuntimeException("AbilityFactory : getAbility -- no Cost in " + hostCard.getName()); - } - abCost = new Cost(mapParams.get("Cost"), type == AbilityRecordType.Ability); - } - return abCost; - } - - public static final SpellAbility getAbility(AbilityRecordType type, ApiType api, Map mapParams, Cost abCost, Card hostCard) { - TargetRestrictions abTgt = mapParams.containsKey("ValidTgts") ? readTarget(mapParams) : null; - - if (api == ApiType.CopySpellAbility || api == ApiType.Counter || api == ApiType.ChangeTargets) { - // Since all "CopySpell" ABs copy things on the Stack no need for it to be everywhere - // Since all "Counter" or "ChangeTargets" abilities only target the Stack Zone - // No need to have each of those scripts have that info - if (abTgt != null) { - abTgt.setZone(ZoneType.Stack); - } - } - - else if (api == ApiType.PermanentCreature || api == ApiType.PermanentNoncreature) { - // If API is a permanent type, and creating AF Spell - // Clear out the auto created SpellPemanent spell - if (type == AbilityRecordType.Spell) { - hostCard.clearFirstSpell(); - } - } - - - SpellAbility spellAbility = type.buildSpellAbility(api, hostCard, abCost, abTgt, mapParams); - - - if (spellAbility == null) { - final StringBuilder msg = new StringBuilder(); - msg.append("AbilityFactory : SpellAbility was not created for "); - msg.append(hostCard.getName()); - msg.append(". Looking for API: ").append(api); - throw new RuntimeException(msg.toString()); - } - - // ********************************************* - // set universal properties of the SpellAbility - - if (mapParams.containsKey("References")) { - for (String svar : mapParams.get("References").split(",")) { - spellAbility.setSVar(svar, hostCard.getSVar(svar)); - } - } - - if (mapParams.containsKey("PreventionSubAbility")) { - spellAbility.setSVar(mapParams.get("PreventionSubAbility"), hostCard.getSVar(mapParams.get("PreventionSubAbility"))); - } - - if (mapParams.containsKey("SubAbility")) { - spellAbility.setSubAbility(getSubAbility(hostCard, hostCard.getSVar(mapParams.get("SubAbility")))); - } - - if (spellAbility instanceof SpellApiBased && hostCard.isPermanent()) { - spellAbility.setDescription(spellAbility.getSourceCard().getName()); - } else if (mapParams.containsKey("SpellDescription")) { - final StringBuilder sb = new StringBuilder(); - - if (type != AbilityRecordType.SubAbility) { // SubAbilities don't have Costs or Cost - // descriptors - if (mapParams.containsKey("PrecostDesc")) { - sb.append(mapParams.get("PrecostDesc")).append(" "); - } - if (mapParams.containsKey("CostDesc")) { - sb.append(mapParams.get("CostDesc")).append(" "); - } else { - sb.append(abCost.toString()); - } - } - - sb.append(mapParams.get("SpellDescription")); - - spellAbility.setDescription(sb.toString()); - } else { - spellAbility.setDescription(""); - } - - if (mapParams.containsKey("NonBasicSpell")) { - spellAbility.setBasicSpell(false); - } - - makeRestrictions(spellAbility, mapParams); - makeConditions(spellAbility, mapParams); - - return spellAbility; - } - - private static final TargetRestrictions readTarget(Map mapParams) { - final String min = mapParams.containsKey("TargetMin") ? mapParams.get("TargetMin") : "1"; - final String max = mapParams.containsKey("TargetMax") ? mapParams.get("TargetMax") : "1"; - - - // TgtPrompt now optional - final String prompt = mapParams.containsKey("TgtPrompt") ? mapParams.get("TgtPrompt") : "Select target " + mapParams.get("ValidTgts"); - - TargetRestrictions abTgt = new TargetRestrictions(prompt, mapParams.get("ValidTgts").split(","), min, max); - - if (mapParams.containsKey("TgtZone")) { // if Targeting - // something - // not in play, this Key - // should be set - abTgt.setZone(ZoneType.listValueOf(mapParams.get("TgtZone"))); - } - - // TargetValidTargeting most for Counter: e.g. target spell that - // targets X. - if (mapParams.containsKey("TargetValidTargeting")) { - abTgt.setSAValidTargeting(mapParams.get("TargetValidTargeting")); - } - - if (mapParams.containsKey("TargetsSingleTarget")) { - abTgt.setSingleTarget(true); - } - if (mapParams.containsKey("TargetUnique")) { - abTgt.setUniqueTargets(true); - } - if (mapParams.containsKey("TargetsFromSingleZone")) { - abTgt.setSingleZone(true); - } - if (mapParams.containsKey("TargetsFromDifferentZone")) { - abTgt.setDifferentZone(true); - } - if (mapParams.containsKey("TargetsWithoutSameCreatureType")) { - abTgt.setWithoutSameCreatureType(true); - } - if (mapParams.containsKey("TargetsWithSameController")) { - abTgt.setSameController(true); - } - if (mapParams.containsKey("TargetsWithDifferentControllers")) { - abTgt.setDifferentControllers(true); - } - if (mapParams.containsKey("DividedAsYouChoose")) { - abTgt.calculateStillToDivide(mapParams.get("DividedAsYouChoose"), null, null); - abTgt.setDividedAsYouChoose(true); - } - if (mapParams.containsKey("TargetsAtRandom")) { - abTgt.setRandomTarget(true); - } - if (mapParams.containsKey("TargetsWithRelatedProperty")) { - abTgt.setRelatedProperty(mapParams.get("TargetsWithRelatedProperty")); - } - return abTgt; - } - - /** - *

- * makeRestrictions. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @param mapParams - */ - private static final void makeRestrictions(final SpellAbility sa, Map mapParams) { - // SpellAbilityRestrictions should be added in here - final SpellAbilityRestriction restrict = sa.getRestrictions(); - if (mapParams.containsKey("Flashback")) { - sa.setFlashBackAbility(true); - } - restrict.setRestrictions(mapParams); - } - - /** - *

- * makeConditions. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @param mapParams - */ - private static final void makeConditions(final SpellAbility sa, Map mapParams) { - // SpellAbilityRestrictions should be added in here - final SpellAbilityCondition condition = sa.getConditions(); - if (mapParams.containsKey("Flashback")) { - sa.setFlashBackAbility(true); - } - condition.setConditions(mapParams); - } - - // Easy creation of SubAbilities - /** - *

- * getSubAbility. - *

- * @param sSub - * - * @return a {@link forge.game.spellability.AbilitySub} object. - */ - private static final AbilitySub getSubAbility(Card hostCard, String sSub) { - - if (!sSub.equals("")) { - return (AbilitySub) AbilityFactory.getAbility(sSub, hostCard); - } - System.out.println("SubAbility not found for: " + hostCard); - - return null; - } - - public static final Map getMapParams(final String abString) { - return FileSection.parseToMap(abString, "$", "|"); - } - - public static final void adjustChangeZoneTarget(final Map params, final SpellAbility sa) { - List origin = new ArrayList(); - if (params.containsKey("Origin")) { - origin = ZoneType.listValueOf(params.get("Origin")); - } - - final TargetRestrictions tgt = sa.getTargetRestrictions(); - - // Don't set the zone if it targets a player - if ((tgt != null) && !tgt.canTgtPlayer()) { - sa.getTargetRestrictions().setZone(origin); - } - } - - public static final SpellAbility buildFusedAbility(final Card card) { - if(!card.isSplitCard()) - throw new IllegalStateException("Fuse ability may be built only on split cards"); - - final String strLeftAbility = card.getState(CardCharacteristicName.LeftSplit).getUnparsedAbilities().get(0); - Map leftMap = getMapParams(strLeftAbility); - AbilityRecordType leftType = AbilityRecordType.getRecordType(leftMap); - ApiType leftApi = leftType.getApiTypeOf(leftMap); - leftMap.put("StackDecription", leftMap.get("SpellDescription")); - leftMap.put("SpellDescription", "Fuse (you may cast both halves of this card from your hand)."); - leftMap.put("ActivationZone", "Hand"); - - final String strRightAbility = card.getState(CardCharacteristicName.RightSplit).getUnparsedAbilities().get(0); - Map rightMap = getMapParams(strRightAbility); - AbilityRecordType rightType = AbilityRecordType.getRecordType(leftMap); - ApiType rightApi = leftType.getApiTypeOf(rightMap); - rightMap.put("StackDecription", rightMap.get("SpellDescription")); - rightMap.put("SpellDescription", ""); - - Cost totalCost = parseAbilityCost(card, leftMap, leftType); - totalCost.add(parseAbilityCost(card, rightMap, rightType)); - - final SpellAbility left = getAbility(leftType, leftApi, leftMap, totalCost, card); - final AbilitySub right = (AbilitySub) getAbility(AbilityRecordType.SubAbility, rightApi, rightMap, null, card); - left.appendSubAbility(right); - return left; - } - - public static final SpellAbility buildEntwineAbility(final SpellAbility sa) { - final Card source = sa.getSourceCard(); - final String[] saChoices = sa.getParam("Choices").split(","); - if (sa.getApi() != ApiType.Charm || saChoices.length != 2) - throw new IllegalStateException("Entwine ability may be built only on charm cards"); - final String ab = source.getSVar(saChoices[0]); - Map firstMap = getMapParams(ab); - AbilityRecordType firstType = AbilityRecordType.getRecordType(firstMap); - ApiType firstApi = firstType.getApiTypeOf(firstMap); - firstMap.put("StackDecription", firstMap.get("SpellDescription")); - firstMap.put("SpellDescription", sa.getDescription() + " Entwine (Choose both if you pay the entwine cost.)"); - SpellAbility entwineSA = getAbility(AbilityRecordType.Spell, firstApi, firstMap, new Cost(sa.getPayCosts().toSimpleString(), false), source); - - final String ab2 = source.getSVar(saChoices[1]); - Map secondMap = getMapParams(ab2); - ApiType secondApi = firstType.getApiTypeOf(secondMap); - secondMap.put("StackDecription", secondMap.get("SpellDescription")); - secondMap.put("SpellDescription", ""); - AbilitySub sub = (AbilitySub) getAbility(AbilityRecordType.SubAbility, secondApi, secondMap, null, source); - entwineSA.appendSubAbility(sub); - - entwineSA.setBasicSpell(false); - entwineSA.setActivatingPlayer(sa.getActivatingPlayer()); - entwineSA.setRestrictions(sa.getRestrictions()); - return entwineSA; - } - -} // end class AbilityFactory +/* + * 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 . + */ +package forge.game.ability; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import forge.card.CardCharacteristicName; +import forge.game.card.Card; +import forge.game.cost.Cost; +import forge.game.spellability.AbilitySub; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityCondition; +import forge.game.spellability.SpellAbilityRestriction; +import forge.game.spellability.TargetRestrictions; +import forge.game.zone.ZoneType; +import forge.util.FileSection; + +/** + *

+ * AbilityFactory class. + *

+ * + * @author Forge + * @version $Id$ + */ +public final class AbilityFactory { + + public enum AbilityRecordType { + Ability("AB"), + Spell("SP"), + StaticAbility("ST"), + SubAbility("DB"); + + private final String prefix; + private AbilityRecordType(String prefix) { + this.prefix = prefix; + } + public String getPrefix() { + return prefix; + } + + public SpellAbility buildSpellAbility(ApiType api, Card hostCard, Cost abCost, TargetRestrictions abTgt, Map mapParams ) { + switch(this) { + case Ability: return new AbilityApiBased(api, hostCard, abCost, abTgt, mapParams); + case Spell: return new SpellApiBased(api, hostCard, abCost, abTgt, mapParams); + case StaticAbility: return new StaticAbilityApiBased(api, hostCard, abCost, abTgt, mapParams); + case SubAbility: return new AbilitySub(api, hostCard, abTgt, mapParams); + } + return null; // exception here would be fine! + } + + public ApiType getApiTypeOf(Map abParams) { + return ApiType.smartValueOf(abParams.get(this.getPrefix())); + } + + public static AbilityRecordType getRecordType(Map abParams) { + if (abParams.containsKey(AbilityRecordType.Ability.getPrefix())) { + return AbilityRecordType.Ability; + } else if (abParams.containsKey(AbilityRecordType.Spell.getPrefix())) { + return AbilityRecordType.Spell; + } else if (abParams.containsKey(AbilityRecordType.StaticAbility.getPrefix())) { + return AbilityRecordType.StaticAbility; + } else if (abParams.containsKey(AbilityRecordType.SubAbility.getPrefix())) { + return AbilityRecordType.SubAbility; + } else { + return null; + } + } + } + /** + *

+ * getAbility. + *

+ * + * @param abString + * a {@link java.lang.String} object. + * @param hostCard + * a {@link forge.game.card.Card} object. + * @return a {@link forge.game.spellability.SpellAbility} object. + */ + public static final SpellAbility getAbility(final String abString, final Card hostCard) { + + + Map mapParams; + try { + mapParams = AbilityFactory.getMapParams(abString); + } + catch (RuntimeException ex) { + throw new RuntimeException(hostCard.getName() + ": " + ex.getMessage()); + } + + // parse universal parameters + AbilityRecordType type = AbilityRecordType.getRecordType(mapParams); + if( null == type ) + throw new RuntimeException("AbilityFactory : getAbility -- no API in " + hostCard.getName()); + + return getAbility(type, type.getApiTypeOf(mapParams), mapParams, parseAbilityCost(hostCard, mapParams, type), hostCard); + } + + public static Cost parseAbilityCost(final Card hostCard, Map mapParams, AbilityRecordType type) { + Cost abCost = null; + if (type != AbilityRecordType.SubAbility) { + if (!mapParams.containsKey("Cost")) { + throw new RuntimeException("AbilityFactory : getAbility -- no Cost in " + hostCard.getName()); + } + abCost = new Cost(mapParams.get("Cost"), type == AbilityRecordType.Ability); + } + return abCost; + } + + public static final SpellAbility getAbility(AbilityRecordType type, ApiType api, Map mapParams, Cost abCost, Card hostCard) { + TargetRestrictions abTgt = mapParams.containsKey("ValidTgts") ? readTarget(mapParams) : null; + + if (api == ApiType.CopySpellAbility || api == ApiType.Counter || api == ApiType.ChangeTargets) { + // Since all "CopySpell" ABs copy things on the Stack no need for it to be everywhere + // Since all "Counter" or "ChangeTargets" abilities only target the Stack Zone + // No need to have each of those scripts have that info + if (abTgt != null) { + abTgt.setZone(ZoneType.Stack); + } + } + + else if (api == ApiType.PermanentCreature || api == ApiType.PermanentNoncreature) { + // If API is a permanent type, and creating AF Spell + // Clear out the auto created SpellPemanent spell + if (type == AbilityRecordType.Spell) { + hostCard.clearFirstSpell(); + } + } + + + SpellAbility spellAbility = type.buildSpellAbility(api, hostCard, abCost, abTgt, mapParams); + + + if (spellAbility == null) { + final StringBuilder msg = new StringBuilder(); + msg.append("AbilityFactory : SpellAbility was not created for "); + msg.append(hostCard.getName()); + msg.append(". Looking for API: ").append(api); + throw new RuntimeException(msg.toString()); + } + + // ********************************************* + // set universal properties of the SpellAbility + + if (mapParams.containsKey("References")) { + for (String svar : mapParams.get("References").split(",")) { + spellAbility.setSVar(svar, hostCard.getSVar(svar)); + } + } + + if (mapParams.containsKey("PreventionSubAbility")) { + spellAbility.setSVar(mapParams.get("PreventionSubAbility"), hostCard.getSVar(mapParams.get("PreventionSubAbility"))); + } + + if (mapParams.containsKey("SubAbility")) { + spellAbility.setSubAbility(getSubAbility(hostCard, hostCard.getSVar(mapParams.get("SubAbility")))); + } + + if (spellAbility instanceof SpellApiBased && hostCard.isPermanent()) { + spellAbility.setDescription(spellAbility.getSourceCard().getName()); + } else if (mapParams.containsKey("SpellDescription")) { + final StringBuilder sb = new StringBuilder(); + + if (type != AbilityRecordType.SubAbility) { // SubAbilities don't have Costs or Cost + // descriptors + if (mapParams.containsKey("PrecostDesc")) { + sb.append(mapParams.get("PrecostDesc")).append(" "); + } + if (mapParams.containsKey("CostDesc")) { + sb.append(mapParams.get("CostDesc")).append(" "); + } else { + sb.append(abCost.toString()); + } + } + + sb.append(mapParams.get("SpellDescription")); + + spellAbility.setDescription(sb.toString()); + } else { + spellAbility.setDescription(""); + } + + if (mapParams.containsKey("NonBasicSpell")) { + spellAbility.setBasicSpell(false); + } + + makeRestrictions(spellAbility, mapParams); + makeConditions(spellAbility, mapParams); + + return spellAbility; + } + + private static final TargetRestrictions readTarget(Map mapParams) { + final String min = mapParams.containsKey("TargetMin") ? mapParams.get("TargetMin") : "1"; + final String max = mapParams.containsKey("TargetMax") ? mapParams.get("TargetMax") : "1"; + + + // TgtPrompt now optional + final String prompt = mapParams.containsKey("TgtPrompt") ? mapParams.get("TgtPrompt") : "Select target " + mapParams.get("ValidTgts"); + + TargetRestrictions abTgt = new TargetRestrictions(prompt, mapParams.get("ValidTgts").split(","), min, max); + + if (mapParams.containsKey("TgtZone")) { // if Targeting + // something + // not in play, this Key + // should be set + abTgt.setZone(ZoneType.listValueOf(mapParams.get("TgtZone"))); + } + + // TargetValidTargeting most for Counter: e.g. target spell that + // targets X. + if (mapParams.containsKey("TargetValidTargeting")) { + abTgt.setSAValidTargeting(mapParams.get("TargetValidTargeting")); + } + + if (mapParams.containsKey("TargetsSingleTarget")) { + abTgt.setSingleTarget(true); + } + if (mapParams.containsKey("TargetUnique")) { + abTgt.setUniqueTargets(true); + } + if (mapParams.containsKey("TargetsFromSingleZone")) { + abTgt.setSingleZone(true); + } + if (mapParams.containsKey("TargetsFromDifferentZone")) { + abTgt.setDifferentZone(true); + } + if (mapParams.containsKey("TargetsWithoutSameCreatureType")) { + abTgt.setWithoutSameCreatureType(true); + } + if (mapParams.containsKey("TargetsWithSameController")) { + abTgt.setSameController(true); + } + if (mapParams.containsKey("TargetsWithDifferentControllers")) { + abTgt.setDifferentControllers(true); + } + if (mapParams.containsKey("DividedAsYouChoose")) { + abTgt.calculateStillToDivide(mapParams.get("DividedAsYouChoose"), null, null); + abTgt.setDividedAsYouChoose(true); + } + if (mapParams.containsKey("TargetsAtRandom")) { + abTgt.setRandomTarget(true); + } + if (mapParams.containsKey("TargetsWithRelatedProperty")) { + abTgt.setRelatedProperty(mapParams.get("TargetsWithRelatedProperty")); + } + return abTgt; + } + + /** + *

+ * makeRestrictions. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @param mapParams + */ + private static final void makeRestrictions(final SpellAbility sa, Map mapParams) { + // SpellAbilityRestrictions should be added in here + final SpellAbilityRestriction restrict = sa.getRestrictions(); + if (mapParams.containsKey("Flashback")) { + sa.setFlashBackAbility(true); + } + restrict.setRestrictions(mapParams); + } + + /** + *

+ * makeConditions. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @param mapParams + */ + private static final void makeConditions(final SpellAbility sa, Map mapParams) { + // SpellAbilityRestrictions should be added in here + final SpellAbilityCondition condition = sa.getConditions(); + if (mapParams.containsKey("Flashback")) { + sa.setFlashBackAbility(true); + } + condition.setConditions(mapParams); + } + + // Easy creation of SubAbilities + /** + *

+ * getSubAbility. + *

+ * @param sSub + * + * @return a {@link forge.game.spellability.AbilitySub} object. + */ + private static final AbilitySub getSubAbility(Card hostCard, String sSub) { + + if (!sSub.equals("")) { + return (AbilitySub) AbilityFactory.getAbility(sSub, hostCard); + } + System.out.println("SubAbility not found for: " + hostCard); + + return null; + } + + public static final Map getMapParams(final String abString) { + return FileSection.parseToMap(abString, "$", "|"); + } + + public static final void adjustChangeZoneTarget(final Map params, final SpellAbility sa) { + List origin = new ArrayList(); + if (params.containsKey("Origin")) { + origin = ZoneType.listValueOf(params.get("Origin")); + } + + final TargetRestrictions tgt = sa.getTargetRestrictions(); + + // Don't set the zone if it targets a player + if ((tgt != null) && !tgt.canTgtPlayer()) { + sa.getTargetRestrictions().setZone(origin); + } + } + + public static final SpellAbility buildFusedAbility(final Card card) { + if(!card.isSplitCard()) + throw new IllegalStateException("Fuse ability may be built only on split cards"); + + final String strLeftAbility = card.getState(CardCharacteristicName.LeftSplit).getUnparsedAbilities().get(0); + Map leftMap = getMapParams(strLeftAbility); + AbilityRecordType leftType = AbilityRecordType.getRecordType(leftMap); + ApiType leftApi = leftType.getApiTypeOf(leftMap); + leftMap.put("StackDecription", leftMap.get("SpellDescription")); + leftMap.put("SpellDescription", "Fuse (you may cast both halves of this card from your hand)."); + leftMap.put("ActivationZone", "Hand"); + + final String strRightAbility = card.getState(CardCharacteristicName.RightSplit).getUnparsedAbilities().get(0); + Map rightMap = getMapParams(strRightAbility); + AbilityRecordType rightType = AbilityRecordType.getRecordType(leftMap); + ApiType rightApi = leftType.getApiTypeOf(rightMap); + rightMap.put("StackDecription", rightMap.get("SpellDescription")); + rightMap.put("SpellDescription", ""); + + Cost totalCost = parseAbilityCost(card, leftMap, leftType); + totalCost.add(parseAbilityCost(card, rightMap, rightType)); + + final SpellAbility left = getAbility(leftType, leftApi, leftMap, totalCost, card); + final AbilitySub right = (AbilitySub) getAbility(AbilityRecordType.SubAbility, rightApi, rightMap, null, card); + left.appendSubAbility(right); + return left; + } + + public static final SpellAbility buildEntwineAbility(final SpellAbility sa) { + final Card source = sa.getSourceCard(); + final String[] saChoices = sa.getParam("Choices").split(","); + if (sa.getApi() != ApiType.Charm || saChoices.length != 2) + throw new IllegalStateException("Entwine ability may be built only on charm cards"); + final String ab = source.getSVar(saChoices[0]); + Map firstMap = getMapParams(ab); + AbilityRecordType firstType = AbilityRecordType.getRecordType(firstMap); + ApiType firstApi = firstType.getApiTypeOf(firstMap); + firstMap.put("StackDecription", firstMap.get("SpellDescription")); + firstMap.put("SpellDescription", sa.getDescription() + " Entwine (Choose both if you pay the entwine cost.)"); + SpellAbility entwineSA = getAbility(AbilityRecordType.Spell, firstApi, firstMap, new Cost(sa.getPayCosts().toSimpleString(), false), source); + + final String ab2 = source.getSVar(saChoices[1]); + Map secondMap = getMapParams(ab2); + ApiType secondApi = firstType.getApiTypeOf(secondMap); + secondMap.put("StackDecription", secondMap.get("SpellDescription")); + secondMap.put("SpellDescription", ""); + AbilitySub sub = (AbilitySub) getAbility(AbilityRecordType.SubAbility, secondApi, secondMap, null, source); + entwineSA.appendSubAbility(sub); + + entwineSA.setBasicSpell(false); + entwineSA.setActivatingPlayer(sa.getActivatingPlayer()); + entwineSA.setRestrictions(sa.getRestrictions()); + return entwineSA; + } + +} // end class AbilityFactory diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-gui/src/main/java/forge/game/ability/AbilityUtils.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/AbilityUtils.java rename to forge-gui/src/main/java/forge/game/ability/AbilityUtils.java diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-gui/src/main/java/forge/game/ability/ApiType.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/ApiType.java rename to forge-gui/src/main/java/forge/game/ability/ApiType.java diff --git a/forge-game/src/main/java/forge/game/ability/SaTargetRoutines.java b/forge-gui/src/main/java/forge/game/ability/SaTargetRoutines.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/SaTargetRoutines.java rename to forge-gui/src/main/java/forge/game/ability/SaTargetRoutines.java diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-gui/src/main/java/forge/game/ability/SpellAbilityEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java rename to forge-gui/src/main/java/forge/game/ability/SpellAbilityEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/SpellApiBased.java b/forge-gui/src/main/java/forge/game/ability/SpellApiBased.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/SpellApiBased.java rename to forge-gui/src/main/java/forge/game/ability/SpellApiBased.java diff --git a/forge-game/src/main/java/forge/game/ability/StaticAbilityApiBased.java b/forge-gui/src/main/java/forge/game/ability/StaticAbilityApiBased.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/StaticAbilityApiBased.java rename to forge-gui/src/main/java/forge/game/ability/StaticAbilityApiBased.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/AbandonEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/AbandonEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/AbandonEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/AbandonEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/AddPhaseEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/AddPhaseEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/AddPhaseEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/AddPhaseEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/AddTurnEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/AddTurnEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/AnimateAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/AnimateAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/AnimateEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/AnimateEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java b/forge-gui/src/main/java/forge/game/ability/effects/AnimateEffectBase.java similarity index 97% rename from forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java rename to forge-gui/src/main/java/forge/game/ability/effects/AnimateEffectBase.java index 69ee2e6b017..f4918d69c48 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java +++ b/forge-gui/src/main/java/forge/game/ability/effects/AnimateEffectBase.java @@ -1,184 +1,184 @@ -/* - * 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 . - */ -package forge.game.ability.effects; - -import java.util.ArrayList; - -import forge.game.ability.SpellAbilityEffect; -import forge.game.card.Card; -import forge.game.replacement.ReplacementEffect; -import forge.game.spellability.SpellAbility; -import forge.game.staticability.StaticAbility; -import forge.game.trigger.Trigger; - -public abstract class AnimateEffectBase extends SpellAbilityEffect { - - /** - *

- * doAnimate. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param af - * a {@link forge.game.ability.AbilityFactory} object. - * @param power - * a int. - * @param toughness - * a int. - * @param types - * a {@link java.util.ArrayList} object. - * @param colors - * a {@link java.lang.String} object. - * @param keywords - * a {@link java.util.ArrayList} object. - * @return a long. - */ - long doAnimate(final Card c, final SpellAbility sa, final int power, final int toughness, - final ArrayList types, final ArrayList removeTypes, final String colors, - final ArrayList keywords, final ArrayList removeKeywords, - final ArrayList hiddenKeywords, final long timestamp) { - - boolean removeSuperTypes = false; - boolean removeCardTypes = false; - boolean removeSubTypes = false; - boolean removeCreatureTypes = false; - - if (sa.hasParam("OverwriteTypes")) { - removeSuperTypes = true; - removeCardTypes = true; - removeSubTypes = true; - removeCreatureTypes = true; - } - - if (sa.hasParam("KeepSupertypes")) { - removeSuperTypes = false; - } - - if (sa.hasParam("KeepCardTypes")) { - removeCardTypes = false; - } - - if (sa.hasParam("RemoveSuperTypes")) { - removeSuperTypes = true; - } - - if (sa.hasParam("RemoveCardTypes")) { - removeCardTypes = true; - } - - if (sa.hasParam("RemoveSubTypes")) { - removeSubTypes = true; - } - - if (sa.hasParam("RemoveCreatureTypes")) { - removeCreatureTypes = true; - } - - if ((power != -1) || (toughness != -1)) { - c.addNewPT(power, toughness, timestamp); - } - - if (!types.isEmpty() || !removeTypes.isEmpty() || removeCreatureTypes) { - c.addChangedCardTypes(types, removeTypes, removeSuperTypes, removeCardTypes, removeSubTypes, - removeCreatureTypes, timestamp); - } - - c.addChangedCardKeywords(keywords, removeKeywords, sa.hasParam("RemoveAllAbilities"), timestamp); - - for (final String k : hiddenKeywords) { - c.addHiddenExtrinsicKeyword(k); - } - - final long colorTimestamp = c.addColor(colors, !sa.hasParam("OverwriteColors"), true); - return colorTimestamp; - } - - /** - *

- * doUnanimate. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param originalPower - * a int. - * @param originalToughness - * a int. - * @param originalTypes - * a {@link java.util.ArrayList} object. - * @param colorDesc - * a {@link java.lang.String} object. - * @param originalKeywords - * a {@link java.util.ArrayList} object. - * @param addedAbilities - * a {@link java.util.ArrayList} object. - * @param addedTriggers - * a {@link java.util.ArrayList} object. - * @param timestamp - * a long. - */ - void doUnanimate(final Card c, SpellAbility sa, final String colorDesc, - final ArrayList hiddenKeywords, final ArrayList addedAbilities, - final ArrayList addedTriggers, final ArrayList addedReplacements, - final long colorTimestamp, final boolean givesStAbs, - final ArrayList removedAbilities, final long timestamp) { - - c.removeNewPT(timestamp); - - c.removeChangedCardKeywords(timestamp); - - // remove all static abilities - if (givesStAbs) { - c.setStaticAbilities(new ArrayList()); - } - - if (sa.hasParam("Types") || sa.hasParam("RemoveTypes") - || sa.hasParam("RemoveCreatureTypes")) { - c.removeChangedCardTypes(timestamp); - } - - c.removeColor(colorDesc, c, !sa.hasParam("OverwriteColors"), colorTimestamp); - - for (final String k : hiddenKeywords) { - c.removeHiddenExtrinsicKeyword(k); - } - - for (final SpellAbility saAdd : addedAbilities) { - c.removeSpellAbility(saAdd); - } - - for (final SpellAbility saRem : removedAbilities) { - c.addSpellAbility(saRem); - } - - for (final Trigger t : addedTriggers) { - c.removeTrigger(t); - } - - for (final ReplacementEffect rep : addedReplacements) { - c.getReplacementEffects().remove(rep); - } - - // any other unanimate cleanup - if (!c.isCreature()) { - c.unEquipAllCards(); - } - } - -} +/* + * 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 . + */ +package forge.game.ability.effects; + +import java.util.ArrayList; + +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.replacement.ReplacementEffect; +import forge.game.spellability.SpellAbility; +import forge.game.staticability.StaticAbility; +import forge.game.trigger.Trigger; + +public abstract class AnimateEffectBase extends SpellAbilityEffect { + + /** + *

+ * doAnimate. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param af + * a {@link forge.game.ability.AbilityFactory} object. + * @param power + * a int. + * @param toughness + * a int. + * @param types + * a {@link java.util.ArrayList} object. + * @param colors + * a {@link java.lang.String} object. + * @param keywords + * a {@link java.util.ArrayList} object. + * @return a long. + */ + long doAnimate(final Card c, final SpellAbility sa, final int power, final int toughness, + final ArrayList types, final ArrayList removeTypes, final String colors, + final ArrayList keywords, final ArrayList removeKeywords, + final ArrayList hiddenKeywords, final long timestamp) { + + boolean removeSuperTypes = false; + boolean removeCardTypes = false; + boolean removeSubTypes = false; + boolean removeCreatureTypes = false; + + if (sa.hasParam("OverwriteTypes")) { + removeSuperTypes = true; + removeCardTypes = true; + removeSubTypes = true; + removeCreatureTypes = true; + } + + if (sa.hasParam("KeepSupertypes")) { + removeSuperTypes = false; + } + + if (sa.hasParam("KeepCardTypes")) { + removeCardTypes = false; + } + + if (sa.hasParam("RemoveSuperTypes")) { + removeSuperTypes = true; + } + + if (sa.hasParam("RemoveCardTypes")) { + removeCardTypes = true; + } + + if (sa.hasParam("RemoveSubTypes")) { + removeSubTypes = true; + } + + if (sa.hasParam("RemoveCreatureTypes")) { + removeCreatureTypes = true; + } + + if ((power != -1) || (toughness != -1)) { + c.addNewPT(power, toughness, timestamp); + } + + if (!types.isEmpty() || !removeTypes.isEmpty() || removeCreatureTypes) { + c.addChangedCardTypes(types, removeTypes, removeSuperTypes, removeCardTypes, removeSubTypes, + removeCreatureTypes, timestamp); + } + + c.addChangedCardKeywords(keywords, removeKeywords, sa.hasParam("RemoveAllAbilities"), timestamp); + + for (final String k : hiddenKeywords) { + c.addHiddenExtrinsicKeyword(k); + } + + final long colorTimestamp = c.addColor(colors, !sa.hasParam("OverwriteColors"), true); + return colorTimestamp; + } + + /** + *

+ * doUnanimate. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param originalPower + * a int. + * @param originalToughness + * a int. + * @param originalTypes + * a {@link java.util.ArrayList} object. + * @param colorDesc + * a {@link java.lang.String} object. + * @param originalKeywords + * a {@link java.util.ArrayList} object. + * @param addedAbilities + * a {@link java.util.ArrayList} object. + * @param addedTriggers + * a {@link java.util.ArrayList} object. + * @param timestamp + * a long. + */ + void doUnanimate(final Card c, SpellAbility sa, final String colorDesc, + final ArrayList hiddenKeywords, final ArrayList addedAbilities, + final ArrayList addedTriggers, final ArrayList addedReplacements, + final long colorTimestamp, final boolean givesStAbs, + final ArrayList removedAbilities, final long timestamp) { + + c.removeNewPT(timestamp); + + c.removeChangedCardKeywords(timestamp); + + // remove all static abilities + if (givesStAbs) { + c.setStaticAbilities(new ArrayList()); + } + + if (sa.hasParam("Types") || sa.hasParam("RemoveTypes") + || sa.hasParam("RemoveCreatureTypes")) { + c.removeChangedCardTypes(timestamp); + } + + c.removeColor(colorDesc, c, !sa.hasParam("OverwriteColors"), colorTimestamp); + + for (final String k : hiddenKeywords) { + c.removeHiddenExtrinsicKeyword(k); + } + + for (final SpellAbility saAdd : addedAbilities) { + c.removeSpellAbility(saAdd); + } + + for (final SpellAbility saRem : removedAbilities) { + c.addSpellAbility(saRem); + } + + for (final Trigger t : addedTriggers) { + c.removeTrigger(t); + } + + for (final ReplacementEffect rep : addedReplacements) { + c.getReplacementEffects().remove(rep); + } + + // any other unanimate cleanup + if (!c.isCreature()) { + c.unEquipAllCards(); + } + } + +} diff --git a/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/AttachEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/AttachEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/BalanceEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/BalanceEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/BondEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/BondEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/BondEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/BondEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/CharmEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/CharmEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ChooseCardEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ChooseCardEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java similarity index 96% rename from forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java index 3b5adaa742d..eef08a3f097 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java +++ b/forge-gui/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java @@ -9,7 +9,7 @@ import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; -import forge.StaticData; +import forge.Singletons; import forge.ai.ComputerUtilCard; import forge.card.CardRules; import forge.card.CardRulesPredicates; @@ -70,7 +70,7 @@ public class ChooseCardNameEffect extends SpellAbilityEffect { Predicate additionalRule = CardRulesPredicates.cmc(ComparableOp.EQUALS, validAmount); - List cards = Lists.newArrayList(StaticData.instance().getCommonCards().getUniqueCards()); + List cards = Lists.newArrayList(Singletons.getMagicDb().getCommonCards().getUniqueCards()); Predicate cpp = Predicates.and(Predicates.compose(baseRule, PaperCard.FN_GET_RULES), Predicates.compose(additionalRule, PaperCard.FN_GET_RULES)); cards = Lists.newArrayList(Iterables.filter(cards, cpp)); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseColorEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ChooseColorEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ChooseColorEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ChooseColorEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChoosePlayerEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ChoosePlayerEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ChoosePlayerEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ChoosePlayerEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseSourceEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ChooseSourceEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ChooseSourceEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ChooseSourceEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseTypeEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ChooseTypeEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ChooseTypeEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ChooseTypeEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ClashEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ClashEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ClashEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ClashEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/CleanUpEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/CleanUpEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/CleanUpEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/CleanUpEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/CloneEffect.java similarity index 95% rename from forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/CloneEffect.java index 46e05e0cc2d..deb17778a17 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java +++ b/forge-gui/src/main/java/forge/game/ability/effects/CloneEffect.java @@ -6,7 +6,7 @@ import java.util.List; import java.util.Map; import forge.Command; -import forge.PreferencesBridge; +import forge.Singletons; import forge.card.CardCharacteristicName; import forge.game.Game; import forge.game.ability.AbilityUtils; @@ -19,6 +19,7 @@ import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; +import forge.properties.ForgePreferences.FPref; public class CloneEffect extends SpellAbilityEffect { // TODO update this method @@ -84,7 +85,8 @@ public class CloneEffect extends SpellAbilityEffect { } // determine the image to be used for the clone - String imageFileName = PreferencesBridge.Instance.getCloneModeSource() ? tgtCard.getImageKey() : cardToCopy.getImageKey(); + String imageFileName = Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_CLONE_MODE_SOURCE) + ? tgtCard.getImageKey() : cardToCopy.getImageKey(); if (sa.hasParam("ImageSource")) { // Allow the image to be stipulated by using a defined card source List cloneImgSources = AbilityUtils.getDefinedCards(host, sa.getParam("ImageSource"), sa); if (!cloneImgSources.isEmpty()) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ControlGainEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ControlGainEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java similarity index 96% rename from forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java index 2f042abe23f..cf82f5541f0 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java +++ b/forge-gui/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java @@ -12,7 +12,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import forge.Command; -import forge.StaticData; +import forge.Singletons; import forge.card.CardCharacteristicName; import forge.card.CardRulesPredicates; import forge.card.mana.ManaCost; @@ -71,7 +71,7 @@ public class CopyPermanentEffect extends SpellAbilityEffect { final TargetRestrictions tgt = sa.getTargetRestrictions(); if (sa.hasParam("ValidSupportedCopy")) { - List cards = Lists.newArrayList(StaticData.instance().getCommonCards().getUniqueCards()); + List cards = Lists.newArrayList(Singletons.getMagicDb().getCommonCards().getUniqueCards()); String valid = sa.getParam("ValidSupportedCopy"); if (valid.contains("X")) { valid = valid.replace("X", Integer.toString(AbilityUtils.calculateAmount(hostCard, "X", sa))); diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/CounterEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/CounterEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/CountersMoveEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/CountersMoveEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/CountersPutEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/CountersPutEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/CountersRemoveAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/CountersRemoveAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/CountersRemoveAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DamageAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DamageAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DamageDealEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DamageDealEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DamageEachEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DamageEachEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamagePreventAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DamagePreventAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DamagePreventAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DamagePreventAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DamagePreventEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DamagePreventEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DebuffAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DebuffAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DebuffAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DebuffAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DebuffEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DebuffEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DeclareCombatantsEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DeclareCombatantsEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DeclareCombatantsEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DeclareCombatantsEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DestroyAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DestroyAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DestroyAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DestroyAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DestroyEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DestroyEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DestroyEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DestroyEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DigEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DigEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DigEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DigUntilEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DigUntilEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DiscardEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DiscardEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DrainManaEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DrainManaEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DrainManaEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DrainManaEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/DrawEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/DrawEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/DrawEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/DrawEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ETBReplacementEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ETBReplacementEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ETBReplacementEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ETBReplacementEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/EffectEffect.java similarity index 95% rename from forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/EffectEffect.java index 0f2c4b386b8..69d950a2a88 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java +++ b/forge-gui/src/main/java/forge/game/ability/effects/EffectEffect.java @@ -3,7 +3,7 @@ package forge.game.ability.effects; import java.util.List; import forge.Command; -import forge.ImageCacheBridge; +import forge.ImageCache; import forge.game.Game; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; @@ -104,7 +104,7 @@ public class EffectEffect extends SpellAbilityEffect { eff.addType("Effect"); // Or Emblem eff.setToken(true); // Set token to true, so when leaving play it gets nuked eff.setOwner(controller); - eff.setImageKey(sa.hasParam("Image") ? ImageCacheBridge.instance.getTokenKey(sa.getParam("Image")) : hostCard.getImageKey()); + eff.setImageKey(sa.hasParam("Image") ? ImageCache.TOKEN_PREFIX + sa.getParam("Image") : hostCard.getImageKey()); eff.setColor(hostCard.getColor()); eff.setImmutable(true); eff.setEffectSource(hostCard); diff --git a/forge-game/src/main/java/forge/game/ability/effects/EncodeEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/EncodeEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/EncodeEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/EncodeEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/EndTurnEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/EndTurnEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/EndTurnEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/EndTurnEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/FightEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/FightEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/FightEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/FlipCoinEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/FlipCoinEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/FogEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/FogEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/FogEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/FogEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/GameLossEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/GameLossEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/GameLossEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/GameLossEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/GameWinEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/GameWinEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/GameWinEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/GameWinEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/LifeExchangeEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/LifeExchangeEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/LifeExchangeEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/LifeExchangeEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/LifeGainEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/LifeGainEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/LifeGainEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/LifeGainEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/LifeLoseEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/LifeLoseEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/LifeLoseEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/LifeLoseEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/LifeSetEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/LifeSetEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/LifeSetEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/LifeSetEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ManaEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ManaEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/MillEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/MillEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/MillEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/MustAttackEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/MustAttackEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/MustAttackEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/MustAttackEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/MustBlockEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/MustBlockEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/MustBlockEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/MustBlockEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/OwnershipGainEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/OwnershipGainEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/OwnershipGainEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/OwnershipGainEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/PeekAndRevealEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/PeekAndRevealEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/PeekAndRevealEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/PeekAndRevealEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/PermanentCreatureEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/PermanentCreatureEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/PermanentCreatureEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/PermanentCreatureEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/PhasesEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/PhasesEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/PhasesEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/PhasesEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlaneswalkEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/PlaneswalkEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/PlaneswalkEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/PlaneswalkEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/PlayEffect.java similarity index 96% rename from forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/PlayEffect.java index 39079f0df2c..9bda553455f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java +++ b/forge-gui/src/main/java/forge/game/ability/effects/PlayEffect.java @@ -10,7 +10,7 @@ import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; -import forge.StaticData; +import forge.Singletons; import forge.card.CardCharacteristicName; import forge.card.CardRulesPredicates; import forge.game.Game; @@ -83,7 +83,7 @@ public class PlayEffect extends SpellAbilityEffect { useEncoded = true; } else if (sa.hasParam("AnySupportedCard")) { - List cards = Lists.newArrayList(StaticData.instance().getCommonCards().getUniqueCards()); + List cards = Lists.newArrayList(Singletons.getMagicDb().getCommonCards().getUniqueCards()); String valid = sa.getParam("AnySupportedCard"); if (StringUtils.containsIgnoreCase(valid, "sorcery")) { Predicate cpp = Predicates.compose(CardRulesPredicates.Presets.IS_SORCERY, PaperCard.FN_GET_RULES); diff --git a/forge-game/src/main/java/forge/game/ability/effects/PoisonEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/PoisonEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/PoisonEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/PoisonEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/PowerExchangeEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/PowerExchangeEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/PowerExchangeEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/PowerExchangeEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ProtectAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ProtectAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ProtectEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ProtectEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ProtectEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ProtectEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/PumpAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/PumpAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/PumpEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/PumpEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/RearrangeTopOfLibraryEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/RearrangeTopOfLibraryEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/RearrangeTopOfLibraryEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/RearrangeTopOfLibraryEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/RegenerateEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/RegenerateEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/RegenerateEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/RegenerateEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/RepeatEachEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/RepeatEachEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/RepeatEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/RepeatEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/RestartGameEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/RestartGameEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/RevealEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/RevealEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/RevealEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/RevealEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/RevealHandEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/RevealHandEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/RevealHandEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/RevealHandEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/RollPlanarDiceEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/RollPlanarDiceEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/RollPlanarDiceEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/RollPlanarDiceEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/RunSVarAbilityEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/RunSVarAbilityEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/RunSVarAbilityEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/RunSVarAbilityEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/SacrificeEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/SacrificeEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ScryEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ScryEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ScryEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ScryEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/SetInMotionEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/SetInMotionEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/SetInMotionEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/SetInMotionEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/SetStateEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/SetStateEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ShuffleEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ShuffleEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ShuffleEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ShuffleEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/SkipTurnEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/SkipTurnEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/StoreSVarEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/StoreSVarEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/StoreSVarEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/StoreSVarEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/TapAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/TapAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/TapAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/TapAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/TapEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/TapEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/TapEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/TapEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/TapOrUntapAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/TapOrUntapAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/TapOrUntapAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/TapOrUntapAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/TapOrUntapEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/TapOrUntapEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/TapOrUntapEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/TapOrUntapEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/TokenEffect.java similarity index 97% rename from forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/TokenEffect.java index f8827e587b4..0f1615de089 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java +++ b/forge-gui/src/main/java/forge/game/ability/effects/TokenEffect.java @@ -1,328 +1,328 @@ -/* - * 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 . - */ -package forge.game.ability.effects; - -import java.util.Arrays; -import java.util.List; - -import com.google.common.collect.Iterables; - - -import forge.game.Game; -import forge.game.GameEntity; -import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityUtils; -import forge.game.ability.SpellAbilityEffect; -import forge.game.card.Card; -import forge.game.card.CardFactory; -import forge.game.combat.Combat; -import forge.game.event.GameEventTokenCreated; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerHandler; -import forge.item.PaperToken; - -public class TokenEffect extends SpellAbilityEffect { - - private String tokenOwner; - private String[] tokenColors; - private String tokenImage; - private String[] tokenAbilities; - private String[] tokenTriggers; - private String[] tokenSVars; - private String[] tokenStaticAbilities; - private boolean tokenTapped; - private boolean tokenAttacking; - private String tokenBlocking; - private String tokenAmount; - private String tokenToughness; - private String tokenPower; - private String[] tokenTypes; - private String tokenName; - private String[] tokenKeywords; - private String[] tokenHiddenKeywords; - - private void readParameters(final SpellAbility mapParams) { - String image; - String[] keywords; - - if (mapParams.hasParam("TokenKeywords")) { - // TODO: Change this Split to a semicolon or something else - keywords = mapParams.getParam("TokenKeywords").split("<>"); - } else { - keywords = new String[0]; - } - - if (mapParams.hasParam("TokenHiddenKeywords")) { - this.tokenHiddenKeywords = mapParams.getParam("TokenHiddenKeywords").split("&"); - } - - if (mapParams.hasParam("TokenImage")) { - image = PaperToken.makeTokenFileName(mapParams.getParam("TokenImage")); - } else { - image = ""; - } - - this.tokenTapped = mapParams.hasParam("TokenTapped") && mapParams.getParam("TokenTapped").equals("True"); - this.tokenAttacking = mapParams.hasParam("TokenAttacking") && mapParams.getParam("TokenAttacking").equals("True"); - - if (mapParams.hasParam("TokenAbilities")) { - this.tokenAbilities = mapParams.getParam("TokenAbilities").split(","); - } else { - this.tokenAbilities = null; - } - if (mapParams.hasParam("TokenTriggers")) { - this.tokenTriggers = mapParams.getParam("TokenTriggers").split(","); - } else { - this.tokenTriggers = null; - } - if (mapParams.hasParam("TokenSVars")) { - this.tokenSVars = mapParams.getParam("TokenSVars").split(","); - } else { - this.tokenSVars = null; - } - if (mapParams.hasParam("TokenStaticAbilities")) { - this.tokenStaticAbilities = mapParams.getParam("TokenStaticAbilities").split(","); - } else { - this.tokenStaticAbilities = null; - } - this.tokenBlocking = mapParams.getParam("TokenBlocking"); - this.tokenAmount = mapParams.getParam("TokenAmount"); - this.tokenPower = mapParams.getParam("TokenPower"); - this.tokenToughness = mapParams.getParam("TokenToughness"); - this.tokenName = mapParams.getParam("TokenName"); - this.tokenTypes = mapParams.getParam("TokenTypes").split(","); - this.tokenColors = mapParams.getParam("TokenColors").split(","); - this.tokenKeywords = keywords; - this.tokenImage = image; - if (mapParams.hasParam("TokenOwner")) { - this.tokenOwner = mapParams.getParam("TokenOwner"); - } else { - this.tokenOwner = "You"; - } - } - - @Override - protected String getStackDescription(SpellAbility sa) { - final StringBuilder sb = new StringBuilder(); - final Card host = sa.getSourceCard(); - - readParameters(sa); - - final int finalPower = AbilityUtils.calculateAmount(host, this.tokenPower, sa); - final int finalToughness = AbilityUtils.calculateAmount(host, this.tokenToughness, sa); - final int finalAmount = AbilityUtils.calculateAmount(host, this.tokenAmount, sa); - - final String substitutedName = this.tokenName.equals("ChosenType") ? host.getChosenType() : this.tokenName; - - sb.append("Put (").append(finalAmount).append(") ").append(finalPower).append("/").append(finalToughness); - sb.append(" ").append(substitutedName).append(" token"); - if (finalAmount != 1) { - sb.append("s"); - } - sb.append(" onto the battlefield"); - - if (this.tokenOwner.equals("Opponent")) { - sb.append(" under your opponent's control."); - } else { - sb.append("."); - } - - return sb.toString(); - } - - @Override - public void resolve(SpellAbility sa) { - final Card host = sa.getSourceCard(); - readParameters(sa); - - String cost = ""; - // Construct colors - final String[] substitutedColors = Arrays.copyOf(this.tokenColors, this.tokenColors.length); - for (int i = 0; i < substitutedColors.length; i++) { - if (substitutedColors[i].equals("ChosenColor")) { - // this currently only supports 1 chosen color - substitutedColors[i] = host.getChosenColor().get(0); - } - } - String colorDesc = ""; - for (final String col : substitutedColors) { - if (col.equalsIgnoreCase("White")) { - colorDesc += "W "; - } else if (col.equalsIgnoreCase("Blue")) { - colorDesc += "U "; - } else if (col.equalsIgnoreCase("Black")) { - colorDesc += "B "; - } else if (col.equalsIgnoreCase("Red")) { - colorDesc += "R "; - } else if (col.equalsIgnoreCase("Green")) { - colorDesc += "G "; - } else if (col.equalsIgnoreCase("Colorless")) { - colorDesc = "C"; - } - } - - final String imageName; - if (this.tokenImage.equals("")) { - imageName = PaperToken.makeTokenFileName(colorDesc.replace(" ", ""), tokenPower, tokenToughness, tokenName); - } else { - imageName = this.tokenImage; - } - // System.out.println("AF_Token imageName = " + imageName); - - for (final char c : colorDesc.toCharArray()) { - cost += c + ' '; - } - - cost = colorDesc.replace('C', '1').trim(); - - final int finalPower = AbilityUtils.calculateAmount(host, this.tokenPower, sa); - final int finalToughness = AbilityUtils.calculateAmount(host, this.tokenToughness, sa); - final int finalAmount = AbilityUtils.calculateAmount(host, this.tokenAmount, sa); - - final String[] substitutedTypes = Arrays.copyOf(this.tokenTypes, this.tokenTypes.length); - for (int i = 0; i < substitutedTypes.length; i++) { - if (substitutedTypes[i].equals("ChosenType")) { - substitutedTypes[i] = host.getChosenType(); - } - } - final String substitutedName = this.tokenName.equals("ChosenType") ? host.getChosenType() : this.tokenName; - - final boolean remember = sa.hasParam("RememberTokens"); - final boolean imprint = sa.hasParam("ImprintTokens"); - for (final Player controller : AbilityUtils.getDefinedPlayers(host, this.tokenOwner, sa)) { - for (int i = 0; i < finalAmount; i++) { - final List tokens = CardFactory.makeToken(substitutedName, imageName, controller, cost, - substitutedTypes, finalPower, finalToughness, this.tokenKeywords); - for(Card tok : tokens) { - controller.getGame().getAction().moveToPlay(tok); - } - controller.getGame().fireEvent(new GameEventTokenCreated()); - - // Grant rule changes - if (this.tokenHiddenKeywords != null) { - for (final String s : this.tokenHiddenKeywords) { - for (final Card c : tokens) { - c.addHiddenExtrinsicKeyword(s); - } - } - } - - // Grant abilities - if (this.tokenAbilities != null) { - for (final String s : this.tokenAbilities) { - final String actualAbility = host.getSVar(s); - for (final Card c : tokens) { - final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, c); - c.addSpellAbility(grantedAbility); - // added ability to intrinsic list so copies and clones work - c.getUnparsedAbilities().add(actualAbility); - } - } - } - - // Grant triggers - if (this.tokenTriggers != null) { - - for (final String s : this.tokenTriggers) { - final String actualTrigger = host.getSVar(s); - - for (final Card c : tokens) { - - final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, c, true); - final String ability = host.getSVar(parsedTrigger.getMapParams().get("Execute")); - parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(ability, c)); - c.addTrigger(parsedTrigger); - } - } - } - - // Grant SVars - if (this.tokenSVars != null) { - for (final String s : this.tokenSVars) { - String actualSVar = host.getSVar(s); - String name = s; - if (actualSVar.startsWith("SVar")) { - actualSVar = actualSVar.split("SVar:")[1]; - name = actualSVar.split(":")[0]; - actualSVar = actualSVar.split(":")[1]; - } - for (final Card c : tokens) { - c.setSVar(name, actualSVar); - } - } - } - - // Grant static abilities - if (this.tokenStaticAbilities != null) { - for (final String s : this.tokenStaticAbilities) { - final String actualAbility = host.getSVar(s); - for (final Card c : tokens) { - c.addStaticAbilityString(actualAbility); - c.addStaticAbility(actualAbility); - } - } - } - - final Game game = controller.getGame(); - for (final Card c : tokens) { - if (this.tokenTapped) { - c.setTapped(true); - } - if (this.tokenAttacking && game.getPhaseHandler().inCombat()) { - Combat combat = game.getPhaseHandler().getCombat(); - final List defs = combat.getDefenders(); - final GameEntity defender = c.getController().getController().chooseSingleEntityForEffect(defs, sa, "Choose which defender to attack with " + c, false); - combat.addAttacker(c, defender); - } - if (this.tokenBlocking != null && game.getPhaseHandler().inCombat()) { - Combat combat = game.getPhaseHandler().getCombat(); - final Card attacker = Iterables.getFirst(AbilityUtils.getDefinedCards(host, this.tokenBlocking, sa), null); - if (attacker != null) { - if (combat.isBlocked(attacker)) { - combat.addBlocker(attacker, c); - combat.orderAttackersForDamageAssignment(c); - } else { - // TODO Flash Foliage: set blocked; attackerBlocked trigger; damage - } - } - } - if (remember) { - game.getCardState(sa.getSourceCard()).addRemembered(c); - } - if (imprint) { - game.getCardState(sa.getSourceCard()).addImprinted(c); - } - if (sa.hasParam("RememberSource")) { - game.getCardState(c).addRemembered(host); - } - if (sa.hasParam("TokenRemembered")) { - final Card token = game.getCardState(c); - final String remembered = sa.getParam("TokenRemembered"); - for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) { - if (!token.getRemembered().contains(o)) { - token.addRemembered(o); - } - } - } - } - } - } - } -} +/* + * 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 . + */ +package forge.game.ability.effects; + +import java.util.Arrays; +import java.util.List; + +import com.google.common.collect.Iterables; + + +import forge.game.Game; +import forge.game.GameEntity; +import forge.game.ability.AbilityFactory; +import forge.game.ability.AbilityUtils; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.card.CardFactory; +import forge.game.combat.Combat; +import forge.game.event.GameEventTokenCreated; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerHandler; +import forge.item.PaperToken; + +public class TokenEffect extends SpellAbilityEffect { + + private String tokenOwner; + private String[] tokenColors; + private String tokenImage; + private String[] tokenAbilities; + private String[] tokenTriggers; + private String[] tokenSVars; + private String[] tokenStaticAbilities; + private boolean tokenTapped; + private boolean tokenAttacking; + private String tokenBlocking; + private String tokenAmount; + private String tokenToughness; + private String tokenPower; + private String[] tokenTypes; + private String tokenName; + private String[] tokenKeywords; + private String[] tokenHiddenKeywords; + + private void readParameters(final SpellAbility mapParams) { + String image; + String[] keywords; + + if (mapParams.hasParam("TokenKeywords")) { + // TODO: Change this Split to a semicolon or something else + keywords = mapParams.getParam("TokenKeywords").split("<>"); + } else { + keywords = new String[0]; + } + + if (mapParams.hasParam("TokenHiddenKeywords")) { + this.tokenHiddenKeywords = mapParams.getParam("TokenHiddenKeywords").split("&"); + } + + if (mapParams.hasParam("TokenImage")) { + image = PaperToken.makeTokenFileName(mapParams.getParam("TokenImage")); + } else { + image = ""; + } + + this.tokenTapped = mapParams.hasParam("TokenTapped") && mapParams.getParam("TokenTapped").equals("True"); + this.tokenAttacking = mapParams.hasParam("TokenAttacking") && mapParams.getParam("TokenAttacking").equals("True"); + + if (mapParams.hasParam("TokenAbilities")) { + this.tokenAbilities = mapParams.getParam("TokenAbilities").split(","); + } else { + this.tokenAbilities = null; + } + if (mapParams.hasParam("TokenTriggers")) { + this.tokenTriggers = mapParams.getParam("TokenTriggers").split(","); + } else { + this.tokenTriggers = null; + } + if (mapParams.hasParam("TokenSVars")) { + this.tokenSVars = mapParams.getParam("TokenSVars").split(","); + } else { + this.tokenSVars = null; + } + if (mapParams.hasParam("TokenStaticAbilities")) { + this.tokenStaticAbilities = mapParams.getParam("TokenStaticAbilities").split(","); + } else { + this.tokenStaticAbilities = null; + } + this.tokenBlocking = mapParams.getParam("TokenBlocking"); + this.tokenAmount = mapParams.getParam("TokenAmount"); + this.tokenPower = mapParams.getParam("TokenPower"); + this.tokenToughness = mapParams.getParam("TokenToughness"); + this.tokenName = mapParams.getParam("TokenName"); + this.tokenTypes = mapParams.getParam("TokenTypes").split(","); + this.tokenColors = mapParams.getParam("TokenColors").split(","); + this.tokenKeywords = keywords; + this.tokenImage = image; + if (mapParams.hasParam("TokenOwner")) { + this.tokenOwner = mapParams.getParam("TokenOwner"); + } else { + this.tokenOwner = "You"; + } + } + + @Override + protected String getStackDescription(SpellAbility sa) { + final StringBuilder sb = new StringBuilder(); + final Card host = sa.getSourceCard(); + + readParameters(sa); + + final int finalPower = AbilityUtils.calculateAmount(host, this.tokenPower, sa); + final int finalToughness = AbilityUtils.calculateAmount(host, this.tokenToughness, sa); + final int finalAmount = AbilityUtils.calculateAmount(host, this.tokenAmount, sa); + + final String substitutedName = this.tokenName.equals("ChosenType") ? host.getChosenType() : this.tokenName; + + sb.append("Put (").append(finalAmount).append(") ").append(finalPower).append("/").append(finalToughness); + sb.append(" ").append(substitutedName).append(" token"); + if (finalAmount != 1) { + sb.append("s"); + } + sb.append(" onto the battlefield"); + + if (this.tokenOwner.equals("Opponent")) { + sb.append(" under your opponent's control."); + } else { + sb.append("."); + } + + return sb.toString(); + } + + @Override + public void resolve(SpellAbility sa) { + final Card host = sa.getSourceCard(); + readParameters(sa); + + String cost = ""; + // Construct colors + final String[] substitutedColors = Arrays.copyOf(this.tokenColors, this.tokenColors.length); + for (int i = 0; i < substitutedColors.length; i++) { + if (substitutedColors[i].equals("ChosenColor")) { + // this currently only supports 1 chosen color + substitutedColors[i] = host.getChosenColor().get(0); + } + } + String colorDesc = ""; + for (final String col : substitutedColors) { + if (col.equalsIgnoreCase("White")) { + colorDesc += "W "; + } else if (col.equalsIgnoreCase("Blue")) { + colorDesc += "U "; + } else if (col.equalsIgnoreCase("Black")) { + colorDesc += "B "; + } else if (col.equalsIgnoreCase("Red")) { + colorDesc += "R "; + } else if (col.equalsIgnoreCase("Green")) { + colorDesc += "G "; + } else if (col.equalsIgnoreCase("Colorless")) { + colorDesc = "C"; + } + } + + final String imageName; + if (this.tokenImage.equals("")) { + imageName = PaperToken.makeTokenFileName(colorDesc.replace(" ", ""), tokenPower, tokenToughness, tokenName); + } else { + imageName = this.tokenImage; + } + // System.out.println("AF_Token imageName = " + imageName); + + for (final char c : colorDesc.toCharArray()) { + cost += c + ' '; + } + + cost = colorDesc.replace('C', '1').trim(); + + final int finalPower = AbilityUtils.calculateAmount(host, this.tokenPower, sa); + final int finalToughness = AbilityUtils.calculateAmount(host, this.tokenToughness, sa); + final int finalAmount = AbilityUtils.calculateAmount(host, this.tokenAmount, sa); + + final String[] substitutedTypes = Arrays.copyOf(this.tokenTypes, this.tokenTypes.length); + for (int i = 0; i < substitutedTypes.length; i++) { + if (substitutedTypes[i].equals("ChosenType")) { + substitutedTypes[i] = host.getChosenType(); + } + } + final String substitutedName = this.tokenName.equals("ChosenType") ? host.getChosenType() : this.tokenName; + + final boolean remember = sa.hasParam("RememberTokens"); + final boolean imprint = sa.hasParam("ImprintTokens"); + for (final Player controller : AbilityUtils.getDefinedPlayers(host, this.tokenOwner, sa)) { + for (int i = 0; i < finalAmount; i++) { + final List tokens = CardFactory.makeToken(substitutedName, imageName, controller, cost, + substitutedTypes, finalPower, finalToughness, this.tokenKeywords); + for(Card tok : tokens) { + controller.getGame().getAction().moveToPlay(tok); + } + controller.getGame().fireEvent(new GameEventTokenCreated()); + + // Grant rule changes + if (this.tokenHiddenKeywords != null) { + for (final String s : this.tokenHiddenKeywords) { + for (final Card c : tokens) { + c.addHiddenExtrinsicKeyword(s); + } + } + } + + // Grant abilities + if (this.tokenAbilities != null) { + for (final String s : this.tokenAbilities) { + final String actualAbility = host.getSVar(s); + for (final Card c : tokens) { + final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, c); + c.addSpellAbility(grantedAbility); + // added ability to intrinsic list so copies and clones work + c.getUnparsedAbilities().add(actualAbility); + } + } + } + + // Grant triggers + if (this.tokenTriggers != null) { + + for (final String s : this.tokenTriggers) { + final String actualTrigger = host.getSVar(s); + + for (final Card c : tokens) { + + final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, c, true); + final String ability = host.getSVar(parsedTrigger.getMapParams().get("Execute")); + parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(ability, c)); + c.addTrigger(parsedTrigger); + } + } + } + + // Grant SVars + if (this.tokenSVars != null) { + for (final String s : this.tokenSVars) { + String actualSVar = host.getSVar(s); + String name = s; + if (actualSVar.startsWith("SVar")) { + actualSVar = actualSVar.split("SVar:")[1]; + name = actualSVar.split(":")[0]; + actualSVar = actualSVar.split(":")[1]; + } + for (final Card c : tokens) { + c.setSVar(name, actualSVar); + } + } + } + + // Grant static abilities + if (this.tokenStaticAbilities != null) { + for (final String s : this.tokenStaticAbilities) { + final String actualAbility = host.getSVar(s); + for (final Card c : tokens) { + c.addStaticAbilityString(actualAbility); + c.addStaticAbility(actualAbility); + } + } + } + + final Game game = controller.getGame(); + for (final Card c : tokens) { + if (this.tokenTapped) { + c.setTapped(true); + } + if (this.tokenAttacking && game.getPhaseHandler().inCombat()) { + Combat combat = game.getPhaseHandler().getCombat(); + final List defs = combat.getDefenders(); + final GameEntity defender = c.getController().getController().chooseSingleEntityForEffect(defs, sa, "Choose which defender to attack with " + c, false); + combat.addAttacker(c, defender); + } + if (this.tokenBlocking != null && game.getPhaseHandler().inCombat()) { + Combat combat = game.getPhaseHandler().getCombat(); + final Card attacker = Iterables.getFirst(AbilityUtils.getDefinedCards(host, this.tokenBlocking, sa), null); + if (attacker != null) { + if (combat.isBlocked(attacker)) { + combat.addBlocker(attacker, c); + combat.orderAttackersForDamageAssignment(c); + } else { + // TODO Flash Foliage: set blocked; attackerBlocked trigger; damage + } + } + } + if (remember) { + game.getCardState(sa.getSourceCard()).addRemembered(c); + } + if (imprint) { + game.getCardState(sa.getSourceCard()).addImprinted(c); + } + if (sa.hasParam("RememberSource")) { + game.getCardState(c).addRemembered(host); + } + if (sa.hasParam("TokenRemembered")) { + final Card token = game.getCardState(c); + final String remembered = sa.getParam("TokenRemembered"); + for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) { + if (!token.getRemembered().contains(o)) { + token.addRemembered(o); + } + } + } + } + } + } + } +} diff --git a/forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/TwoPilesEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/TwoPilesEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/UnattachAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/UnattachAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/UnattachAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/UnattachAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/UntapAllEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/UntapAllEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/UntapAllEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/UntapAllEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/UntapEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/UntapEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/UntapEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/UntapEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/ZoneExchangeEffect.java b/forge-gui/src/main/java/forge/game/ability/effects/ZoneExchangeEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/ability/effects/ZoneExchangeEffect.java rename to forge-gui/src/main/java/forge/game/ability/effects/ZoneExchangeEffect.java diff --git a/forge-game/src/main/java/forge/game/ability/package-info.java b/forge-gui/src/main/java/forge/game/ability/package-info.java similarity index 94% rename from forge-game/src/main/java/forge/game/ability/package-info.java rename to forge-gui/src/main/java/forge/game/ability/package-info.java index 5c556f87565..a4e870a84a5 100644 --- a/forge-game/src/main/java/forge/game/ability/package-info.java +++ b/forge-gui/src/main/java/forge/game/ability/package-info.java @@ -1,3 +1,3 @@ -/** Forge Card Game. */ -package forge.game.ability; - +/** Forge Card Game. */ +package forge.game.ability; + diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-gui/src/main/java/forge/game/card/Card.java similarity index 96% rename from forge-game/src/main/java/forge/game/card/Card.java rename to forge-gui/src/main/java/forge/game/card/Card.java index a4276da04d1..8ca384c0cef 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-gui/src/main/java/forge/game/card/Card.java @@ -1,8730 +1,8730 @@ -/* - * 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 . - */ -package forge.game.card; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeMap; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.CopyOnWriteArrayList; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; - -import com.esotericsoftware.minlog.Log; -import com.google.common.base.Function; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; - -import forge.Command; -import forge.StaticData; -import forge.card.CardCharacteristicName; -import forge.card.CardEdition; -import forge.card.CardRarity; -import forge.card.CardRules; -import forge.card.ColorSet; -import forge.card.MagicColor; -import forge.card.mana.ManaCost; -import forge.card.mana.ManaCostParser; -import forge.game.Game; -import forge.game.GameActionUtil; -import forge.game.GameEntity; -import forge.game.GameLogEntryType; -import forge.game.GlobalRuleChange; -import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; -import forge.game.card.CardPredicates.Presets; -import forge.game.combat.AttackingBand; -import forge.game.combat.Combat; -import forge.game.cost.Cost; -import forge.game.event.GameEventCardDamaged; -import forge.game.event.GameEventCardAttachment.AttachMethod; -import forge.game.event.GameEventCardDamaged.DamageType; -import forge.game.event.GameEventCardAttachment; -import forge.game.event.GameEventCardCounters; -import forge.game.event.GameEventCardPhased; -import forge.game.event.GameEventCardStatsChanged; -import forge.game.event.GameEventCardTapped; -import forge.game.player.Player; -import forge.game.replacement.ReplaceMoved; -import forge.game.replacement.ReplacementEffect; -import forge.game.replacement.ReplacementResult; -import forge.game.spellability.Ability; -import forge.game.spellability.AbilityTriggered; -import forge.game.spellability.OptionalCost; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.SpellPermanent; -import forge.game.spellability.TargetRestrictions; -import forge.game.staticability.StaticAbility; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerType; -import forge.game.trigger.ZCTrigger; -import forge.game.zone.Zone; -import forge.game.zone.ZoneType; -import forge.item.IPaperCard; -import forge.item.PaperCard; -import forge.util.Expressions; -import forge.util.Lang; -import forge.util.MyRandom; -import forge.util.TextUtil; - -/** - *

- * Card class. - *

- * - * Can now be used as keys in Tree data structures. The comparison is based - * entirely on getUniqueNumber(). - * - * @author Forge - * @version $Id: Card.java 24341 2014-01-18 06:15:53Z swordshine $ - */ -public class Card extends GameEntity implements Comparable { - private final int uniqueNumber; - - private final Map characteristicsMap - = new EnumMap(CardCharacteristicName.class); - private CardCharacteristicName curCharacteristics = CardCharacteristicName.Original; - private CardCharacteristicName preTFDCharacteristic = CardCharacteristicName.Original; - - private ZoneType castFrom = null; - - private final CardDamageHistory damageHistory = new CardDamageHistory(); - private Map counters = new TreeMap(); - private Map> countersAddedBy = new TreeMap>(); - private ArrayList extrinsicKeyword = new ArrayList(); - // Hidden keywords won't be displayed on the card - private final ArrayList hiddenExtrinsicKeyword = new ArrayList(); - - // which equipment cards are equipping this card? - private ArrayList equippedBy = new ArrayList(); - // equipping size will always be 0 or 1 - // if this card is of the type equipment, what card is it currently equipping? - private ArrayList equipping = new ArrayList(); - - // which fortification cards are fortifying this card? - private ArrayList fortifiedBy = new ArrayList(); - // fortifying size will always be 0 or 1 - // if this card is of the type fortification, what card is it currently fortifying? - private ArrayList fortifying = new ArrayList(); - - // which auras enchanted this card? - // if this card is an Aura, what Entity is it enchanting? - private GameEntity enchanting = null; - - // changes by AF animate and continuous static effects - timestamp is the key of maps - private Map changedCardTypes = new ConcurrentSkipListMap(); - private Map changedCardKeywords = new ConcurrentSkipListMap(); - - private final ArrayList rememberedObjects = new ArrayList(); - private final ArrayList imprintedCards = new ArrayList(); - private final ArrayList encodedCards = new ArrayList(); - private final List devouredCards = new ArrayList(); - private Map flipResult = new TreeMap(); - - private Map receivedDamageFromThisTurn = new TreeMap(); - private Map dealtDamageToThisTurn = new TreeMap(); - private Map dealtDamageToPlayerThisTurn = new TreeMap(); - private final Map assignedDamageMap = new TreeMap(); - private List blockedThisTurn = null; - private List blockedByThisTurn = null; - - private boolean isCommander = false; - private boolean startsGameInPlay = false; - private boolean drawnThisTurn = false; - private boolean becameTargetThisTurn = false; - private boolean startedTheTurnUntapped = false; - private boolean tapped = false; - private boolean sickness = true; // summoning sickness - private boolean token = false; - private boolean copiedToken = false; - private boolean copiedSpell = false; - - private ArrayList mustBlockCards = null; - - private boolean canCounter = true; - private boolean evoked = false; - - private boolean unearthed; - - private boolean monstrous = false; - private int monstrosityNum = 0; - - private long bestowTimestamp = -1; - private boolean suspendCast = false; - private boolean suspend = false; - private boolean tributed = false; - - private boolean phasedOut = false; - private boolean directlyPhasedOut = true; - - private boolean usedToPayCost = false; - - // for Vanguard / Manapool / Emblems etc. - private boolean isImmutable = false; - - private long timestamp = -1; // permanents on the battlefield - - // stack of set power/toughness - private ArrayList newPT = new ArrayList(); - private int baseLoyalty = 0; - private String baseAttackString = null; - private String baseDefenseString = null; - - private int damage; - - // regeneration - private List nShield = new ArrayList(); - private int regeneratedThisTurn = 0; - - private int turnInZone; - - private int tempAttackBoost = 0; - private int tempDefenseBoost = 0; - - private int semiPermanentAttackBoost = 0; - private int semiPermanentDefenseBoost = 0; - - private int xManaCostPaid = 0; - - private int sunburstValue = 0; - private byte colorsPaid = 0; - - private Player owner = null; - private Player controller = null; - private long controllerTimestamp = 0; - private TreeMap tempControllers = new TreeMap(); - - private String text = ""; - private String echoCost = ""; - private Cost miracleCost = null; - private String chosenType = ""; - private List chosenColor = new ArrayList(); - private String namedCard = ""; - private int chosenNumber; - private Player chosenPlayer; - private List chosenCard = new ArrayList(); - - private Card cloneOrigin = null; - private final List clones = new ArrayList(); - private final List gainControlTargets = new ArrayList(); - - private final List zcTriggers = new ArrayList(); - private final List untapCommandList = new ArrayList(); - private final List changeControllerCommandList = new ArrayList(); - private final List staticCommandList = new ArrayList(); - - private final static ImmutableList storableSVars = ImmutableList.of("ChosenX"); - - private final List hauntedBy = new ArrayList(); - private Card haunting = null; - private Card effectSource = null; - - // Soulbond pairing card - private Card pairedWith = null; - - // Zone-changing spells should store card's zone here - private Zone currentZone = null; - - private int countersAdded = 0; - - // Enumeration for CMC request types - public enum SplitCMCMode { - CurrentSideCMC, - CombinedCMC, - LeftSplitCMC, - RightSplitCMC - } - - /** - * Instantiates a new card. - */ - public Card(int id) { - this.uniqueNumber = id; - this.characteristicsMap.put(CardCharacteristicName.Original, new CardCharacteristics()); - this.characteristicsMap.put(CardCharacteristicName.FaceDown, CardUtil.getFaceDownCharacteristic()); - } - - /** - * Sets the state. - * - * @param state - * the state - * @return true, if successful - */ - public boolean changeToState(final CardCharacteristicName state) { - - CardCharacteristicName cur = this.curCharacteristics; - - if (!setState(state)) { - return false; - } - - if ((cur == CardCharacteristicName.Original && state == CardCharacteristicName.Transformed) - || (cur == CardCharacteristicName.Transformed && state == CardCharacteristicName.Original)) { - HashMap runParams = new HashMap(); - runParams.put("Transformer", this); - getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false); - } - - return true; - } - - /** - * Sets the state. - * - * @param state - * the state - * @return true, if successful - */ - public boolean setState(final CardCharacteristicName state) { - if (state == CardCharacteristicName.FaceDown && this.isDoubleFaced()) { - return false; // Doublefaced cards can't be turned face-down. - } - - if (!this.characteristicsMap.containsKey(state)) { - System.out.println(this.getName() + " tried to switch to non-existant state \"" + state + "\"!"); - return false; // Nonexistant state. - } - - if (state.equals(this.curCharacteristics)) { - return false; - } - - this.curCharacteristics = state; - - return true; - } - - /** - * Gets the states. - * - * @return the states - */ - public Set getStates() { - return this.characteristicsMap.keySet(); - } - - /** - * Gets the cur state. - * - * @return the cur state - */ - public CardCharacteristicName getCurState() { - return this.curCharacteristics; - } - - /** - * Switch states. - * - * @param from - * the from - * @param to - * the to - */ - public void switchStates(final CardCharacteristicName from, final CardCharacteristicName to) { - final CardCharacteristics tmp = this.characteristicsMap.get(from); - this.characteristicsMap.put(from, this.characteristicsMap.get(to)); - this.characteristicsMap.put(to, tmp); - } - - /** - * Clear states. - * - * @param state - * the state - */ - public void clearStates(final CardCharacteristicName state) { - this.characteristicsMap.remove(state); - } - - public void setPreFaceDownCharacteristic(CardCharacteristicName preCharacteristic) { - this.preTFDCharacteristic = preCharacteristic; - } - - /** - * Turn face down. - * - * @return true, if successful - */ - public boolean turnFaceDown() { - if (!this.isDoubleFaced()) { - this.preTFDCharacteristic = this.curCharacteristics; - return this.setState(CardCharacteristicName.FaceDown); - } - - return false; - } - - /** - * Turn face up. - * - * @return true, if successful - */ - public boolean turnFaceUp() { - if (this.curCharacteristics == CardCharacteristicName.FaceDown) { - boolean result = this.setState(this.preTFDCharacteristic); - if (result) { - // Run replacement effects - HashMap repParams = new HashMap(); - repParams.put("Event", "TurnFaceUp"); - repParams.put("Affected", this); - getGame().getReplacementHandler().run(repParams); - - // Run triggers - final Map runParams = new TreeMap(); - runParams.put("Card", this); - getGame().getTriggerHandler().runTrigger(TriggerType.TurnFaceUp, runParams, false); - } - return result; - } - - return false; - } - - /** - * Gets the state. - * - * @param state - * the state - * @return the state - */ - public CardCharacteristics getState(final CardCharacteristicName state) { - return this.characteristicsMap.get(state); - } - - /** - * Gets the characteristics. - * - * @return the characteristics - */ - public CardCharacteristics getCharacteristics() { - return this.characteristicsMap.get(this.curCharacteristics); - } - - /** - * addAlternateState. - * - * @param state - * the state - */ - public final void addAlternateState(final CardCharacteristicName state) { - this.characteristicsMap.put(state, new CardCharacteristics()); - } - - /* - * (non-Javadoc) - * - * @see forge.GameEntity#getName() - */ - @Override - public final String getName() { - return this.getCharacteristics().getName(); - } - - /* - * (non-Javadoc) - * - * @see forge.GameEntity#setName(java.lang.String) - */ - @Override - public final void setName(final String name0) { - this.getCharacteristics().setName(name0); - } - - /** - * - * isInAlternateState. - * - * @return boolean - */ - public final boolean isInAlternateState() { - return this.curCharacteristics != CardCharacteristicName.Original - && this.curCharacteristics != CardCharacteristicName.Cloned; - } - - /** - * - * hasAlternateState. - * - * @return boolean - */ - public final boolean hasAlternateState() { - return this.characteristicsMap.keySet().size() > 2; - } - - public final boolean isDoubleFaced() { - return characteristicsMap.containsKey(CardCharacteristicName.Transformed); - } - - public final boolean isFlipCard() { - return characteristicsMap.containsKey(CardCharacteristicName.Flipped); - } - - public final boolean isSplitCard() { - return characteristicsMap.containsKey(CardCharacteristicName.LeftSplit); - } - - /** - * Checks if is cloned. - * - * @return true, if is cloned - */ - public boolean isCloned() { - return characteristicsMap.containsKey(CardCharacteristicName.Cloner); - } - - /** - * - * TODO Write javadoc for this method. - * - * @return a String array - */ - public static List getStorableSVars() { - return Card.storableSVars; - } - - /** - * - * TODO Write javadoc for this method. - * - * @param c - * a Card object - */ - public final void addDevoured(final Card c) { - this.devouredCards.add(c); - } - - /** - * - * TODO Write javadoc for this method. - */ - public final void clearDevoured() { - this.devouredCards.clear(); - } - - /** - * - * TODO Write javadoc for this method. - * - * @return a List object - */ - public final List getDevoured() { - return this.devouredCards; - } - - /** - *

- * addRemembered. - *

- * - * @param o - * a {@link java.lang.Object} object. - */ - public final void addRemembered(final Object o) { - this.rememberedObjects.add(o); - } - - /** - *

- * removeRemembered. - *

- * - * @param o - * a {@link java.lang.Object} object. - */ - public final void removeRemembered(final Object o) { - this.rememberedObjects.remove(o); - } - - /** - *

- * getRemembered. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getRemembered() { - return this.rememberedObjects; - } - - /** - *

- * clearRemembered. - *

- */ - public final void clearRemembered() { - this.rememberedObjects.clear(); - } - - /** - *

- * addImprinted. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - */ - public final void addImprinted(final Card c) { - this.imprintedCards.add(c); - } - - /** - *

- * addImprinted. - *

- * - * @param list - * a {@link java.util.ArrayList} object. - */ - public final void addImprinted(final ArrayList list) { - this.imprintedCards.addAll(list); - } - - /** - * TODO: Write javadoc for this method. - */ - public final void removeImprinted(final Object o) { - this.imprintedCards.remove(o); - } - - /** - *

- * getImprinted. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getImprinted() { - return this.imprintedCards; - } - - /** - *

- * clearImprinted. - *

- */ - public final void clearImprinted() { - this.imprintedCards.clear(); - } - - /** - *

- * addEncoded. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - */ - public final void addEncoded(final Card c) { - this.encodedCards.add(c); - } - - /** - *

- * addEncoded. - *

- * - * @param list - * a {@link java.util.ArrayList} object. - */ - public final void addEncoded(final ArrayList list) { - this.encodedCards.addAll(list); - } - - /** - * TODO: Write javadoc for this method. - */ - public final void removeEncoded(final Object o) { - this.encodedCards.remove(o); - } - - /** - *

- * getEncoded. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getEncoded() { - return this.encodedCards; - } - - /** - *

- * clearEncoded. - *

- */ - public final void clearEncoded() { - this.encodedCards.clear(); - } - - /** - *

- * addFlipResult. - *

- * - * @param flipper The Player who flipped the coin. - * @param result The result of the coin flip as a String. - */ - public final void addFlipResult(final Player flipper, final String result) { - this.flipResult.put(flipper, result); - } - - /** - *

- * getFlipResult. - *

- * - * @param flipper The Player who flipped the coin. - * @return a String result - Heads or Tails. - */ - public final String getFlipResult(final Player flipper) { - return this.flipResult.get(flipper); - } - - /** - *

- * clearFlipResult. - *

- */ - public final void clearFlipResult() { - this.flipResult.clear(); - } - - /** - *

- * addTrigger. - *

- * - * @param t - * a {@link forge.game.trigger.Trigger} object. - * @return a {@link forge.game.trigger.Trigger} object. - */ - public final Trigger addTrigger(final Trigger t) { - final Trigger newtrig = t.getCopyForHostCard(this); - this.getCharacteristics().getTriggers().add(newtrig); - return newtrig; - } - - /** - * - * moveTrigger. - * - * @param t - * a Trigger - */ - public final void moveTrigger(final Trigger t) { - t.setHostCard(this); - if (!this.getCharacteristics().getTriggers().contains(t)) { - this.getCharacteristics().getTriggers().add(t); - } - } - - /** - *

- * removeTrigger. - *

- * - * @param t - * a {@link forge.game.trigger.Trigger} object. - */ - public final void removeTrigger(final Trigger t) { - this.getCharacteristics().getTriggers().remove(t); - } - - /** - *

- * removeTrigger. - *

- * - * @param t - * a {@link forge.game.trigger.Trigger} object. - * - * @param state - * a {@link forge.card.CardCharacteristicName} object. - */ - public final void removeTrigger(final Trigger t, final CardCharacteristicName state) { - CardCharacteristics stateCharacteristics = this.getState(state); - stateCharacteristics.getTriggers().remove(t); - } - - /** - *

- * Getter for the field triggers. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final List getTriggers() { - return this.getCharacteristics().getTriggers(); - } - - /** - *

- * Setter for the field triggers. - *

- * - * @param trigs - * a {@link java.util.ArrayList} object. - */ - public final void setTriggers(final List trigs, boolean intrinsicOnly) { - final List copyList = new CopyOnWriteArrayList(); - for (final Trigger t : trigs) { - if (!intrinsicOnly || t.isIntrinsic()) { - copyList.add(t.getCopyForHostCard(this)); - } - } - - this.getCharacteristics().setTriggers(copyList); - } - - /** - *

- * clearTriggersNew. - *

- */ - public final void clearTriggersNew() { - this.getCharacteristics().getTriggers().clear(); - } - - /** - *

- * getTriggeringObject. - *

- * - * @param typeIn - * a {@link java.lang.String} object. - * @return a {@link java.lang.Object} object. - */ - public final Object getTriggeringObject(final String typeIn) { - Object triggered = null; - if (!this.getCharacteristics().getTriggers().isEmpty()) { - for (final Trigger t : this.getCharacteristics().getTriggers()) { - final SpellAbility sa = t.getTriggeredSA(); - triggered = sa.getTriggeringObject(typeIn); - if (triggered != null) { - break; - } - } - } - return triggered; - } - - /** - *

- * Getter for the field sunburstValue. - *

- * - * @return a int. - */ - public final int getSunburstValue() { - return this.sunburstValue; - } - - // TODO: Append colors instead of replacing - public final void setColorsPaid(final byte s) { - this.colorsPaid = s; - } - - /** - *

- * Getter for the field colorsPaid. - *

- * - * @return a String. - */ - public final byte getColorsPaid() { - return this.colorsPaid; - } - - /** - *

- * Setter for the field sunburstValue. - *

- * - * @param valueIn - * a int. - */ - public final void setSunburstValue(final int valueIn) { - this.sunburstValue = valueIn; - } - - /** - *

- * addXManaCostPaid. - *

- * - * @param n - * a int. - */ - public final void addXManaCostPaid(final int n) { - this.xManaCostPaid += n; - } - - /** - *

- * Setter or the field xManaCostPaid. - *

- * - * @param n - * a int. - */ - public final void setXManaCostPaid(final int n) { - this.xManaCostPaid = n; - } - - /** - *

- * Getter for the field xManaCostPaid. - *

- * - * @return a int. - */ - public final int getXManaCostPaid() { - return this.xManaCostPaid; - } - - /** - * @return the blockedThisTurn - */ - public List getBlockedThisTurn() { - return blockedThisTurn; - } - - /** - * @param attacker the blockedThisTurn to set - */ - public void addBlockedThisTurn(Card attacker) { - if (this.blockedThisTurn == null) { - this.blockedThisTurn = new ArrayList(); - } - this.blockedThisTurn.add(attacker); - } - - /** - *

- * clearBlockedThisTurn. - *

- */ - public void clearBlockedThisTurn() { - this.blockedThisTurn = null; - } - - /** - * @return the blockedByThisTurn - */ - public List getBlockedByThisTurn() { - return blockedByThisTurn; - } - - /** - * @param blocker the blockedByThisTurn to set - */ - public void addBlockedByThisTurn(Card blocker) { - if (this.blockedByThisTurn == null) { - this.blockedByThisTurn = new ArrayList(); - } - this.blockedByThisTurn.add(blocker); - } - - /** - *

- * clearBlockedByThisTurn. - *

- */ - public void clearBlockedByThisTurn() { - this.blockedByThisTurn = null; - } - - /** - * a Card that this Card must block if able in an upcoming combat. This is - * cleared at the end of each turn. - * - * @param c - * Card to block - * - * @since 1.1.6 - */ - public final void addMustBlockCard(final Card c) { - if (mustBlockCards == null) { - mustBlockCards = new ArrayList(); - } - this.mustBlockCards.add(c); - } - - public final void addMustBlockCards(final List attackersToBlock) { - if (mustBlockCards == null) { - mustBlockCards = new ArrayList(); - } - this.mustBlockCards.addAll(attackersToBlock); - } - - /** - * get the Card that this Card must block this combat. - * - * @return the Cards to block (if able) - * - * @since 1.1.6 - */ - public final ArrayList getMustBlockCards() { - return this.mustBlockCards; - } - - /** - * clear the list of Cards that this Card must block this combat. - * - * @since 1.1.6 - */ - public final void clearMustBlockCards() { - this.mustBlockCards = null; - } - - /** - *

- * Getter for the field clones. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final List getClones() { - return this.clones; - } - - /** - *

- * Setter for the field clones. - *

- * - * @param c - * a {@link java.util.ArrayList} object. - */ - public final void setClones(final Collection c) { - this.clones.clear(); - this.clones.addAll(c); - } - - /** - *

- * addClone. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - */ - public final void addClone(final Card c) { - this.clones.add(c); - } - - /** - *

- * clearClones. - *

- */ - public final void clearClones() { - this.clones.clear(); - } - - /** - *

- * Getter for the field cloneOrigin. - *

- * - * @return a {@link forge.game.card.Card} object. - */ - public final Card getCloneOrigin() { - return this.cloneOrigin; - } - - /** - *

- * Setter for the field cloneOrigin. - *

- * - * @param name - * a {@link forge.game.card.Card} object. - */ - public final void setCloneOrigin(final Card name) { - this.cloneOrigin = name; - } - - /** - *

- * hasFirstStrike. - *

- * - * @return a boolean. - */ - public final boolean hasFirstStrike() { - return this.hasKeyword("First Strike"); - } - - /** - *

- * hasDoubleStrike. - *

- * - * @return a boolean. - */ - public final boolean hasDoubleStrike() { - return this.hasKeyword("Double Strike"); - } - - /** - *

- * hasSecondStrike. - *

- * - * @return a boolean. - */ - public final boolean hasSecondStrike() { - return this.hasDoubleStrike() || !this.hasFirstStrike(); - } - - /** - * Can have counters placed on it. - * - * @param type - * the counter name - * @return true, if successful - */ - public final boolean canReceiveCounters(final CounterType type) { - if (this.hasKeyword("CARDNAME can't have counters placed on it.")) { - return false; - } - if (this.isCreature() && type == CounterType.M1M1) { - for (final Card c : this.getController().getCreaturesInPlay()) { // look for Melira, Sylvok Outcast - if (c.hasKeyword("Creatures you control can't have -1/-1 counters placed on them.")) { - return false; - } - } - } else if (type == CounterType.DREAM) { - if (this.hasKeyword("CARDNAME can't have more than seven dream counters on it.") && this.getCounters(CounterType.DREAM) > 6) { - return false; - } - } - return true; - } - - public final int getTotalCountersToAdd() { - return countersAdded; - } - - public final void setTotalCountersToAdd(int value) { - countersAdded = value; - } - - public final void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier) { - int addAmount = n; - final HashMap repParams = new HashMap(); - repParams.put("Event", "AddCounter"); - repParams.put("Affected", this); - repParams.put("CounterType", counterType); - repParams.put("CounterNum", addAmount); - repParams.put("EffectOnly", applyMultiplier); - if (this.getGame().getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { - return; - } - if (this.canReceiveCounters(counterType)) { - if (counterType == CounterType.DREAM && this.hasKeyword("CARDNAME can't have more than seven dream counters on it.")) { - addAmount = Math.min(7 - this.getCounters(CounterType.DREAM), addAmount); - } - } else { - addAmount = 0; - } - - if (addAmount == 0) { - return; - } - this.setTotalCountersToAdd(addAmount); - - Integer oldValue = this.counters.get(counterType); - int newValue = addAmount + (oldValue == null ? 0 : oldValue.intValue()); - this.counters.put(counterType, Integer.valueOf(newValue)); - - // play the Add Counter sound - getGame().fireEvent(new GameEventCardCounters(this, counterType, oldValue == null ? 0 : oldValue.intValue(), newValue)); - - // Run triggers - final Map runParams = new TreeMap(); - runParams.put("Card", this); - runParams.put("CounterType", counterType); - for (int i = 0; i < addAmount; i++) { - getGame().getTriggerHandler().runTrigger(TriggerType.CounterAdded, runParams, false); - } - } - - /** - *

- * addCountersAddedBy. - *

- * @param source the card adding the counters to this card - * @param counterType the counter type added - * @param counterAmount the amount of counters added - */ - public final void addCountersAddedBy(final Card source, final CounterType counterType, final int counterAmount) { - final Map counterMap = new TreeMap(); - counterMap.put(counterType, counterAmount); - this.countersAddedBy.put(source, counterMap); - } - - /** - *

- * getCountersAddedBy. - *

- * @param source the card the counters were added by - * @param counterType the counter type added - * @return the amount of counters added. - */ - public final int getCountersAddedBy(final Card source, final CounterType counterType) { - int counterAmount = 0; - if (this.countersAddedBy.containsKey(source)) { - final Map counterMap = this.countersAddedBy.get(source); - counterAmount = counterMap.containsKey(counterType) ? counterMap.get(counterType) : 0; - this.countersAddedBy.remove(source); - } - return counterAmount; - } - - /** - *

- * subtractCounter. - *

- * - * @param counterName - * a {@link forge.game.card.CounterType} object. - * @param n - * a int. - */ - public final void subtractCounter(final CounterType counterName, final int n) { - Integer oldValue = this.counters.get(counterName); - int newValue = oldValue == null ? 0 : Math.max(oldValue.intValue() - n, 0); - - final int delta = (oldValue == null ? 0 : oldValue.intValue()) - newValue; - if (delta == 0) { - return; - } - - if (newValue > 0) { - this.counters.put(counterName, Integer.valueOf(newValue)); - } else { - this.counters.remove(counterName); - } - - // Play the Subtract Counter sound - getGame().fireEvent(new GameEventCardCounters(this, counterName, oldValue == null ? 0 : oldValue.intValue(), newValue)); - - // Run triggers - int curCounters = oldValue == null ? 0 : oldValue.intValue(); - for (int i = 0; i < delta && curCounters != 0; i++) { - final Map runParams = new TreeMap(); - runParams.put("Card", this); - runParams.put("CounterType", counterName); - runParams.put("NewCounterAmount", --curCounters); - getGame().getTriggerHandler().runTrigger(TriggerType.CounterRemoved, runParams, false); - } - } - - /** - *

- * Getter for the field counters. - *

- * - * @param counterName - * a {@link forge.game.card.CounterType} object. - * @return a int. - */ - public final int getCounters(final CounterType counterName) { - Integer value = this.counters.get(counterName); - return value == null ? 0 : value.intValue(); - } - - // get all counters from a card - /** - *

- * Getter for the field counters. - *

- * - * @return a Map object. - * @since 1.0.15 - */ - public final Map getCounters() { - return this.counters; - } - - /** - *

- * hasCounters. - *

- * - * @return a boolean. - */ - public final boolean hasCounters() { - return !this.counters.isEmpty(); - } - - /** - *

- * Setter for the field counters. - *

- * - * @param allCounters - * a Map object. - * @since 1.0.15 - */ - public final void setCounters(final Map allCounters) { - this.counters = allCounters; - } - - /** - *

- * clearCounters. - *

- * - * @since 1.0.15 - */ - public final void clearCounters() { - this.counters.clear(); - } - - /** - *

- * getSVar. - *

- * - * @param var - * a {@link java.lang.String} object. - * @return a {@link java.lang.String} object. - */ - public final String getSVar(final String var) { - return this.getCharacteristics().getSVar(var); - } - - /** - *

- * hasSVar. - *

- * - * @param var - * a {@link java.lang.String} object. - */ - public final boolean hasSVar(final String var) { - return this.getCharacteristics().hasSVar(var); - } - - /** - *

- * setSVar. - *

- * - * @param var - * a {@link java.lang.String} object. - * @param str - * a {@link java.lang.String} object. - */ - public final void setSVar(final String var, final String str) { - this.getCharacteristics().setSVar(var, str); - } - - /** - *

- * getSVars. - *

- * - * @return a Map object. - */ - public final Map getSVars() { - return this.getCharacteristics().getSVars(); - } - - /** - *

- * setSVars. - *

- * - * @param newSVars - * a Map object. - */ - public final void setSVars(final Map newSVars) { - this.getCharacteristics().setSVars(newSVars); - } - - /** - *

- * sumAllCounters. - *

- * - * @return a int. - */ - public final int sumAllCounters() { - int count = 0; - for (final Integer value2 : this.counters.values()) { - count += value2.intValue(); - } - return count; - } - - - /** - *

- * Getter for the field turnInZone. - *

- * - * @return a int. - */ - public final int getTurnInZone() { - return this.turnInZone; - } - - /** - *

- * Setter for the field turnInZone. - *

- * - * @param turn - * a int. - */ - public final void setTurnInZone(final int turn) { - this.turnInZone = turn; - } - - /** - *

- * Setter for the field echoCost. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void setEchoCost(final String s) { - this.echoCost = s; - } - - public final String getEchoCost() { - return this.echoCost; - } - - /** - *

- * Setter for the field manaCost. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void setManaCost(final ManaCost s) { - this.getCharacteristics().setManaCost(s); - } - - /** - *

- * Getter for the field manaCost. - *

- * - * @return a {@link java.lang.String} object. - */ - public final ManaCost getManaCost() { - return this.getCharacteristics().getManaCost(); - } - - - /** - *

- * addColor. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void addColor(String s) { - if (s.equals("")) { - s = "0"; - } - ManaCost mc = new ManaCost(new ManaCostParser(s)); - this.getCharacteristics().getCardColor().add(new CardColor(mc.getColorProfile())); - } - - /** - *

- * addColor. - *

- * - * @param s - * a {@link java.lang.String} object. - * @param c - * a {@link forge.game.card.Card} object. - * @param addToColors - * a boolean. - * @param bIncrease - * a boolean. - * @return a long. - */ - public final long addColor(final String s, final boolean addToColors, final boolean bIncrease) { - if (bIncrease) { - CardColor.increaseTimestamp(); - } - this.getCharacteristics().getCardColor().add(new CardColor(s, addToColors)); - return CardColor.getTimestamp(); - } - - /** - *

- * removeColor. - *

- * - * @param s - * a {@link java.lang.String} object. - * @param c - * a {@link forge.game.card.Card} object. - * @param addTo - * a boolean. - * @param timestampIn - * a long. - */ - public final void removeColor(final String s, final Card c, final boolean addTo, final long timestampIn) { - CardColor removeCol = null; - for (final CardColor cc : this.getCharacteristics().getCardColor()) { - if (cc.equals(s, c, addTo, timestampIn)) { - removeCol = cc; - } - } - - if (removeCol != null) { - this.getCharacteristics().getCardColor().remove(removeCol); - } - } - - /** - *

- * setColor. - *

- * - * @param colors - * a {@link java.util.ArrayList} object. - */ - public final void setColor(final Iterable colors) { - this.getCharacteristics().setCardColor(colors); - } - - /** - *

- * getColor. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final List getColor() { - return this.getCharacteristics().getCardColor(); - } - - /** - * - * TODO Write javadoc for this method. - * - * @param globalChanges - * an ArrayList - * @return a CardColor - */ - public final ColorSet determineColor() { - if (this.isImmutable()) { - return ColorSet.getNullColor(); - } - - List colorList = this.getCharacteristics().getCardColor(); - - byte colors = 0; - for (int i = colorList.size() - 1;i >= 0;i--) { - final CardColor cc = colorList.get(i); - colors |= cc.getColorMask(); - if (!cc.isAdditional()) { - return ColorSet.fromMask(colors); - } - } - return ColorSet.fromMask(colors); - } - - /** - *

- * Getter for the field chosenPlayer. - *

- * - * @return a Player - * @since 1.1.6 - */ - public final Player getChosenPlayer() { - return this.chosenPlayer; - } - - /** - *

- * Setter for the field chosenNumber. - *

- * - * @param p - * an int - * @since 1.1.6 - */ - public final void setChosenPlayer(final Player p) { - this.chosenPlayer = p; - } - - /** - *

- * Getter for the field chosenNumber. - *

- * - * @return an int - */ - public final int getChosenNumber() { - return this.chosenNumber; - } - - /** - *

- * Setter for the field chosenNumber. - *

- * - * @param i - * an int - */ - public final void setChosenNumber(final int i) { - this.chosenNumber = i; - } - - // used for cards like Belbe's Portal, Conspiracy, Cover of Darkness, etc. - /** - *

- * Getter for the field chosenType. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getChosenType() { - return this.chosenType; - } - - /** - *

- * Setter for the field chosenType. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void setChosenType(final String s) { - this.chosenType = s; - } - - /** - *

- * Getter for the field chosenColor. - *

- * - * @return an ArrayList object. - */ - public final List getChosenColor() { - return this.chosenColor; - } - - /** - *

- * Setter for the field chosenColor. - *

- * - * @param s - * an ArrayList object. - */ - public final void setChosenColor(final List s) { - this.chosenColor = s; - } - - /** - *

- * Getter for the field chosenCard. - *

- * - * @return an ArrayList object. - */ - public final List getChosenCard() { - return this.chosenCard; - } - - /** - *

- * Setter for the field chosenCard. - *

- * - * @param c - * an ArrayList object. - */ - public final void setChosenCard(final List c) { - this.chosenCard = c; - } - - // used for cards like Meddling Mage... - /** - *

- * Getter for the field namedCard. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getNamedCard() { - return this.namedCard; - } - - /** - *

- * Setter for the field namedCard. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void setNamedCard(final String s) { - this.namedCard = s; - } - - /** - *

- * Setter for the field drawnThisTurn. - *

- * - * @param b - * a boolean. - */ - public final void setDrawnThisTurn(final boolean b) { - this.drawnThisTurn = b; - } - - /** - *

- * Getter for the field drawnThisTurn. - *

- * - * @return a boolean. - */ - public final boolean getDrawnThisTurn() { - return this.drawnThisTurn; - } - - /** - * get a list of Cards this card has gained control of. - *

- * used primarily with AbilityFactory_GainControl - * - * @return a list of cards this card has gained control of - */ - public final List getGainControlTargets() { - return this.gainControlTargets; - } - - /** - * add a Card to the list of Cards this card has gained control of. - *

- * used primarily with AbilityFactory_GainControl - * - * @param c - * a {@link forge.game.card.Card} object. - */ - public final void addGainControlTarget(final Card c) { - this.gainControlTargets.add(c); - } - - /** - * clear the list of Cards this card has gained control of. - *

- * used primarily with AbilityFactory_GainControl - */ - public final void removeGainControlTargets(final Card c) { - this.gainControlTargets.remove(c); - } - - /** - *

- * getSpellText. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getSpellText() { - return this.text; - } - - /** - *

- * Setter for the field text. - *

- * - * @param t - * a {@link java.lang.String} object. - */ - public final void setText(final String t) { - this.text = t; - } - - // get the text that should be displayed - /** - *

- * Getter for the field text. - *

- * - * @return a {@link java.lang.String} object. - */ - public String getText() { - final StringBuilder sb = new StringBuilder(); - - // Vanguard Modifiers - if (this.isType("Vanguard")) { - sb.append("Hand Modifier: ").append(getRules().getHand()); - sb.append("\r\nLife Modifier: ").append(getRules().getLife()); - sb.append("\r\n\r\n"); - } - if (this.isCommander) - { - sb.append(getOwner() + "'s Commander\r\n\r\n"); - } - if (this.getName().equals("Commander effect")) - { - sb.append("Zone: " + getOwner().getCommander().getZone().toString() + "\r\n"); - sb.append(CardFactoryUtil.getCommanderInfo(getOwner())); - } - sb.append(this.getAbilityText()); - - String nonAbilityText = this.getNonAbilityText(); - if (this.getAmountOfKeyword("CARDNAME can block an additional creature.") > 1) { - final StringBuilder ab = new StringBuilder(); - ab.append("CARDNAME can block an additional "); - ab.append(this.getAmountOfKeyword("CARDNAME can block an additional creature.")); - ab.append(" creatures."); - nonAbilityText = nonAbilityText.replaceFirst("CARDNAME can block an additional creature.", ab.toString()); - nonAbilityText = nonAbilityText.replaceAll("CARDNAME can block an additional creature.", ""); - nonAbilityText = nonAbilityText.replaceAll("\r\n\r\n\r\n", ""); - } - if (nonAbilityText.length() > 0) { - sb.append("\r\n \r\nNon ability features: \r\n"); - sb.append(nonAbilityText.replaceAll("CARDNAME", this.getName())); - } - - // Remembered cards - if (this.rememberedObjects.size() > 0) { - sb.append("\r\nRemembered: \r\n"); - for (final Object o : this.rememberedObjects) { - if (o instanceof Card) { - final Card c = (Card) o; - if (c.isFaceDown()) { - sb.append("Face Down "); - } else { - sb.append(c.getName()); - } - sb.append("("); - sb.append(c.getUniqueNumber()); - sb.append(")"); - } else if (o != null) { - sb.append(o.toString()); - } - sb.append("\r\n"); - } - } - - if (this.chosenPlayer != null) { - sb.append("\r\n[Chosen player: "); - sb.append(this.getChosenPlayer()); - sb.append("]\r\n"); - } - - if (this.hauntedBy.size() != 0) { - sb.append("Haunted by: "); - for (final Card c : this.hauntedBy) { - sb.append(c).append(","); - } - sb.deleteCharAt(sb.length() - 1); - sb.append("\r\n"); - } - - if (this.haunting != null) { - sb.append("Haunting: ").append(this.haunting); - sb.append("\r\n"); - } - - if (this.pairedWith != null) { - sb.append("\r\n \r\nPaired With: ").append(this.pairedWith); - sb.append("\r\n"); - } - - if (this.characteristicsMap.get(CardCharacteristicName.Cloner) != null) { - sb.append("\r\nCloned by: ").append(this.characteristicsMap.get(CardCharacteristicName.Cloner).getName()).append(" (") - .append(this.getUniqueNumber()).append(")"); - } - - return sb.toString(); - } - - // get the text that does not belong to a cards abilities (and is not really - // there rules-wise) - /** - *

- * getNonAbilityText. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getNonAbilityText() { - final StringBuilder sb = new StringBuilder(); - final ArrayList keyword = this.getHiddenExtrinsicKeyword(); - - sb.append(this.keywordsToText(keyword)); - - return sb.toString(); - } - - // convert a keyword list to the String that should be displayed ingame - /** - *

- * keywordsToText. - *

- * - * @param keywords - * a {@link java.util.ArrayList} object. - * @return a {@link java.lang.String} object. - */ - public final String keywordsToText(final ArrayList keywords) { - final StringBuilder sb = new StringBuilder(); - final StringBuilder sbLong = new StringBuilder(); - final StringBuilder sbMana = new StringBuilder(); - - for (int i = 0; i < keywords.size(); i++) { - String keyword = keywords.get(i); - if (keyword.startsWith("Permanents don't untap during their controllers' untap steps") - || keyword.startsWith("PreventAllDamageBy") - || keyword.startsWith("CantBlock") - || keyword.startsWith("CantEquip") - || keyword.startsWith("SpellCantTarget")) { - continue; - } - if (keyword.startsWith("etbCounter")) { - final String[] p = keyword.split(":"); - final StringBuilder s = new StringBuilder(); - if (p.length > 4) { - s.append(p[4]); - } else { - final CounterType counter = CounterType.valueOf(p[1]); - final String numCounters = p[2]; - s.append(this.getName()); - s.append(" enters the battlefield with "); - s.append(numCounters); - s.append(" "); - s.append(counter.getName()); - s.append(" counter"); - if ("1" != numCounters) { - s.append("s"); - } - s.append(" on it."); - } - sbLong.append(s).append("\r\n"); - } else if (keyword.startsWith("Protection:")) { - final String[] k = keyword.split(":"); - sbLong.append(k[2]).append("\r\n"); - } else if (keyword.startsWith("Creatures can't attack unless their controller pays")) { - final String[] k = keyword.split(":"); - if (!k[3].equals("no text")) { - sbLong.append(k[3]).append("\r\n"); - } - } else if (keyword.startsWith("Enchant")) { - String k = keyword; - k = k.replace("Curse", ""); - sbLong.append(k).append("\r\n"); - } else if (keyword.startsWith("Fading") || keyword.startsWith("Ripple") || keyword.startsWith("Vanishing")) { - sbLong.append(keyword.replace(":", " ")).append("\r\n"); - } else if (keyword.startsWith("Unearth") || keyword.startsWith("Madness")) { - String[] parts = keyword.split(":"); - sbLong.append(parts[0] + " " + ManaCostParser.parse(parts[1])).append("\r\n"); - } else if (keyword.startsWith("Devour")) { - final String[] parts = keyword.split(":"); - final String extra = parts.length > 2 ? parts[2] : ""; - final String devour = "Devour " + parts[1] + extra; - sbLong.append(devour).append("\r\n"); - } else if (keyword.startsWith("Morph")) { - sbLong.append("Morph"); - if (keyword.contains(":")) { - final Cost mCost = new Cost(keyword.substring(6), true); - if (!mCost.isOnlyManaCost()) { - sbLong.append(" -"); - } - sbLong.append(" ").append(mCost.toString()).delete(sbLong.length() - 2, sbLong.length()); - if (!mCost.isOnlyManaCost()) { - sbLong.append("."); - } - sbLong.append("\r\n"); - } - } else if (keyword.startsWith("Echo")) { - sbLong.append("Echo "); - final String[] upkeepCostParams = keyword.split(":"); - sbLong.append(upkeepCostParams.length > 2 ? "- " + upkeepCostParams[2] : ManaCostParser.parse(upkeepCostParams[1])); - sbLong.append("\r\n"); - } else if (keyword.startsWith("Cumulative upkeep")) { - sbLong.append("Cumulative upkeep "); - final String[] upkeepCostParams = keyword.split(":"); - sbLong.append(upkeepCostParams.length > 2 ? "- " + upkeepCostParams[2] : ManaCostParser.parse(upkeepCostParams[1])); - sbLong.append("\r\n"); - } else if (keyword.startsWith("Amplify")) { - sbLong.append("Amplify "); - final String[] ampParams = keyword.split(":"); - final String magnitude = ampParams[1]; - sbLong.append(magnitude); - sbLong.append("(As this creature enters the battlefield, put a +1/+1 counter on it for each "); - sbLong.append(ampParams[2].replace(",", " and/or ")).append(" card you reveal in your hand.)"); - sbLong.append("\r\n"); - } else if (keyword.startsWith("Alternative Cost")) { - sbLong.append("Has alternative cost."); - } else if (keyword.startsWith("AlternateAdditionalCost")) { - final String costString1 = keyword.split(":")[1]; - final String costString2 = keyword.split(":")[2]; - final Cost cost1 = new Cost(costString1, false); - final Cost cost2 = new Cost(costString2, false); - sbLong.append("As an additional cost to cast " + this.getName() + ", " + cost1.toSimpleString() - + " or pay " + cost2.toSimpleString() + ".\r\n"); - } else if (keyword.startsWith("Kicker")) { - final Cost cost = new Cost(keyword.substring(7), false); - sbLong.append("Kicker " + cost.toSimpleString() + "\r\n"); - } else if (keyword.endsWith(".") && !keyword.startsWith("Haunt")) { - sbLong.append(keyword.toString()).append("\r\n"); - } else if (keyword.contains("At the beginning of your upkeep, ") - && keyword.contains(" unless you pay")) { - sbLong.append(keyword.toString()).append("\r\n"); - } else if (keyword.toString().contains("tap: add ")) { - sbMana.append(keyword.toString()).append("\r\n"); - } else if (keyword.startsWith("Modular") || keyword.startsWith("Soulshift") || keyword.startsWith("Bloodthirst") - || keyword.startsWith("ETBReplacement") || keyword.startsWith("MayEffectFromOpeningHand")) { - continue; - } else if (keyword.startsWith("Provoke")) { - sbLong.append(keyword); - sbLong.append(" (When this attacks, you may have target creature "); - sbLong.append("defending player controls untap and block it if able.)"); - } else if (keyword.contains("Haunt")) { - sb.append("\r\nHaunt ("); - if (this.isCreature()) { - sb.append("When this creature dies, exile it haunting target creature."); - } else { - sb.append("When this spell card is put into a graveyard after resolving, "); - sb.append("exile it haunting target creature."); - } - sb.append(")"); - continue; - } else if (keyword.equals("Convoke")) { - if (sb.length() != 0) { - sb.append("\r\n"); - } - sb.append("Convoke (Each creature you tap while casting this spell reduces its cost by 1 or by one mana of that creature's color.)"); - } else if (keyword.endsWith(" offering")) { - String offeringType = keyword.split(" ")[0]; - if (sb.length() != 0) { - sb.append("\r\n"); - } - sbLong.append(keyword); - sbLong.append(" (You may cast this card any time you could cast an instant by sacrificing a "); - sbLong.append(offeringType); - sbLong.append("and paying the difference in mana costs between this and the sacrificed "); - sbLong.append(offeringType); - sbLong.append(". Mana cost includes color.)"); - } else if (keyword.startsWith("Soulbond")) { - sbLong.append(keyword); - sbLong.append(" (You may pair this creature "); - sbLong.append("with another unpaired creature when either "); - sbLong.append("enters the battlefield. They remain paired for "); - sbLong.append("as long as you control both of them)"); - } else if (keyword.startsWith("Equip") || keyword.startsWith("Fortify")) { - // keyword parsing takes care of adding a proper description - continue; - } else if (keyword.startsWith("CantBeBlockedBy")) { - sbLong.append(this.getName()).append(" can't be blocked "); - if (keyword.startsWith("CantBeBlockedByAmount")) - sbLong.append(getTextForKwCantBeBlockedByAmount(keyword)); - else - sbLong.append(getTextForKwCantBeBlockedByType(keyword)); - } else if (keyword.equals("Unblockable")) { - sbLong.append(this.getName()).append(" can't be blocked.\r\n"); - } - else { - if ((i != 0) && (sb.length() != 0)) { - sb.append(", "); - } - sb.append(keyword); - } - } - if (sb.length() > 0) { - sb.append("\r\n"); - if (sbLong.length() > 0) { - sb.append("\r\n"); - } - } - if (sbLong.length() > 0) { - sbLong.append("\r\n"); - } - sb.append(sbLong); - sb.append(sbMana); - - return sb.toString(); - } - - private String getTextForKwCantBeBlockedByAmount(String keyword) { - String restriction = keyword.split(" ", 2)[1]; - boolean isLT = "LT".equals(restriction.substring(0,2)); - final String byClause = isLT ? "except by " : "by more than "; - int cnt = Integer.parseInt(restriction.substring(2)); - return byClause + Lang.nounWithNumeral(cnt, isLT ? "or more creature" : "creature"); - } - - private String getTextForKwCantBeBlockedByType(String keyword) { - boolean negative = true; - List subs = Lists.newArrayList(TextUtil.split(keyword.split(" ", 2)[1], ',')); - List> subsAnd = Lists.newArrayList(); - List orClauses = new ArrayList(); - for (int iOr = 0; iOr < subs.size(); iOr++) { - String expession = subs.get(iOr); - List parts = Lists.newArrayList(expession.split("[.+]")); - for (int p = 0; p < parts.size(); p++) { - String part = parts.get(p); - if (part.equalsIgnoreCase("creature")) { - parts.remove(p--); - continue; - } - // based on suppossition that each expression has at least 1 predicate except 'creature' - negative &= part.contains("non") || part.contains("without"); - } - subsAnd.add(parts); - } - - final boolean allNegative = negative; - final String byClause = allNegative ? "except by " : "by "; - - final Function, String> withToString = new Function, String>() { - @Override - public String apply(Pair inp) { - boolean useNon = inp.getKey().booleanValue() == allNegative; - return (useNon ? "*NO* " : "") + inp.getRight(); - } - }; - - for (int iOr = 0; iOr < subsAnd.size(); iOr++) { - List andOperands = subsAnd.get(iOr); - List> prependedAdjectives = Lists.newArrayList(); - List> postponedAdjectives = Lists.newArrayList(); - String creatures = null; - - for (String part : andOperands) { - boolean positive = true; - if (part.startsWith("non")) { - part = part.substring(3); - positive = false; - } - if (part.startsWith("with")) { - positive = !part.startsWith("without"); - postponedAdjectives.add(Pair.of(positive, part.substring(positive ? 4 : 7))); - } else if (part.startsWith("power")) { - int kwLength = 5; - String opName = Expressions.operatorName(part.substring(kwLength, kwLength + 2)); - String operand = part.substring(kwLength + 2); - postponedAdjectives.add(Pair.of(true, "power" + opName + operand)); - } else if (forge.card.CardType.isACreatureType(part)) { - creatures = StringUtils.capitalize(Lang.getPlural(part)) + (creatures == null ? "" : " or " + creatures); - } else { - prependedAdjectives.add(Pair.of(positive, part.toLowerCase())); - } - } - - StringBuilder sbShort = new StringBuilder(); - if (allNegative) { - boolean isFirst = true; - for (Pair pre : prependedAdjectives) { - if (isFirst) isFirst = false; - else sbShort.append(" and/or "); - - boolean useNon = pre.getKey().booleanValue() == allNegative; - if (useNon) sbShort.append("non-"); - sbShort.append(pre.getValue()).append(" ").append(creatures == null ? "creatures" : creatures); - } - if (prependedAdjectives.isEmpty()) - sbShort.append(creatures == null ? "creatures" : creatures); - - if (!postponedAdjectives.isEmpty()) { - if (!prependedAdjectives.isEmpty()) { - sbShort.append(" and/or creatures"); - } - - sbShort.append(" with "); - sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and")); - } - - } else { - for (Pair pre : prependedAdjectives) { - boolean useNon = pre.getKey().booleanValue() == allNegative; - if (useNon) sbShort.append("non-"); - sbShort.append(pre.getValue()).append(" "); - } - sbShort.append(creatures == null ? "creatures" : creatures); - - if (!postponedAdjectives.isEmpty()) { - sbShort.append(" with "); - sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and")); - } - - } - orClauses.add(sbShort.toString()); - } - return byClause + StringUtils.join(orClauses, " or "); - } - - // get the text of the abilities of a card - /** - *

- * getAbilityText. - *

- * - * @return a {@link java.lang.String} object. - */ - public String getAbilityText() { - if (this.isInstant() || this.isSorcery()) { - final StringBuilder sb = this.abilityTextInstantSorcery(); - - if (this.haunting != null) { - sb.append("Haunting: ").append(this.haunting); - sb.append("\r\n"); - } - - while (sb.toString().endsWith("\r\n")) { - sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); - } - - return sb.toString().replaceAll("CARDNAME", this.getName()); - } - - final StringBuilder sb = new StringBuilder(); - final ArrayList keyword = this.getUnhiddenKeyword(); - - if (this.monstrous) { - sb.append("Monstrous\r\n"); - } - sb.append(this.keywordsToText(keyword)); - - // Give spellText line breaks for easier reading - sb.append("\r\n"); - sb.append(this.text.replaceAll("\\\\r\\\\n", "\r\n")); - sb.append("\r\n"); - - // Triggered abilities - for (final Trigger trig : this.getCharacteristics().getTriggers()) { - if (!trig.isSecondary()) { - sb.append(trig.toString() + "\r\n"); - } - } - - // Replacement effects - for (final ReplacementEffect replacementEffect : this.getCharacteristics().getReplacementEffects()) { - if (!replacementEffect.isSecondary()) { - sb.append(replacementEffect.toString() + "\r\n"); - } - } - - // static abilities - for (final StaticAbility stAb : this.getCharacteristics().getStaticAbilities()) { - sb.append(stAb.toString() + "\r\n"); - } - - final ArrayList addedManaStrings = new ArrayList(); - boolean primaryCost = true; - for (final SpellAbility sa : this.getSpellAbilities()) { - // only add abilities not Spell portions of cards - if (!this.isPermanent()) { - continue; - } - - if ((sa instanceof SpellPermanent) && primaryCost && !this.isAura()) { - // For Alt costs, make sure to display the cost! - primaryCost = false; - continue; - } - - final String sAbility = sa.toString(); - - if (sa.getManaPart() != null) { - if (addedManaStrings.contains(sAbility)) { - continue; - } - addedManaStrings.add(sAbility); - } - - if ((sa instanceof SpellPermanent) && !this.isAura()) { - sb.insert(0, "\r\n"); - sb.insert(0, sAbility); - } else if (!sAbility.endsWith(this.getName())) { - sb.append(sAbility); - sb.append("\r\n"); - } - } - - // NOTE: - if (sb.toString().contains(" (NOTE: ")) { - sb.insert(sb.indexOf("(NOTE: "), "\r\n"); - } - if (sb.toString().contains("(NOTE: ") && sb.toString().contains(".) ")) { - sb.insert(sb.indexOf(".) ") + 3, "\r\n"); - } - - // replace triple line feeds with double line feeds - int start; - final String s = "\r\n\r\n\r\n"; - while (sb.toString().contains(s)) { - start = sb.lastIndexOf(s); - if ((start < 0) || (start >= sb.length())) { - break; - } - sb.replace(start, start + 4, "\r\n"); - } - - return sb.toString().replaceAll("CARDNAME", this.getName()).trim(); - } // getText() - - /** - * TODO: Write javadoc for this method. - * - * @return - */ - private StringBuilder abilityTextInstantSorcery() { - final String s = this.getSpellText(); - final StringBuilder sb = new StringBuilder(); - - // Give spellText line breaks for easier reading - sb.append(s.replaceAll("\\\\r\\\\n", "\r\n")); - - // NOTE: - if (sb.toString().contains(" (NOTE: ")) { - sb.insert(sb.indexOf("(NOTE: "), "\r\n"); - } - if (sb.toString().contains("(NOTE: ") && sb.toString().endsWith(".)") && !sb.toString().endsWith("\r\n")) { - sb.append("\r\n"); - } - - // Add SpellAbilities - for (final SpellAbility element : this.getSpellAbilities()) { - sb.append(element.toString() + "\r\n"); - } - - // Add Keywords - final List kw = this.getKeyword(); - - // Triggered abilities - for (final Trigger trig : this.getCharacteristics().getTriggers()) { - if (!trig.isSecondary()) { - sb.append(trig.toString() + "\r\n"); - } - } - - // Replacement effects - for (final ReplacementEffect replacementEffect : this.getCharacteristics().getReplacementEffects()) { - sb.append(replacementEffect.toString() + "\r\n"); - } - - // static abilities - for (final StaticAbility stAb : this.getCharacteristics().getStaticAbilities()) { - final String stAbD = stAb.toString(); - if (!stAbD.equals("")) { - sb.append(stAbD + "\r\n"); - } - } - - // keyword descriptions - for (int i = 0; i < kw.size(); i++) { - final String keyword = kw.get(i); - if ((keyword.startsWith("Ripple") && !sb.toString().contains("Ripple")) - || (keyword.startsWith("Dredge") && !sb.toString().contains("Dredge")) - || (keyword.startsWith("CARDNAME is ") && !sb.toString().contains("CARDNAME is "))) { - sb.append(keyword.replace(":", " ")).append("\r\n"); - } - else if ((keyword.startsWith("Madness") && !sb.toString().contains("Madness")) - || (keyword.startsWith("Recover") && !sb.toString().contains("Recover")) - || (keyword.startsWith("Miracle") && !sb.toString().contains("Miracle"))) { - String[] parts = keyword.split(":"); - sb.append(parts[0] + " " + ManaCostParser.parse(parts[1])).append("\r\n"); - } - else if (keyword.equals("CARDNAME can't be countered.") - || keyword.startsWith("May be played") || keyword.startsWith("Conspire") - || keyword.startsWith("Cascade") || keyword.startsWith("Wither") - || (keyword.startsWith("Epic") && !sb.toString().contains("Epic")) - || (keyword.startsWith("Split second") && !sb.toString().contains("Split second")) - || (keyword.startsWith("Multikicker") && !sb.toString().contains("Multikicker"))) { - sb.append(keyword).append("\r\n"); - } - else if (keyword.startsWith("Flashback")) { - sb.append("Flashback"); - if (keyword.contains(" ")) { - final Cost fbCost = new Cost(keyword.substring(10), true); - if (!fbCost.isOnlyManaCost()) { - sb.append(" -"); - } - sb.append(" " + fbCost.toString()).delete(sb.length() - 2, sb.length()); - if (!fbCost.isOnlyManaCost()) { - sb.append("."); - } - } - sb.append("\r\n"); - } else if (keyword.startsWith("Splice")) { - final Cost cost = new Cost(keyword.substring(19), false); - sb.append("Splice onto Arcane " + cost.toSimpleString() + "\r\n"); - } else if (keyword.startsWith("Buyback")) { - final Cost cost = new Cost(keyword.substring(8), false); - sb.append("Buyback " + cost.toSimpleString() + "\r\n"); - } else if (keyword.startsWith("Entwine")) { - final Cost cost = new Cost(keyword.substring(8), false); - sb.append("Entwine " + cost.toSimpleString() + "\r\n"); - } else if (keyword.startsWith("Kicker")) { - final Cost cost = new Cost(keyword.substring(7), false); - sb.append("Kicker " + cost.toSimpleString() + "\r\n"); - } else if (keyword.startsWith("AlternateAdditionalCost")) { - final String costString1 = keyword.split(":")[1]; - final String costString2 = keyword.split(":")[2]; - final Cost cost1 = new Cost(costString1, false); - final Cost cost2 = new Cost(costString2, false); - sb.append("As an additional cost to cast " + this.getName() + ", " + cost1.toSimpleString() - + " or pay " + cost2.toSimpleString() + ".\r\n"); - } else if (keyword.startsWith("Storm")) { - if (sb.toString().contains("Target") || sb.toString().contains("target")) { - sb.insert( - sb.indexOf("Storm (When you cast this spell, copy it for each spell cast before it this turn.") + 81, - " You may choose new targets for the copies."); - } - } else if (keyword.contains("Replicate") && !sb.toString().contains("you paid its replicate cost.")) { - if (sb.toString().endsWith("\r\n\r\n")) { - sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); - } - sb.append(keyword); - sb.append(" (When you cast this spell, copy it for each time you paid its replicate cost."); - if (sb.toString().contains("Target") || sb.toString().contains("target")) { - sb.append(" You may choose new targets for the copies."); - } - sb.append(")\r\n"); - } else if (keyword.startsWith("Haunt")) { - if (sb.toString().endsWith("\r\n\r\n")) { - sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); - } - sb.append("Haunt ("); - if (this.isCreature()) { - sb.append("When this creature dies, exile it haunting target creature."); - } else { - sb.append("When this spell card is put into a graveyard after resolving, "); - sb.append("exile it haunting target creature."); - } - sb.append(")\r\n"); - } else if (keyword.equals("Convoke")) { - if (sb.toString().endsWith("\r\n\r\n")) { - sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); - } - sb.append("Convoke (Each creature you tap while casting this spell reduces its cost by 1 or by one mana of that creature's color.)\r\n"); - } else if (keyword.equals("Delve")) { - if (sb.toString().endsWith("\r\n\r\n")) { - sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); - } - sb.append("Delve (You may exile any number of cards from your graveyard as you cast this spell. It costs 1 less to cast for each card exiled this way.)\r\n"); - } else if (keyword.endsWith(" offering")) { - if (sb.toString().endsWith("\r\n\r\n")) { - sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); - } - String offeringType = keyword.split(" ")[0]; - sb.append(keyword); - sb.append(" (You may cast this card any time you could cast an instant by sacrificing a "); - sb.append(offeringType); - sb.append("and paying the difference in mana costs between this and the sacrificed "); - sb.append(offeringType); - sb.append(". Mana cost includes color.)"); - } else if (keyword.equals("Remove CARDNAME from your deck before playing if you're not playing for ante.")) { - if (sb.toString().endsWith("\r\n\r\n")) { - sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); - } - sb.append("Remove CARDNAME from your deck before playing if you're not playing for ante.\r\n"); - } else if (keyword.equals("Rebound")) { - sb.append(keyword) - .append(" (If you cast this spell from your hand, exile it as it resolves. At the beginning of your next upkeep, you may cast this card from exile without paying its mana cost.)\r\n"); - } - } - return sb; - } - - /** - *

- * Getter for the field manaAbility. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final List getManaAbility() { - return Collections.unmodifiableList(this.getCharacteristics().getManaAbility()); - } - - - /** - *

- * clearFirstSpellAbility. - *

- */ - public final void clearFirstSpell() { - for (int i = 0; i < this.getCharacteristics().getSpellAbility().size(); i++) { - if (this.getCharacteristics().getSpellAbility().get(i).isSpell()) { - this.getCharacteristics().getSpellAbility().remove(i); - return; - } - } - } - - /** - *

- * getFirstSpellAbility. - *

- * - * @return a SpellAbility object. - */ - public final SpellAbility getFirstSpellAbility() { - final List sas = this.getCharacteristics().getSpellAbility(); - return sas.isEmpty() ? null : sas.get(0); - } - - - /** - *

- * getSpellPermanent. - *

- * - * @return a {@link forge.game.spellability.SpellPermanent} object. - */ - public final SpellPermanent getSpellPermanent() { - for (final SpellAbility sa : this.getCharacteristics().getSpellAbility()) { - if (sa instanceof SpellPermanent) { - return (SpellPermanent) sa; - } - } - return null; - } - - /** - *

- * addSpellAbility. - *

- * - * @param a - * a {@link forge.game.spellability.SpellAbility} object. - */ - public final void addSpellAbility(final SpellAbility a) { - - a.setSourceCard(this); - if (a.isManaAbility()) { - this.getCharacteristics().getManaAbility().add(a); - } else { - this.getCharacteristics().getSpellAbility().add(a); - } - } - - - /** - *

- * removeSpellAbility. - *

- * - * @param a - * a {@link forge.game.spellability.SpellAbility} object. - */ - public final void removeSpellAbility(final SpellAbility a) { - if (a.isManaAbility()) { - // if (a.isExtrinsic()) //never remove intrinsic mana abilities, is - // this the way to go?? - this.getCharacteristics().getManaAbility().remove(a); - } else { - this.getCharacteristics().getSpellAbility().remove(a); - } - } - - /** - *

- * getSpellAbilities. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getSpellAbilities() { - final ArrayList res = new ArrayList(this.getManaAbility()); - res.addAll(this.getCharacteristics().getSpellAbility()); - return res; - } - - /** - *

- * getNonManaSpellAbilities. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final List getNonManaSpellAbilities() { - return this.getCharacteristics().getSpellAbility(); - } - - /** - * - * getAllSpellAbilities. - * - * @return ArrayList - */ - public final ArrayList getAllSpellAbilities() { - final ArrayList res = new ArrayList(); - - for (final CardCharacteristicName key : this.characteristicsMap.keySet()) { - res.addAll(this.getState(key).getSpellAbility()); - res.addAll(this.getState(key).getManaAbility()); - } - - return res; - } - - /** - *

- * getSpells. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final List getSpells() { - final List res = new ArrayList(); - for (final SpellAbility sa : this.getCharacteristics().getSpellAbility()) { - if (!sa.isSpell()) { - continue; - } - res.add(sa); - } - return res; - } - - /** - *

- * getBasicSpells. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final List getBasicSpells() { - final ArrayList res = new ArrayList(); - - for (final SpellAbility sa : this.getCharacteristics().getSpellAbility()) { - if (sa.isSpell() && sa.isBasicSpell()) { - res.add(sa); - } - } - return res; - } - - // shield = regeneration - /** - *

- * getShield. - *

- * - * @return a int. - */ - public final List getShield() { - return this.nShield; - } - - /** - *

- * addShield. - *

- */ - public final void addShield(final CardShields shield) { - this.nShield.add(shield); - } - - /** - *

- * subtractShield. - *

- */ - public final void subtractShield(CardShields shield) { - if (shield != null && shield.hasTrigger()) { - this.getGame().getStack().addSimultaneousStackEntry(shield.getTriggerSA()); - } - this.nShield.remove(shield); - } - - /** - * Adds the regenerated this turn. - */ - public final void addRegeneratedThisTurn() { - this.regeneratedThisTurn += 1; - } - - /** - * Gets the regenerated this turn. - * - * @return the regenerated this turn - */ - public final int getRegeneratedThisTurn() { - return this.regeneratedThisTurn; - } - - /** - * Sets the regenerated this turn. - * - * @param n - * the new regenerated this turn - */ - public final void setRegeneratedThisTurn(final int n) { - this.regeneratedThisTurn = n; - } - - /** - *

- * resetShield. - *

- */ - public final void resetShield() { - this.nShield.clear();; - } - - /** - *

- * canBeShielded. - *

- * - * @return a boolean. - */ - public final boolean canBeShielded() { - return !this.hasKeyword("CARDNAME can't be regenerated."); - } - - // is this "Card" supposed to be a token? - /** - *

- * Setter for the field token. - *

- * - * @param b - * a boolean. - */ - public final void setToken(final boolean b) { - this.token = b; - } - - /** - *

- * isToken. - *

- * - * @return a boolean. - */ - public final boolean isToken() { - return this.token; - } - - /** - *

- * Setter for the field copiedToken. - *

- * - * @param b - * a boolean. - */ - public final void setCopiedToken(final boolean b) { - this.copiedToken = b; - } - - /** - *

- * isCopiedToken. - *

- * - * @return a boolean. - */ - public final boolean isCopiedToken() { - return this.copiedToken; - } - - /** - *

- * Setter for the field copiedSpell. - *

- * - * @param b - * a boolean. - */ - public final void setCopiedSpell(final boolean b) { - this.copiedSpell = b; - } - - /** - *

- * isCopiedSpell. - *

- * - * @return a boolean. - */ - public final boolean isCopiedSpell() { - return this.copiedSpell; - } - - /** - *

- * isFaceDown. - *

- * - * @return a boolean. - */ - public final boolean isFaceDown() { - return this.curCharacteristics == CardCharacteristicName.FaceDown; - } - - /** - *

- * setCanCounter. - *

- * - * @param b - * a boolean. - */ - public final void setCanCounter(final boolean b) { - this.canCounter = b; - } - - /** - *

- * getCanCounter. - *

- * - * @return a boolean. - */ - public final boolean getCanCounter() { - return this.canCounter; - } - - - /** - *

- * addTrigger. - *

- * - * @param c - * a {@link forge.Command} object. - * @param typeIn - * a {@link forge.game.trigger.ZCTrigger} object. - */ - public final void addTrigger(final Command c, final ZCTrigger typeIn) { - this.zcTriggers.add(new AbilityTriggered(this, c, typeIn)); - } - - /** - *

- * removeTrigger. - *

- * - * @param c - * a {@link forge.Command} object. - * @param typeIn - * a {@link forge.game.trigger.ZCTrigger} object. - */ - public final void removeTrigger(final Command c, final ZCTrigger typeIn) { - this.zcTriggers.remove(new AbilityTriggered(this, c, typeIn)); - } - - /** - *

- * executeTrigger. - *

- * - * @param type - * a {@link forge.game.trigger.ZCTrigger} object. - */ - public final void executeTrigger(final ZCTrigger type) { - for (final AbilityTriggered t : this.zcTriggers) { - if (t.getTrigger().equals(type) && t.isBasic()) { - t.run(); - } - } - } - - /** - *

- * clearTriggers. - *

- */ - public final void clearTriggers() { - this.zcTriggers.clear(); - } - - /** - *

- * addComesIntoPlayCommand. - *

- * - * @param c - * a {@link forge.Command} object. - */ - public final void addComesIntoPlayCommand(final Command c) { - this.addTrigger(c, ZCTrigger.ENTERFIELD); - } - - - - /** - *

- * addDestroyCommand. - *

- * - * @param c - * a {@link forge.Command} object. - */ - public final void addDestroyCommand(final Command c) { - this.addTrigger(c, ZCTrigger.DESTROY); - } - - - /** - *

- * addLeavesPlayCommand. - *

- * - * @param c - * a {@link forge.Command} object. - */ - public final void addLeavesPlayCommand(final Command c) { - this.addTrigger(c, ZCTrigger.LEAVEFIELD); - } - - /** - *

- * addUntapCommand. - *

- * - * @param c - * a {@link forge.Command} object. - */ - public final void addUntapCommand(final Command c) { - this.untapCommandList.add(c); - } - - /** - *

- * addChangeControllerCommand. - *

- * - * @param c - * a {@link forge.Command} object. - */ - public final void addChangeControllerCommand(final Command c) { - this.changeControllerCommandList.add(c); - } - - public final void runChangeControllerCommands() { - for (final Command c : this.changeControllerCommandList) { - c.run(); - } - } - - /** - *

- * Setter for the field sickness. - *

- * - * @param b - * a boolean. - */ - public final void setSickness(final boolean b) { - this.sickness = b; - } - - /** @return boolean */ - public final boolean isFirstTurnControlled() { - return this.sickness; - } - - /** - *

- * hasSickness. - *

- * - * @return a boolean. - */ - public final boolean hasSickness() { - return this.sickness && !this.hasKeyword("Haste"); - } - - /** - * - * isSick. - * - * @return boolean - */ - public final boolean isSick() { - return this.sickness && this.isCreature() && !this.hasKeyword("Haste"); - } - - /** - * @return the becameTargetThisTurn - */ - public boolean hasBecomeTargetThisTurn() { - return becameTargetThisTurn; - } - - /** - * @param becameTargetThisTurn0 the becameTargetThisTurn to set - */ - public void setBecameTargetThisTurn(boolean becameTargetThisTurn) { - this.becameTargetThisTurn = becameTargetThisTurn; - } - - /** - * @return the startedTheTurnUntapped - */ - public boolean hasStartedTheTurnUntapped() { - return startedTheTurnUntapped; - } - - /** - * @param startedTheTurnUntapped0 the startedTheTurnUntapped to set - */ - public void setStartedTheTurnUntapped(boolean untapped) { - this.startedTheTurnUntapped = untapped; - } - - /** - *

- * Getter for the field owner. - *

- * - * @return a {@link forge.game.player.Player} object. - */ - public final Player getOwner() { - return this.owner; - } - - /** - * Get the controller for this card. - * - * @return a {@link forge.game.player.Player} object. - */ - public final Player getController() { - Entry lastEntry = this.tempControllers.lastEntry(); - if (lastEntry != null) { - final long lastTimestamp = lastEntry.getKey(); - if (lastTimestamp > this.controllerTimestamp) { - return lastEntry.getValue(); - } - } - if (this.controller != null) { - return this.controller; - } - return this.owner; - } - - public final void addTempController(final Player player, final long tstamp) { - this.tempControllers.put(tstamp, player); - } - - public final void removeTempController(final long tstamp) { - this.tempControllers.remove(tstamp); - } - - public final void clearTempControllers() { - this.tempControllers.clear(); - } - - public final void clearControllers() { - clearTempControllers(); - this.controller = null; - } - - public final void setController(final Player player, final long tstamp) { - clearTempControllers(); - this.controller = player; - this.controllerTimestamp = tstamp; - } - - /** - *

- * Setter for the field owner. - *

- * - * @param player - * a {@link forge.game.player.Player} object. - */ - public final void setOwner(final Player player) { - this.owner = player; - } - - /** - *

- * Getter for the field equippedBy. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getEquippedBy() { - return this.equippedBy; - } - - /** - *

- * Getter for the field fortifiedBy. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getFortifiedBy() { - return this.fortifiedBy; - } - - /** - *

- * Setter for the field equippedBy. - *

- * - * @param list - * a {@link java.util.ArrayList} object. - */ - public final void setEquippedBy(final ArrayList list) { - this.equippedBy = list; - } - - /** - *

- * Setter for the field fortifiedBy. - *

- * - * @param list - * a {@link java.util.ArrayList} object. - */ - public final void setFortifiedBy(final ArrayList list) { - this.fortifiedBy = list; - } - - /** - *

- * Getter for the field equipping. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getEquipping() { - return this.equipping; - } - - /** - *

- * Getter for the field fortifying. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getFortifying() { - return this.fortifying; - } - - /** - *

- * getEquippingCard. - *

- * - * @return a {@link forge.game.card.Card} object. - */ - public final Card getEquippingCard() { - if (this.equipping.isEmpty()) { - return null; - } - return this.equipping.get(0); - } - - /** - *

- * getFortifyingCard. - *

- * - * @return a {@link forge.game.card.Card} object. - */ - public final Card getFortifyingCard() { - if (this.fortifying.isEmpty()) { - return null; - } - return this.fortifying.get(0); - } - - /** - *

- * Setter for the field equipping. - *

- * - * @param list - * a {@link java.util.ArrayList} object. - */ - public final void setEquipping(final ArrayList list) { - this.equipping = list; - } - - /** - *

- * Setter for the field fortifying. - *

- * - * @param list - * a {@link java.util.ArrayList} object. - */ - public final void setFortifying(final ArrayList list) { - this.fortifying = list; - } - - /** - *

- * isEquipped. - *

- * - * @return a boolean. - */ - public final boolean isEquipped() { - return !this.equippedBy.isEmpty(); - } - - /** - *

- * isFortified. - *

- * - * @return a boolean. - */ - public final boolean isFortified() { - return !this.fortifiedBy.isEmpty(); - } - - /** - *

- * isEquipping. - *

- * - * @return a boolean. - */ - public final boolean isEquipping() { - return this.equipping.size() != 0; - } - - /** - *

- * isFortifying. - *

- * - * @return a boolean. - */ - public final boolean isFortifying() { - return !this.fortifying.isEmpty(); - } - - /** - *

- * equipCard. - *

- * equipment.equipCard(cardToBeEquipped) - * - * @param c - * a {@link forge.game.card.Card} object. - */ - public final void equipCard(final Card c) { - if (c.hasKeyword("CARDNAME can't be equipped.")) { - getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, "Trying to equip " + c.getName() + " but it can't be equipped."); - return; - } - if (this.hasStartOfKeyword("CantEquip")) { - final int keywordPosition = this.getKeywordPosition("CantEquip"); - final String parse = this.getKeyword().get(keywordPosition).toString(); - final String[] k = parse.split(" ", 2); - final String[] restrictions = k[1].split(","); - if (c.isValid(restrictions, this.getController(), this)) { - getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, "Trying to equip " + c.getName() + " but it can't be equipped."); - return; - } - } - - Card oldTarget = null; - if (this.isEquipping()) { - oldTarget = this.getEquipping().get(0); - this.unEquipCard(oldTarget); - } - - // They use double links... it's doubtful - this.equipping.add(c); - this.setTimestamp(this.getGame().getNextTimestamp()); - c.equippedBy.add(this); - - // Play the Equip sound - getGame().fireEvent(new GameEventCardAttachment(this, oldTarget, c, AttachMethod.Equip)); - - // run trigger - final HashMap runParams = new HashMap(); - runParams.put("AttachSource", this); - runParams.put("AttachTarget", c); - this.getController().getGame().getTriggerHandler().runTrigger(TriggerType.Attached, runParams, false); - } - - /** - *

- * fortifyCard. - *

- * fortification.fortifyCard(cardToBeFortified) - * - * @param c - * a {@link forge.game.card.Card} object. - */ - public final void fortifyCard(final Card c) { - Card oldTarget = null; - if (this.isFortifying()) { - oldTarget = this.getFortifying().get(0); - this.unFortifyCard(oldTarget); - } - - this.fortifying.add(c); - this.setTimestamp(this.getGame().getNextTimestamp()); - c.fortifiedBy.add(this); - - // Play the Equip sound - getGame().fireEvent(new GameEventCardAttachment(this, oldTarget, c, AttachMethod.Fortify)); - // run trigger - final HashMap runParams = new HashMap(); - runParams.put("AttachSource", this); - runParams.put("AttachTarget", c); - this.getController().getGame().getTriggerHandler().runTrigger(TriggerType.Attached, runParams, false); - } - - /** - *

- * unEquipCard. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - */ - public final void unEquipCard(final Card c) { // equipment.unEquipCard(equippedCard); - this.equipping.remove(c); - c.equippedBy.remove(this); - - getGame().fireEvent(new GameEventCardAttachment(this, c, null, AttachMethod.Equip)); - - // Run triggers - final Map runParams = new TreeMap(); - runParams.put("Equipment", this); - runParams.put("Card", c); - getGame().getTriggerHandler().runTrigger(TriggerType.Unequip, runParams, false); - } - - /** - *

- * unFortifyCard. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - */ - public final void unFortifyCard(final Card c) { // fortification.unEquipCard(fortifiedCard); - this.fortifying.remove(c); - c.fortifiedBy.remove(this); - - getGame().fireEvent(new GameEventCardAttachment(this, c, null, AttachMethod.Fortify)); - } - - /** - *

- * unEquipAllCards. - *

- */ - public final void unEquipAllCards() { - // while there exists equipment, unequip the first one - while (this.equippedBy.size() > 0) { - this.equippedBy.get(0).unEquipCard(this); - } - } - - /** - *

- * Getter for the field enchanting. - *

- * - * @return a {@link forge.game.GameEntity} object. - */ - public final GameEntity getEnchanting() { - return this.enchanting; - } - - /** - *

- * getEnchantingCard. - *

- * - * @return a {@link forge.game.card.Card} object. - */ - public final Card getEnchantingCard() { - if (this.enchanting instanceof Card) { - return (Card) this.enchanting; - } - return null; - } - - /** - *

- * getEnchantingPlayer. - *

- * - * @return a {@link forge.game.player.Player} object. - */ - public final Player getEnchantingPlayer() { - if (this.enchanting instanceof Player) { - return (Player) this.enchanting; - } - return null; - } - - /** - *

- * Setter for the field enchanting. - *

- * - * @param e - * a GameEntity object. - */ - public final void setEnchanting(final GameEntity e) { - this.enchanting = e; - } - - /** - *

- * isEnchanting. - *

- * - * @return a boolean. - */ - public final boolean isEnchanting() { - return this.enchanting != null; - } - - /** - *

- * isEnchanting. - *

- * - * @return a boolean. - */ - public final boolean isEnchantingCard() { - return this.getEnchantingCard() != null; - } - - /** - *

- * isEnchanting. - *

- * - * @return a boolean. - */ - public final boolean isEnchantingPlayer() { - return this.getEnchantingPlayer() != null; - } - - /** - * checks to see if this card is enchanted by an aura with a given name. - * - * @param cardName - * the name of the aura - * @return true if this card is enchanted by an aura with the given name, - * false otherwise - */ - public final boolean isEnchantedBy(final String cardName) { - final ArrayList allAuras = this.getEnchantedBy(); - for (final Card aura : allAuras) { - if (aura.getName().equals(cardName)) { - return true; - } - } - return false; - } - - /** - *

- * removeEnchanting. - *

- * - * @param e - * a {@link forge.game.GameEntity} object. - */ - public final void removeEnchanting(final GameEntity e) { - if (this.enchanting.equals(e)) { - this.enchanting = null; - } - } - - /** - *

- * enchant. - *

- * - * @param entity - * a {@link forge.game.GameEntity} object. - */ - public final void enchantEntity(final GameEntity entity) { - if (entity.hasKeyword("CARDNAME can't be enchanted.")) { - getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, "Trying to enchant " + entity.getName() - + " but it can't be enchanted."); - return; - } - this.enchanting = entity; - this.setTimestamp(this.getGame().getNextTimestamp()); - entity.addEnchantedBy(this); - - getGame().fireEvent(new GameEventCardAttachment(this, null, entity, AttachMethod.Enchant)); - - // run trigger - final HashMap runParams = new HashMap(); - runParams.put("AttachSource", this); - runParams.put("AttachTarget", entity); - this.getController().getGame().getTriggerHandler().runTrigger(TriggerType.Attached, runParams, false); - } - - /** - *

- * unEnchant. - *

- * - * @param gameEntity - * a {@link forge.game.GameEntity} object. - */ - public final void unEnchantEntity(final GameEntity entity) { - if (this.enchanting == null || !this.enchanting.equals(entity)) - return; - - this.enchanting = null; - entity.removeEnchantedBy(this); - getGame().fireEvent(new GameEventCardAttachment(this, entity, null, AttachMethod.Enchant)); - } - - /** - *

- * Setter for the field type. - *

- * - * @param a - * a {@link java.util.ArrayList} object. - */ - public final void setType(final List a) { - this.getCharacteristics().setType(new ArrayList(a)); - } - - /** - *

- * addType. - *

- * - * @param a - * a {@link java.lang.String} object. - */ - public final void addType(final String a) { - this.getCharacteristics().getType().add(a); - } - - - /** - *

- * Getter for the field type. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getType() { - - // see if type changes are in effect - final ArrayList newType = new ArrayList(this.getCharacteristics().getType()); - for (final CardType ct : this.changedCardTypes.values()) { - final ArrayList removeTypes = new ArrayList(); - if (ct.getRemoveType() != null) { - removeTypes.addAll(ct.getRemoveType()); - } - // remove old types - for (int i = 0; i < newType.size(); i++) { - final String t = newType.get(i); - if (ct.isRemoveSuperTypes() && forge.card.CardType.isASuperType(t)) { - removeTypes.add(t); - } - if (ct.isRemoveCardTypes() && forge.card.CardType.isACardType(t)) { - removeTypes.add(t); - } - if (ct.isRemoveSubTypes() && forge.card.CardType.isASubType(t)) { - removeTypes.add(t); - } - if (ct.isRemoveCreatureTypes() && (forge.card.CardType.isACreatureType(t) || t.equals("AllCreatureTypes"))) { - removeTypes.add(t); - } - } - newType.removeAll(removeTypes); - // add new types - if (ct.getType() != null) { - newType.addAll(ct.getType()); - } - - } - - return newType; - } - - /** - * - * TODO Write javadoc for this method. - * - * @param types - * ArrayList - * @param removeTypes - * ArrayList - * @param removeSuperTypes - * boolean - * @param removeCardTypes - * boolean - * @param removeSubTypes - * boolean - * @param removeCreatureTypes - * boolean - * @param timestamp - * long - */ - public final void addChangedCardTypes(final ArrayList types, final ArrayList removeTypes, - final boolean removeSuperTypes, final boolean removeCardTypes, final boolean removeSubTypes, - final boolean removeCreatureTypes, final long timestamp) { - - this.changedCardTypes.put(timestamp, new CardType(types, removeTypes, removeSuperTypes, removeCardTypes, removeSubTypes, removeCreatureTypes)); - } - - /** - * - * TODO Write javadoc for this method. - * - * @param types - * String[] - * @param removeTypes - * String[] - * @param removeSuperTypes - * boolean - * @param removeCardTypes - * boolean - * @param removeSubTypes - * boolean - * @param removeCreatureTypes - * boolean - * @param timestamp - * long - */ - public final void addChangedCardTypes(final String[] types, final String[] removeTypes, - final boolean removeSuperTypes, final boolean removeCardTypes, final boolean removeSubTypes, - final boolean removeCreatureTypes, final long timestamp) { - ArrayList typeList = null; - ArrayList removeTypeList = null; - if (types != null) { - typeList = new ArrayList(Arrays.asList(types)); - } - - if (removeTypes != null) { - removeTypeList = new ArrayList(Arrays.asList(removeTypes)); - } - - this.addChangedCardTypes(typeList, removeTypeList, removeSuperTypes, removeCardTypes, removeSubTypes, - removeCreatureTypes, timestamp); - } - - /** - * - * TODO Write javadoc for this method. - * - * @param timestamp - * long - */ - public final void removeChangedCardTypes(final long timestamp) { - this.changedCardTypes.remove(Long.valueOf(timestamp)); - } - - // values that are printed on card - /** - *

- * Getter for the field baseLoyalty. - *

- * - * @return a int. - */ - public final int getBaseLoyalty() { - return this.baseLoyalty; - } - - // values that are printed on card - /** - *

- * Setter for the field baseLoyalty. - *

- * - * @param n - * a int. - */ - public final void setBaseLoyalty(final int n) { - this.baseLoyalty = n; - } - - // values that are printed on card - /** - *

- * Getter for the field baseAttack. - *

- * - * @return a int. - */ - public final int getBaseAttack() { - return this.getCharacteristics().getBaseAttack(); - } - - /** - *

- * Getter for the field baseDefense. - *

- * - * @return a int. - */ - public final int getBaseDefense() { - return this.getCharacteristics().getBaseDefense(); - } - - // values that are printed on card - /** - *

- * Setter for the field baseAttack. - *

- * - * @param n - * a int. - */ - public final void setBaseAttack(final int n) { - this.getCharacteristics().setBaseAttack(n); - } - - /** - *

- * Setter for the field baseDefense. - *

- * - * @param n - * a int. - */ - public final void setBaseDefense(final int n) { - this.getCharacteristics().setBaseDefense(n); - } - - // values that are printed on card - /** - *

- * Getter for the field baseAttackString. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getBaseAttackString() { - return (null == this.baseAttackString) ? "" + this.getBaseAttack() : this.baseAttackString; - } - - /** - *

- * Getter for the field baseDefenseString. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getBaseDefenseString() { - return (null == this.baseDefenseString) ? "" + this.getBaseDefense() : this.baseDefenseString; - } - - // values that are printed on card - /** - *

- * Setter for the field baseAttackString. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void setBaseAttackString(final String s) { - this.baseAttackString = s; - } - - /** - *

- * Setter for the field baseDefenseString. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void setBaseDefenseString(final String s) { - this.baseDefenseString = s; - } - - /** - * - * TODO Write javadoc for this method. - * - * @return int - */ - public final int getSetPower() { - if (this.newPT.isEmpty()) { - return -1; - } - - final CardPowerToughness latestPT = this.getLatestPT(); - - return latestPT.getPower(); - } - - /** - * - * TODO Write javadoc for this method. - * - * @return int - */ - public final int getSetToughness() { - if (this.newPT.isEmpty()) { - return -1; - } - - final CardPowerToughness latestPT = this.getLatestPT(); - - return latestPT.getToughness(); - } - - /** - * - * TODO Write javadoc for this method. - * - * @return CardPowerToughness - */ - public final CardPowerToughness getLatestPT() { - CardPowerToughness latestPT = new CardPowerToughness(-1, -1, -2); - long max = -2; - - for (final CardPowerToughness pt : this.newPT) { - if (pt.getTimestamp() >= max) { - max = pt.getTimestamp(); - latestPT = pt; - } - } - - return latestPT; - } - - /** - * - * TODO Write javadoc for this method. - * - * @param power - * int - * @param toughness - * int - * @param timestamp - * int - */ - public final void addNewPT(final int power, final int toughness, final long timestamp) { - this.newPT.add(new CardPowerToughness(power, toughness, timestamp)); - } - - /** - * - * TODO Write javadoc for this method. - * - * @param timestamp - * long - */ - public final void removeNewPT(final long timestamp) { - for (int i = 0; i < this.newPT.size(); i++) { - final CardPowerToughness cardPT = this.newPT.get(i); - if (cardPT.getTimestamp() == timestamp) { - this.newPT.remove(cardPT); - } - } - } - - /** - * - * TODO Write javadoc for this method. - * - * @return int - */ - public final int getCurrentPower() { - int total = this.getBaseAttack(); - final int setPower = this.getSetPower(); - if (setPower != -1) { - total = setPower; - } - - return total; - } - - /** - *

- * getUnswitchedAttack. - *

- * - * @return a int. - */ - public final int getUnswitchedPower() { - int total = this.getCurrentPower(); - - total += this.getTempAttackBoost() + this.getSemiPermanentAttackBoost() + getPowerBonusFromCounters(); - return total; - } - - public final int getPowerBonusFromCounters() { - int total = 0; - - total += this.getCounters(CounterType.P1P1) + this.getCounters(CounterType.P1P2) + this.getCounters(CounterType.P1P0) - - this.getCounters(CounterType.M1M1) + 2 * this.getCounters(CounterType.P2P2) - 2 * this.getCounters(CounterType.M2M1) - - 2 * this.getCounters(CounterType.M2M2) - this.getCounters(CounterType.M1M0) + 2 * this.getCounters(CounterType.P2P0); - return total; - } - - /** - *

- * getNetAttack. - *

- * - * @return a int. - */ - public final int getNetAttack() { - if (this.getAmountOfKeyword("CARDNAME's power and toughness are switched") % 2 != 0) { - return this.getUnswitchedToughness(); - } - return this.getUnswitchedPower(); - } - - /** - * - * TODO Write javadoc for this method. - * - * @return an int - */ - public final int getCurrentToughness() { - int total = this.getBaseDefense(); - - final int setToughness = this.getSetToughness(); - if (setToughness != -1) { - total = setToughness; - } - - return total; - } - - /** - *

- * getUnswitchedDefense. - *

- * - * @return a int. - */ - public final int getUnswitchedToughness() { - int total = this.getCurrentToughness(); - - total += this.getTempDefenseBoost() + this.getSemiPermanentDefenseBoost() + getToughnessBonusFromCounters(); - return total; - } - - public final int getToughnessBonusFromCounters() { - int total = 0; - - total += this.getCounters(CounterType.P1P1) + 2 * this.getCounters(CounterType.P1P2) - this.getCounters(CounterType.M1M1) - + this.getCounters(CounterType.P0P1) - 2 * this.getCounters(CounterType.M0M2) + 2 * this.getCounters(CounterType.P2P2) - - this.getCounters(CounterType.M0M1) - this.getCounters(CounterType.M2M1) - 2 * this.getCounters(CounterType.M2M2) - + 2 * this.getCounters(CounterType.P0P2); - return total; - } - - /** - *

- * getNetDefense. - *

- * - * @return a int. - */ - public final int getNetDefense() { - if (this.getAmountOfKeyword("CARDNAME's power and toughness are switched") % 2 != 0) { - return this.getUnswitchedPower(); - } - return this.getUnswitchedToughness(); - } - - // How much combat damage does the card deal - /** - *

- * getNetCombatDamage. - *

- * - * @return a int. - */ - public final int getNetCombatDamage() { - if (this.hasKeyword("CARDNAME assigns no combat damage")) { - return 0; - } - - if (getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.toughnessAssignsDamage)) { - return this.getNetDefense(); - } - return this.getNetAttack(); - } - - private int multiKickerMagnitude = 0; - public final void addMultiKickerMagnitude(final int n) { this.multiKickerMagnitude += n; } - public final void setKickerMagnitude(final int n) { this.multiKickerMagnitude = n; } - public final int getKickerMagnitude() { - if (this.multiKickerMagnitude > 0) - return multiKickerMagnitude; - boolean hasK1 = costsPaid.contains(OptionalCost.Kicker1); - return hasK1 == costsPaid.contains(OptionalCost.Kicker2) ? (hasK1 ? 2 : 0) : 1; - } - - private int pseudoKickerMagnitude = 0; - public final void addPseudoMultiKickerMagnitude(final int n) { this.pseudoKickerMagnitude += n; } - public final void setPseudoMultiKickerMagnitude(final int n) { this.pseudoKickerMagnitude = n; } - public final int getPseudoKickerMagnitude() { return pseudoKickerMagnitude; } - - // for cards like Giant Growth, etc. - /** - *

- * Getter for the field tempAttackBoost. - *

- * - * @return a int. - */ - public final int getTempAttackBoost() { - return this.tempAttackBoost; - } - - /** - *

- * Getter for the field tempDefenseBoost. - *

- * - * @return a int. - */ - public final int getTempDefenseBoost() { - return this.tempDefenseBoost; - } - - /** - *

- * addTempAttackBoost. - *

- * - * @param n - * a int. - */ - public final void addTempAttackBoost(final int n) { - this.tempAttackBoost += n; - } - - /** - *

- * addTempDefenseBoost. - *

- * - * @param n - * a int. - */ - public final void addTempDefenseBoost(final int n) { - this.tempDefenseBoost += n; - } - - // for cards like Glorious Anthem, etc. - /** - *

- * Getter for the field semiPermanentAttackBoost. - *

- * - * @return a int. - */ - public final int getSemiPermanentAttackBoost() { - return this.semiPermanentAttackBoost; - } - - /** - *

- * Getter for the field semiPermanentDefenseBoost. - *

- * - * @return a int. - */ - public final int getSemiPermanentDefenseBoost() { - return this.semiPermanentDefenseBoost; - } - - /** - *

- * addSemiPermanentAttackBoost. - *

- * - * @param n - * a int. - */ - public final void addSemiPermanentAttackBoost(final int n) { - this.semiPermanentAttackBoost += n; - } - - /** - *

- * addSemiPermanentDefenseBoost. - *

- * - * @param n - * a int. - */ - public final void addSemiPermanentDefenseBoost(final int n) { - this.semiPermanentDefenseBoost += n; - } - - /** - *

- * Setter for the field semiPermanentAttackBoost. - *

- * - * @param n - * a int. - */ - public final void setSemiPermanentAttackBoost(final int n) { - this.semiPermanentAttackBoost = n; - } - - /** - *

- * Setter for the field semiPermanentDefenseBoost. - *

- * - * @param n - * a int. - */ - public final void setSemiPermanentDefenseBoost(final int n) { - this.semiPermanentDefenseBoost = n; - } - - /** - *

- * isUntapped. - *

- * - * @return a boolean. - */ - public final boolean isUntapped() { - return !this.tapped; - } - - /** - *

- * isTapped. - *

- * - * @return a boolean. - */ - public final boolean isTapped() { - return this.tapped; - } - - /** - *

- * Setter for the field tapped. - *

- * - * @param b - * a boolean. - */ - public final void setTapped(final boolean b) { - this.tapped = b; - } - - /** - *

- * tap. - *

- */ - public final void tap() { - if (this.isTapped()) - return; - - // Run triggers - final Map runParams = new TreeMap(); - runParams.put("Card", this); - getGame().getTriggerHandler().runTrigger(TriggerType.Taps, runParams, false); - - this.setTapped(true); - getGame().fireEvent(new GameEventCardTapped(this, true)); - } - - /** - *

- * untap. - *

- */ - public final void untap() { - if (this.isUntapped()) - return; - // Run Replacement effects - final HashMap repRunParams = new HashMap(); - repRunParams.put("Event", "Untap"); - repRunParams.put("Affected", this); - - if (getGame().getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) { - return; - } - - // Run triggers - final Map runParams = new TreeMap(); - runParams.put("Card", this); - getGame().getTriggerHandler().runTrigger(TriggerType.Untaps, runParams, false); - - for (final Command var : this.untapCommandList) { - var.run(); - } - this.setTapped(false); - getGame().fireEvent(new GameEventCardTapped(this, false)); - } - - // keywords are like flying, fear, first strike, etc... - /** - *

- * getKeyword. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final List getKeyword() { - final ArrayList keywords = this.getUnhiddenKeyword(); - keywords.addAll(this.getHiddenExtrinsicKeyword()); - - return keywords; - } - - /** - * Gets the keyword amount. - * - * @param keyword - * the keyword - * @return the keyword amount - */ - public final int getKeywordAmount(final String keyword) { - int res = 0; - for (final String k : this.getKeyword()) { - if (k.equals(keyword)) { - res++; - } - } - - return res; - } - - - /** - * Adds the changed card keywords. - * - * @param keywords - * the keywords - * @param removeKeywords - * the remove keywords - * @param removeAllKeywords - * the remove all keywords - * @param timestamp - * the timestamp - */ - public final void addChangedCardKeywords(final List keywords, final List removeKeywords, - final boolean removeAllKeywords, final long timestamp) { - keywords.removeAll(this.getCantHaveOrGainKeyword()); - // if the key already exists - merge entries - if (changedCardKeywords.containsKey(timestamp)) { - List kws = keywords; - List rkws = removeKeywords; - boolean remAll = removeAllKeywords; - CardKeywords cks = changedCardKeywords.get(timestamp); - kws.addAll(cks.getKeywords()); - rkws.addAll(cks.getRemoveKeywords()); - remAll |= cks.isRemoveAllKeywords(); - this.changedCardKeywords.put(timestamp, new CardKeywords(kws, rkws, remAll)); - return; - } - - this.changedCardKeywords.put(timestamp, new CardKeywords(keywords, removeKeywords, removeAllKeywords)); - } - - /** - * Adds the changed card keywords. - * - * @param keywords - * the keywords - * @param removeKeywords - * the remove keywords - * @param removeAllKeywords - * the remove all keywords - * @param timestamp - * the timestamp - */ - public final void addChangedCardKeywords(final String[] keywords, final String[] removeKeywords, - final boolean removeAllKeywords, final long timestamp) { - ArrayList keywordsList = new ArrayList(); - ArrayList removeKeywordsList = new ArrayList(); - if (keywords != null) { - keywordsList = new ArrayList(Arrays.asList(keywords)); - } - - if (removeKeywords != null) { - removeKeywordsList = new ArrayList(Arrays.asList(removeKeywords)); - } - - this.addChangedCardKeywords(keywordsList, removeKeywordsList, removeAllKeywords, timestamp); - } - - /** - * Removes the changed card keywords. - * - * @param timestamp - * the timestamp - */ - public final void removeChangedCardKeywords(final long timestamp) { - changedCardKeywords.remove(Long.valueOf(timestamp)); - } - - // Hidden keywords will be left out - /** - *

- * getUnhiddenKeyword. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getUnhiddenKeyword() { - final ArrayList keywords = new ArrayList(); - keywords.addAll(this.getIntrinsicKeyword()); - keywords.addAll(this.getExtrinsicKeyword()); - - // see if keyword changes are in effect - for (final CardKeywords ck : this.changedCardKeywords.values()) { - - if (ck.isRemoveAllKeywords()) { - keywords.clear(); - } else if (ck.getRemoveKeywords() != null) { - keywords.removeAll(ck.getRemoveKeywords()); - } - - if (ck.getKeywords() != null) { - keywords.addAll(ck.getKeywords()); - } - } - - return keywords; - } - - /** - *

- * getIntrinsicAbilities. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final List getUnparsedAbilities() { - return this.getCharacteristics().getUnparsedAbilities(); - } - - /** - *

- * Getter for the field intrinsicKeyword. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final List getIntrinsicKeyword() { - // will not create a copy here - due to performance reasons. - // Most of other code checks for contains, or creates copy by itself - return this.getCharacteristics().getIntrinsicKeyword(); - } - - /** - *

- * Setter for the field intrinsicKeyword. - *

- * - * @param a - * a {@link java.util.ArrayList} object. - */ - public final void setIntrinsicKeyword(final List a) { - this.getCharacteristics().setIntrinsicKeyword(new ArrayList(a)); - } - - - /** - *

- * setIntrinsicAbilities. - *

- * - * @param a - * a {@link java.util.ArrayList} object. - */ - public final void setIntrinsicAbilities(final List a) { - this.getCharacteristics().setUnparsedAbilities(new ArrayList(a)); - } - - /** - *

- * addIntrinsicKeyword. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void addIntrinsicKeyword(final String s) { - if (s.trim().length() != 0) { - this.getCharacteristics().getIntrinsicKeyword().add(s); - } - } - - /** - *

- * addIntrinsicAbility. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void addIntrinsicAbility(final String s) { - if (s.trim().length() != 0) { - this.getCharacteristics().getUnparsedAbilities().add(s); - } - } - - /** - *

- * removeIntrinsicKeyword. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void removeIntrinsicKeyword(final String s) { - this.getCharacteristics().getIntrinsicKeyword().remove(s); - } - - - /** - *

- * Getter for the field extrinsicKeyword. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public ArrayList getExtrinsicKeyword() { - return this.extrinsicKeyword; - } - - /** - *

- * Setter for the field extrinsicKeyword. - *

- * - * @param a - * a {@link java.util.ArrayList} object. - */ - public final void setExtrinsicKeyword(final ArrayList a) { - this.extrinsicKeyword = new ArrayList(a); - } - - /** - *

- * addExtrinsicKeyword. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public void addExtrinsicKeyword(final String s) { - if (s.startsWith("HIDDEN")) { - this.addHiddenExtrinsicKeyword(s); - } else { - this.extrinsicKeyword.add(s); - } - } - - /** - *

- * removeExtrinsicKeyword. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public void removeExtrinsicKeyword(final String s) { - if (s.startsWith("HIDDEN")) { - this.removeHiddenExtrinsicKeyword(s); - } else { - this.extrinsicKeyword.remove(s); - } - } - - /** - * Removes the all extrinsic keyword. - * - * @param s - * the s - */ - public void removeAllExtrinsicKeyword(final String s) { - final ArrayList strings = new ArrayList(); - strings.add(s); - this.hiddenExtrinsicKeyword.removeAll(strings); - this.extrinsicKeyword.removeAll(strings); - } - - - // Hidden Keywords will be returned without the indicator HIDDEN - /** - *

- * getHiddenExtrinsicKeyword. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final ArrayList getHiddenExtrinsicKeyword() { - while (true) { - try { - final ArrayList keywords = new ArrayList(); - for (int i = 0; i < this.hiddenExtrinsicKeyword.size(); i++) { - String keyword = this.hiddenExtrinsicKeyword.get(i); - if (keyword.startsWith("HIDDEN")) { - keyword = keyword.substring(7); - } - keywords.add(keyword); - } - return keywords; - } catch (IndexOutOfBoundsException ex) { - // Do nothing and let the while loop retry - } - } - } - - - /** - *

- * addHiddenExtrinsicKeyword. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void addHiddenExtrinsicKeyword(final String s) { - this.hiddenExtrinsicKeyword.add(s); - } - - /** - *

- * removeHiddenExtrinsicKeyword. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void removeHiddenExtrinsicKeyword(final String s) { - this.hiddenExtrinsicKeyword.remove(s); - } - - /** - *

- * getCantHaveOrGainKeyword. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final List getCantHaveOrGainKeyword() { - final List cantGain = new ArrayList(); - for (String s : this.hiddenExtrinsicKeyword) { - if (s.contains("can't have or gain")) { - cantGain.add(s.split("can't have or gain ")[1]); - } - } - return cantGain; - } - - /** - *

- * setStaticAbilityStrings. - *

- * - * @param a - * a {@link java.util.ArrayList} object. - */ - public final void setStaticAbilityStrings(final List a) { - this.getCharacteristics().setStaticAbilityStrings(new ArrayList(a)); - } - - /** - * Gets the static ability strings. - * - * @return the static ability strings - */ - public final List getStaticAbilityStrings() { - return this.getCharacteristics().getStaticAbilityStrings(); - } - - /** - *

- * addStaticAbilityStrings. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void addStaticAbilityString(final String s) { - if (StringUtils.isNotBlank(s)) { - this.getCharacteristics().getStaticAbilityStrings().add(s); - } - } - - /** - * Sets the static abilities. - * - * @param a - * the new static abilities - */ - public final void setStaticAbilities(final ArrayList a) { - this.getCharacteristics().setStaticAbilities(new ArrayList(a)); - } - - /** - * Gets the static abilities. - * - * @return the static abilities - */ - public final ArrayList getStaticAbilities() { - return new ArrayList(this.getCharacteristics().getStaticAbilities()); - } - - /** - * Adds the static ability. - * - * @param s - * the s - */ - public final StaticAbility addStaticAbility(final String s) { - if (s.trim().length() != 0) { - final StaticAbility stAb = new StaticAbility(s, this); - this.getCharacteristics().getStaticAbilities().add(stAb); - return stAb; - } - return null; - } - - public final boolean isPermanent() { - return !(this.isInstant() || this.isSorcery() || this.isImmutable()); - } - - public final boolean isSpell() { - return (this.isInstant() || this.isSorcery() || (this.isAura() && !this.isInZone((ZoneType.Battlefield)))); - } - - public final boolean isEmblem() { return this.typeContains("Emblem"); } - - public final boolean isLand() { return this.typeContains("Land"); } - public final boolean isBasicLand() { return this.typeContains("Basic"); } - public final boolean isSnow() { return this.typeContains("Snow"); } - - public final boolean isTribal() { return this.typeContains("Tribal"); } - public final boolean isSorcery() { return this.typeContains("Sorcery"); } - public final boolean isInstant() { return this.typeContains("Instant"); } - - public final boolean isCreature() { return this.typeContains("Creature"); } - public final boolean isArtifact() { return this.typeContains("Artifact"); } - public final boolean isEquipment() { return this.typeContains("Equipment"); } - public final boolean isFortification() { return this.typeContains("Fortification"); } - public final boolean isScheme() { return this.typeContains("Scheme"); } - - public final boolean isPlaneswalker() { return this.typeContains("Planeswalker"); } - - public final boolean isEnchantment() { return this.typeContains("Enchantment"); } - public final boolean isAura() { return this.typeContains("Aura"); } - - private boolean typeContains(final String s) { - final Iterator it = this.getType().iterator(); - while (it.hasNext()) { - if (it.next().startsWith(s)) { - return true; - } - } - - return false; - } - - /** - *

- * Getter for the field uniqueNumber. - *

- * - * @return a int. - */ - public final int getUniqueNumber() { - return this.uniqueNumber; - } - - /** {@inheritDoc} */ - @Override - public final int compareTo(final Card that) { - /* - * Return a negative integer of this < that, a positive integer if this - * > that, and zero otherwise. - */ - - if (that == null) { - /* - * "Here we can arbitrarily decide that all non-null Cards are - * `greater than' null Cards. It doesn't really matter what we - * return in this case, as long as it is consistent. I rather think - * of null as being lowly." --Braids - */ - return +1; - } else if (this.getUniqueNumber() > that.getUniqueNumber()) { - return +1; - } else if (this.getUniqueNumber() < that.getUniqueNumber()) { - return -1; - } else { - return 0; - } - } - - /** {@inheritDoc} */ - @Override - public final boolean equals(final Object o) { - if (o instanceof Card) { - final Card c = (Card) o; - final int a = this.getUniqueNumber(); - final int b = c.getUniqueNumber(); - return (a == b); - } - return false; - } - - /** {@inheritDoc} */ - @Override - public final int hashCode() { - return this.getUniqueNumber(); - } - - /** {@inheritDoc} */ - @Override - public final String toString() { - return this.getName() + " (" + this.getUniqueNumber() + ")"; - } - - /** - *

- * isUnearthed. - *

- * - * @return a boolean. - */ - public final boolean isUnearthed() { - return this.unearthed; - } - - /** - *

- * Setter for the field unearthed. - *

- * - * @param b - * a boolean. - */ - public final void setUnearthed(final boolean b) { - this.unearthed = b; - } - - /** - *

- * Getter for the field miracleCost. - *

- * - * @return a {@link java.lang.String} object. - */ - public final Cost getMiracleCost() { - return this.miracleCost; - } - - /** - *

- * Setter for the field miracleCost. - *

- * - * @param cost - * a {@link java.lang.String} object. - */ - public final void setMiracleCost(final Cost cost) { - this.miracleCost = cost; - } - - /** - *

- * hasSuspend. - *

- * - * @return a boolean. - */ - public final boolean hasSuspend() { - return this.suspend; - } - - /** - *

- * Setter for the field suspend. - *

- * - * @param b - * a boolean. - */ - public final void setSuspend(final boolean b) { - this.suspend = b; - } - - /** - *

- * wasSuspendCast. - *

- * - * @return a boolean. - */ - public final boolean wasSuspendCast() { - return this.suspendCast; - } - - /** - *

- * Setter for the field suspendCast. - *

- * - * @param b - * a boolean. - */ - public final void setSuspendCast(final boolean b) { - this.suspendCast = b; - } - - /** - * Checks if is phased out. - * - * @return true, if is phased out - */ - public final boolean isPhasedOut() { - return this.phasedOut; - } - - /** - * Sets the phased out. - * - * @param phasedOut - * the new phased out - */ - public final void setPhasedOut(final boolean phasedOut) { - this.phasedOut = phasedOut; - } - - /** - * Phase. - */ - public final void phase() { - this.phase(true); - } - - /** - * Phase. - * - * @param direct - * the direct - */ - public final void phase(final boolean direct) { - final boolean phasingIn = this.isPhasedOut(); - - if (!this.switchPhaseState()) { - // Switch Phase State returns False if the Permanent can't Phase Out - return; - } - - if (!phasingIn) { - this.setDirectlyPhasedOut(direct); - } - - for (final Card eq : this.getEquippedBy()) { - if (eq.isPhasedOut() == phasingIn) { - eq.phase(false); - } - } - - for (final Card f : this.getFortifiedBy()) { - if (f.isPhasedOut() == phasingIn) { - f.phase(false); - } - } - - for (final Card aura : this.getEnchantedBy()) { - if (aura.isPhasedOut() == phasingIn) { - aura.phase(false); - } - } - - this.getGame().fireEvent(new GameEventCardPhased(this, this.isPhasedOut())); - } - - private boolean switchPhaseState() { - if (!this.phasedOut && this.hasKeyword("CARDNAME can't phase out.")) { - return false; - } - - this.phasedOut = !this.phasedOut; - final Combat combat = this.getGame().getPhaseHandler().getCombat(); - if (combat != null && this.phasedOut) { - combat.removeFromCombat(this); - } - if (this.phasedOut && this.isToken()) { - // 702.23k Phased-out tokens cease to exist as a state-based action. - // See rule 704.5d. - // 702.23d The phasing event doesn't actually cause a permanent to - // change zones or control, - // even though it's treated as though it's not on the battlefield - // and not under its controller's control while it's phased out. - // Zone-change triggers don't trigger when a permanent phases in or - // out. - - // Suppressed Exiling is as close as we can get to - // "ceasing to exist" - getGame().getTriggerHandler().suppressMode(TriggerType.ChangesZone); - getGame().getAction().exile(this); - getGame().getTriggerHandler().clearSuppression(TriggerType.ChangesZone); - } - return true; - } - - /** - * Checks if is directly phased out. - * - * @return true, if is directly phased out - */ - public final boolean isDirectlyPhasedOut() { - return this.directlyPhasedOut; - } - - /** - * Sets the directly phased out. - * - * @param direct - * the new directly phased out - */ - public final void setDirectlyPhasedOut(final boolean direct) { - this.directlyPhasedOut = direct; - } - - /** - *

- * isReflectedLand. - *

- * - * @return a boolean. - */ - public final boolean isReflectedLand() { - for (final SpellAbility a : this.getCharacteristics().getManaAbility()) { - if (a.getApi() == ApiType.ManaReflected) { - return true; - } - } - - return false; - } - - /** - *

- * hasKeyword. - *

- * - * @param keyword - * a {@link java.lang.String} object. - * @return a boolean. - */ - @Override - public final boolean hasKeyword(final String keyword) { - String kw = keyword; - if (kw.startsWith("HIDDEN")) { - kw = kw.substring(7); - } - return this.getKeyword().contains(kw); - } - - /** - *

- * hasStartOfKeyword. - *

- * - * @param keyword - * a {@link java.lang.String} object. - * @return a boolean. - */ - public final boolean hasStartOfKeyword(final String keyword) { - final List a = this.getKeyword(); - for (int i = 0; i < a.size(); i++) { - if (a.get(i).toString().startsWith(keyword)) { - return true; - } - } - return false; - } - - /** - *

- * hasStartOfKeyword. - *

- * - * @param keyword - * a {@link java.lang.String} object. - * @return a boolean. - */ - public final boolean hasStartOfUnHiddenKeyword(final String keyword) { - final List a = this.getUnhiddenKeyword(); - for (int i = 0; i < a.size(); i++) { - if (a.get(i).toString().startsWith(keyword)) { - return true; - } - } - return false; - } - - /** - *

- * getKeywordPosition. - *

- * - * @param k - * a {@link java.lang.String} object. - * @return a int. - */ - public final int getKeywordPosition(final String k) { - final List a = this.getKeyword(); - for (int i = 0; i < a.size(); i++) { - if (a.get(i).toString().startsWith(k)) { - return i; - } - } - return -1; - } - - /** - *

- * hasAnyKeyword. - *

- * - * @param keywords - * an array of {@link java.lang.String} objects. - * @return a boolean. - */ - public final boolean hasAnyKeyword(final Iterable keywords) { - for (final String keyword : keywords) { - if (this.hasKeyword(keyword)) { - return true; - } - } - - return false; - } - - // This counts the number of instances of a keyword a card has - /** - *

- * getAmountOfKeyword. - *

- * - * @param k - * a {@link java.lang.String} object. - * @return a int. - */ - public final int getAmountOfKeyword(final String k) { - int count = 0; - for (String kw : this.getKeyword()) { - if (kw.equals(k)) { - count++; - } - } - - return count; - } - - // This is for keywords with a number like Bushido, Annihilator and Rampage. - // It returns the total. - /** - *

- * getKeywordMagnitude. - *

- * - * @param k - * a {@link java.lang.String} object. - * @return a int. - */ - public final int getKeywordMagnitude(final String k) { - int count = 0; - for (final String kw : this.getKeyword()) { - if (kw.startsWith(k)) { - final String[] parse = kw.split(" "); - final String s = parse[1]; - count += Integer.parseInt(s); - } - } - return count; - } - - private String toMixedCase(final String s) { - if (s.equals("")) { - return s; - } - final StringBuilder sb = new StringBuilder(); - // to handle hyphenated Types - final String[] types = s.split("-"); - for (int i = 0; i < types.length; i++) { - if (i != 0) { - sb.append("-"); - } - sb.append(types[i].substring(0, 1).toUpperCase()); - sb.append(types[i].substring(1).toLowerCase()); - } - - return sb.toString(); - } - - // usable to check for changelings - /** - *

- * isType. - *

- * - * @param type - * a {@link java.lang.String} object. - * @return a boolean. - */ - public final boolean isType(String type) { - if (type == null) { - return false; - } - - type = this.toMixedCase(type); - - if (this.typeContains(type) - || ((this.isCreature() || this.isTribal()) && forge.card.CardType.isACreatureType(type) && this - .typeContains("AllCreatureTypes"))) { - return true; - } - return false; - } // isType - - // Takes one argument like Permanent.Blue+withFlying - /** - *

- * isValid. - *

- * - * @param restriction - * a {@link java.lang.String} object. - * @param sourceController - * a {@link forge.game.player.Player} object. - * @param source - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - @Override - public final boolean isValid(final String restriction, final Player sourceController, final Card source) { - - if (this.isImmutable() - && !source.getRemembered().contains(this)) { // special case exclusion - return false; - } - - // Inclusive restrictions are Card types - final String[] incR = restriction.split("\\.", 2); - - boolean testFailed = false; - if (incR[0].startsWith("!")) { - testFailed = true; // a bit counter logical)) - incR[0] = incR[0].substring(1); // consume negation sign - } - - if (incR[0].equals("Spell") && !this.isSpell()) { - return testFailed; - } - if (incR[0].equals("Permanent") && (this.isInstant() || this.isSorcery())) { - return testFailed; - } - if (!incR[0].equals("card") && !incR[0].equals("Card") && !incR[0].equals("Spell") - && !incR[0].equals("Permanent") && !(this.isType(incR[0]))) { - return testFailed; // Check for wrong type - } - - if (incR.length > 1) { - final String excR = incR[1]; - final String[] exR = excR.split("\\+"); // Exclusive Restrictions are ... - for (int j = 0; j < exR.length; j++) { - if (!this.hasProperty(exR[j], sourceController, source)) { - return testFailed; - } - } - } - return !testFailed; - } // isValid(String Restriction) - - // Takes arguments like Blue or withFlying - /** - *

- * hasProperty. - *

- * - * @param property - * a {@link java.lang.String} object. - * @param sourceController - * a {@link forge.game.player.Player} object. - * @param source - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - @Override - public boolean hasProperty(final String property, final Player sourceController, final Card source) { - final Game game = getGame(); - final Combat combat = game.getCombat(); - // by name can also have color names, so needs to happen before colors. - if (property.startsWith("named")) { - if (!this.getName().equals(property.substring(5))) { - return false; - } - } else if (property.startsWith("notnamed")) { - if (this.getName().equals(property.substring(8))) { - return false; - } - } else if (property.startsWith("sameName")) { - if (!this.getName().equals(source.getName())) { - return false; - } - } else if (property.equals("NamedCard")) { - if (!this.getName().equals(source.getNamedCard())) { - return false; - } - } else if (property.equals("NamedByRememberedPlayer")) { - if (source.getRemembered().isEmpty()) { - final Card newCard = game.getCardState(source); - for (final Object o : newCard.getRemembered()) { - if (o instanceof Player) { - if (!this.getName().equals(((Player) o).getNamedCard())) { - return false; - } - } - } - } - for (final Object o : source.getRemembered()) { - if (o instanceof Player) { - if (!this.getName().equals(((Player) o).getNamedCard())) { - return false; - } - } - } - } else if (property.equals("ChosenCard")) { - if (!source.getChosenCard().contains(this)) { - return false; - } - } else if (property.equals("nonChosenCard")) { - if (source.getChosenCard().contains(this)) { - return false; - } - } - // ... Card colors - else if (property.contains("White") || property.contains("Blue") || property.contains("Black") - || property.contains("Red") || property.contains("Green")) { - boolean mustHave = !property.startsWith("non"); - int desiredColor = MagicColor.fromName(mustHave ? property : property.substring(3)); - boolean hasColor = CardUtil.getColors(this).hasAnyColor(desiredColor); - if (mustHave != hasColor) - return false; - - } else if (property.contains("Colorless")) { // ... Card is colorless - if (property.startsWith("non") == CardUtil.getColors(this).isColorless()) return false; - - } else if (property.contains("MultiColor")) { // ... Card is multicolored - if (property.startsWith("non") == CardUtil.getColors(this).isMulticolor()) return false; - - } else if (property.contains("MonoColor")) { // ... Card is monocolored - if (property.startsWith("non") == CardUtil.getColors(this).isMonoColor()) return false; - - } else if (property.equals("ChosenColor")) { - if (source.getChosenColor().isEmpty() || !CardUtil.getColors(this).hasAnyColor(MagicColor.fromName(source.getChosenColor().get(0)))) - return false; - - } else if (property.equals("AllChosenColors")) { - if (source.getChosenColor().isEmpty() || !CardUtil.getColors(this).hasAllColors(ColorSet.fromNames(source.getChosenColor()).getColor())) - return false; - - } else if (property.equals("AnyChosenColor")) { - if (source.getChosenColor().isEmpty() || !CardUtil.getColors(this).hasAnyColor(ColorSet.fromNames(source.getChosenColor()).getColor())) - return false; - - } else if (property.equals("DoubleFaced")) { - if (!this.isDoubleFaced()) { - return false; - } - } else if (property.equals("Flip")) { - if (!this.isFlipCard()) { - return false; - } - } else if (property.startsWith("YouCtrl")) { - if (!this.getController().equals(sourceController)) { - return false; - } - } else if (property.startsWith("YouDontCtrl")) { - if (this.getController().equals(sourceController)) { - return false; - } - } else if (property.startsWith("OppCtrl")) { - if (!this.getController().getOpponents().contains(sourceController)) { - return false; - } - } else if (property.startsWith("ChosenCtrl")) { - if (!this.getController().equals(source.getChosenPlayer())) { - return false; - } - } else if (property.startsWith("DefenderCtrl")) { - if (!game.getPhaseHandler().inCombat()) { - return false; - } - if (property.endsWith("ForRemembered")) { - if (source.getRemembered().isEmpty()) { - return false; - } - if (getGame().getCombat().getDefendingPlayerRelatedTo((Card) source.getRemembered().get(0)) - != this.getController()) { - return false; - } - } else { - if (getGame().getCombat().getDefendingPlayerRelatedTo(source) != this.getController()) { - return false; - } - } - } else if (property.startsWith("DefendingPlayerCtrl")) { - if (!game.getPhaseHandler().inCombat()) { - return false; - } - if (!getGame().getCombat().isPlayerAttacked(this.getController())) { - return false; - } - } else if (property.startsWith("EnchantedPlayerCtrl")) { - final Object o = source.getEnchanting(); - if (o instanceof Player) { - if (!this.getController().equals(o)) { - return false; - } - } else { // source not enchanting a player - return false; - } - } else if (property.startsWith("EnchantedControllerCtrl")) { - final Object o = source.getEnchanting(); - if (o instanceof Card) { - if (!this.getController().equals(((Card) o).getController())) { - return false; - } - } else { // source not enchanting a card - return false; - } - } else if (property.startsWith("RememberedPlayer")) { - Player p = property.endsWith("Ctrl") ? this.getController() : this.getOwner(); - if (source.getRemembered().isEmpty()) { - final Card newCard = game.getCardState(source); - for (final Object o : newCard.getRemembered()) { - if (o instanceof Player) { - if (!p.equals(o)) { - return false; - } - } - } - } - - for (final Object o : source.getRemembered()) { - if (o instanceof Player) { - if (!p.equals(o)) { - return false; - } - } - } - } else if (property.startsWith("nonRememberedPlayerCtrl")) { - if (source.getRemembered().isEmpty()) { - final Card newCard = game.getCardState(source); - if (newCard.getRemembered().contains(this.getController())) { - return false; - } - } - - if (source.getRemembered().contains(this.getController())) { - return false; - } - } else if (property.equals("TargetedPlayerCtrl")) { - for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { - final SpellAbility saTargeting = sa.getSATargetingPlayer(); - if (saTargeting != null) { - for (final Player p : saTargeting.getTargets().getTargetPlayers()) { - if (!this.getController().equals(p)) { - return false; - } - } - } - } - } else if (property.equals("TargetedControllerCtrl")) { - for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { - final List list = AbilityUtils.getDefinedCards(source, "Targeted", sa); - final List sas = AbilityUtils.getDefinedSpellAbilities(source, "Targeted", sa); - for (final Card c : list) { - final Player p = c.getController(); - if (!this.getController().equals(p)) { - return false; - } - } - for (final SpellAbility s : sas) { - final Player p = s.getSourceCard().getController(); - if (!this.getController().equals(p)) { - return false; - } - } - } - } else if (property.startsWith("ActivePlayerCtrl")) { - if (!game.getPhaseHandler().isPlayerTurn(this.getController())) { - return false; - } - } else if (property.startsWith("NonActivePlayerCtrl")) { - if (game.getPhaseHandler().isPlayerTurn(this.getController())) { - return false; - } - } else if (property.startsWith("YouOwn")) { - if (!this.getOwner().equals(sourceController)) { - return false; - } - } else if (property.startsWith("YouDontOwn")) { - if (this.getOwner().equals(sourceController)) { - return false; - } - } else if (property.startsWith("OppOwn")) { - if (!this.getOwner().getOpponents().contains(sourceController)) { - return false; - } - } else if (property.equals("TargetedPlayerOwn")) { - for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { - final SpellAbility saTargeting = sa.getSATargetingPlayer(); - if (saTargeting != null) { - for (final Player p : saTargeting.getTargets().getTargetPlayers()) { - if (!this.getOwner().equals(p)) { - return false; - } - } - } - } - } else if (property.startsWith("OwnedBy")) { - final String valid = property.substring(8); - if (!this.getOwner().isValid(valid, sourceController, source)) { - return false; - } - } else if (property.startsWith("OwnerDoesntControl")) { - if (this.getOwner().equals(this.getController())) { - return false; - } - } else if (property.startsWith("ControllerControls")) { - final String type = property.substring(18); - if (type.startsWith("AtLeastAsMany")) { - String realType = type.split("AtLeastAsMany")[1]; - List list = CardLists.getType(this.getController().getCardsIn(ZoneType.Battlefield), realType); - List yours = CardLists.getType(sourceController.getCardsIn(ZoneType.Battlefield), realType); - if (list.size() < yours.size()) { - return false; - } - } else { - final List list = this.getController().getCardsIn(ZoneType.Battlefield); - if (CardLists.getType(list, type).isEmpty()) { - return false; - } - } - } else if (property.startsWith("Other")) { - if (this.equals(source)) { - return false; - } - } else if (property.startsWith("Self")) { - if (!this.equals(source)) { - return false; - } - } else if (property.startsWith("AttachedBy")) { - if (!this.equippedBy.contains(source) && !this.getEnchantedBy().contains(source) && !this.getFortifiedBy().contains(source)) { - return false; - } - } else if (property.equals("Attached")) { - if (!this.equipping.contains(source) && !source.equals(this.enchanting) && !this.fortifying.contains(source)) { - return false; - } - } else if (property.startsWith("AttachedTo")) { - final String restriction = property.split("AttachedTo ")[1]; - if (restriction.equals("Targeted")) { - if (!source.getCharacteristics().getTriggers().isEmpty()) { - for (final Trigger t : source.getCharacteristics().getTriggers()) { - final SpellAbility sa = t.getTriggeredSA(); - final List list = AbilityUtils.getDefinedCards(source, "Targeted", sa); - for (final Card c : list) { - if (!this.equipping.contains(c) && !c.equals(this.enchanting)) { - return false; - } - } - } - } else { - for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { - final List list = AbilityUtils.getDefinedCards(source, "Targeted", sa); - for (final Card c : list) { - if (!this.equipping.contains(c) && !c.equals(this.enchanting)) { - return false; - } - } - } - } - } else { - if (((this.enchanting == null) || !this.enchanting.isValid(restriction, sourceController, source)) - && (this.equipping.isEmpty() || !this.equipping.get(0).isValid(restriction, sourceController, source)) - && (this.fortifying.isEmpty() || !this.fortifying.get(0).isValid(restriction, sourceController, source))) { - return false; - } - } - } else if (property.equals("NameNotEnchantingEnchantedPlayer")) { - Player enchantedPlayer = source.getEnchantingPlayer(); - if (enchantedPlayer == null) { - return false; - } - - List enchanting = enchantedPlayer.getEnchantedBy(); - for (Card c : enchanting) { - if (this.getName().equals(c.getName())) { - return false; - } - } - } else if (property.equals("NotAttachedTo")) { - if (this.equipping.contains(source) || source.equals(this.enchanting) || this.fortifying.contains(source)) { - return false; - } - } else if (property.startsWith("EnchantedBy")) { - if (property.equals("EnchantedBy")) { - if (!this.getEnchantedBy().contains(source) && !this.equals(source.getEnchanting())) { - return false; - } - } else { - final String restriction = property.split("EnchantedBy ")[1]; - if (restriction.equals("Imprinted")) { - for (final Card card : source.getImprinted()) { - if (!this.getEnchantedBy().contains(card) && !this.equals(card.getEnchanting())) { - return false; - } - } - } else if (restriction.equals("Targeted")) { - for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { - final SpellAbility saTargeting = sa.getSATargetingCard(); - if (saTargeting != null) { - for (final Card c : saTargeting.getTargets().getTargetCards()) { - if (!this.getEnchantedBy().contains(c) && !this.equals(c.getEnchanting())) { - return false; - } - } - } - } - } - } - } else if (property.startsWith("NotEnchantedBy")) { - if (property.substring(14).equals("Targeted")) { - for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { - final SpellAbility saTargeting = sa.getSATargetingCard(); - if (saTargeting != null) { - for (final Card c : saTargeting.getTargets().getTargetCards()) { - if (this.getEnchantedBy().contains(c)) { - return false; - } - } - } - } - } else { - if (this.getEnchantedBy().contains(source)) { - return false; - } - } - } else if (property.startsWith("Enchanted")) { - if (!source.equals(this.enchanting)) { - return false; - } - } else if (property.startsWith("CanEnchant")) { - final String restriction = property.substring(10); - if (restriction.equals("Remembered")) { - for (final Object rem : source.getRemembered()) { - if (!(rem instanceof Card) || !((Card) rem).canBeEnchantedBy(this)) - return false; - } - } else if (restriction.equals("Source")) { - if (!source.canBeEnchantedBy(this)) return false; - } - } else if (property.startsWith("CanBeEnchantedBy")) { - if (property.substring(16).equals("Targeted")) { - for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { - final SpellAbility saTargeting = sa.getSATargetingCard(); - if (saTargeting != null) { - for (final Card c : saTargeting.getTargets().getTargetCards()) { - if (!this.canBeEnchantedBy(c)) { - return false; - } - } - } - } - } else if (property.substring(16).equals("AllRemembered")) { - for (final Object rem : source.getRemembered()) { - if (rem instanceof Card) { - final Card card = (Card) rem; - if (!this.canBeEnchantedBy(card)) { - return false; - } - } - } - } else { - if (!this.canBeEnchantedBy(source)) { - return false; - } - } - } else if (property.startsWith("EquippedBy")) { - if (property.substring(10).equals("Targeted")) { - for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { - final SpellAbility saTargeting = sa.getSATargetingCard(); - if (saTargeting != null) { - for (final Card c : saTargeting.getTargets().getTargetCards()) { - if (!this.equippedBy.contains(c)) { - return false; - } - } - } - } - } else if (property.substring(10).equals("Enchanted")) { - if (source.getEnchantingCard() == null || - !this.equippedBy.contains(source.getEnchantingCard())) { - return false; - } - } else { - if (!this.equippedBy.contains(source)) { - return false; - } - } - } else if (property.startsWith("FortifiedBy")) { - if (!this.fortifiedBy.contains(source)) { - return false; - } - } else if (property.startsWith("CanBeEquippedBy")) { - if (!this.canBeEquippedBy(source)) { - return false; - } - } else if (property.startsWith("Equipped")) { - if (!this.equipping.contains(source)) { - return false; - } - } else if (property.startsWith("Fortified")) { - if (!this.fortifying.contains(source)) { - return false; - } - } else if (property.startsWith("HauntedBy")) { - if (!this.hauntedBy.contains(source)) { - return false; - } - } else if (property.startsWith("notTributed")) { - if (this.tributed) { - return false; - } - } else if (property.contains("Paired")) { - if (property.contains("With")) { // PairedWith - if (!this.isPaired() || this.pairedWith != source) { - return false; - } - } else if (property.startsWith("Not")) { // NotPaired - if (this.isPaired()) { - return false; - } - } else { // Paired - if (!this.isPaired()) { - return false; - } - } - } else if (property.startsWith("Above")) { // "Are Above" Source - final List list = this.getOwner().getCardsIn(ZoneType.Graveyard); - if (list.indexOf(source) >= list.indexOf(this)) { - return false; - } - } else if (property.startsWith("DirectlyAbove")) { // "Are Directly Above" Source - final List list = this.getOwner().getCardsIn(ZoneType.Graveyard); - if (list.indexOf(this) - list.indexOf(source) != 1) { - return false; - } - } else if (property.startsWith("TopGraveyardCreature")) { - List list = CardLists.filter(this.getOwner().getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES); - Collections.reverse(list); - if (list.isEmpty() || !this.equals(list.get(0))) { - return false; - } - } else if (property.startsWith("TopGraveyard")) { - final List list = new ArrayList(this.getOwner().getCardsIn(ZoneType.Graveyard)); - Collections.reverse(list); - if (property.substring(12).matches("[0-9][0-9]?")) { - int n = Integer.parseInt(property.substring(12)); - int num = Math.min(n, list.size()); - final List newlist = new ArrayList(); - for (int i = 0; i < num; i++) { - newlist.add(list.get(i)); - } - if (list.isEmpty() || !newlist.contains(this)) { - return false; - } - } else { - if (list.isEmpty() || !this.equals(list.get(0))) { - return false; - } - } - } else if (property.startsWith("BottomGraveyard")) { - final List list = this.getOwner().getCardsIn(ZoneType.Graveyard); - if (list.isEmpty() || !this.equals(list.get(0))) { - return false; - } - } else if (property.startsWith("TopLibrary")) { - final List list = this.getOwner().getCardsIn(ZoneType.Library); - if (list.isEmpty() || !this.equals(list.get(0))) { - return false; - } - } else if (property.startsWith("Cloned")) { - if ((this.cloneOrigin == null) || !this.cloneOrigin.equals(source)) { - return false; - } - } else if (property.startsWith("DamagedBy")) { - if (!this.receivedDamageFromThisTurn.containsKey(source)) { - return false; - } - } else if (property.startsWith("Damaged")) { - if (!this.dealtDamageToThisTurn.containsKey(source)) { - return false; - } - } else if (property.startsWith("IsTargetingSource")) { - for (final SpellAbility sa : this.getCharacteristics().getSpellAbility()) { - final SpellAbility saTargeting = sa.getSATargetingCard(); - if (saTargeting != null) { - for (final Card c : saTargeting.getTargets().getTargetCards()) { - if (c.equals(source)) { - return true; - } - } - } - } - return false; - } else if (property.startsWith("SharesColorWith")) { - if (property.equals("SharesColorWith")) { - if (!this.sharesColorWith(source)) { - return false; - } - } else { - final String restriction = property.split("SharesColorWith ")[1]; - if (restriction.equals("TopCardOfLibrary")) { - final List list = sourceController.getCardsIn(ZoneType.Library); - if (list.isEmpty() || !this.sharesColorWith(list.get(0))) { - return false; - } - } else if (restriction.equals("Remembered")) { - for (final Object obj : source.getRemembered()) { - if (!(obj instanceof Card) || !this.sharesColorWith((Card) obj)) { - return false; - } - } - } else if (restriction.equals("Imprinted")) { - for (final Card card : source.getImprinted()) { - if (!this.sharesColorWith(card)) { - return false; - } - } - } else if (restriction.equals("Equipped")) { - if (!source.isEquipment() || !source.isEquipping() - || !this.sharesColorWith(source.getEquippingCard())) { - return false; - } - } else if (restriction.equals("MostProminentColor")) { - byte mask = CardFactoryUtil.getMostProminentColors(game.getCardsIn(ZoneType.Battlefield)); - if (!CardUtil.getColors(this).hasAnyColor(mask)) - return false; - } else if (restriction.equals("LastCastThisTurn")) { - final List c = game.getStack().getCardsCastThisTurn(); - if (c.isEmpty() || !this.sharesColorWith(c.get(c.size() - 1))) { - return false; - } - } else if (restriction.equals("ActivationColor")) { - byte manaSpent = source.getColorsPaid(); - if (!CardUtil.getColors(this).hasAnyColor(manaSpent)) { - return false; - } - } else { - for (final Card card : sourceController.getCardsIn(ZoneType.Battlefield)) { - if (card.isValid(restriction, sourceController, source) && this.sharesColorWith(card)) { - return true; - } - } - return false; - } - } - } else if (property.startsWith("MostProminentColor")) { - // MostProminentColor - // e.g. MostProminentColor black - String[] props = property.split(" "); - if (props.length == 1) { - System.out.println("WARNING! Using MostProminentColor property without a color."); - return false; - } - String color = props[1]; - - byte mostProm = CardFactoryUtil.getMostProminentColors(game.getCardsIn(ZoneType.Battlefield)); - return ColorSet.fromMask(mostProm).hasAnyColor(MagicColor.fromName(color)); - } else if (property.startsWith("notSharesColorWith")) { - if (property.equals("notSharesColorWith")) { - if (this.sharesColorWith(source)) { - return false; - } - } else { - final String restriction = property.split("notSharesColorWith ")[1]; - for (final Card card : sourceController.getCardsIn(ZoneType.Battlefield)) { - if (card.isValid(restriction, sourceController, source) && this.sharesColorWith(card)) { - return false; - } - } - } - } else if (property.startsWith("sharesCreatureTypeWith")) { - if (property.equals("sharesCreatureTypeWith")) { - if (!this.sharesCreatureTypeWith(source)) { - return false; - } - } else { - final String restriction = property.split("sharesCreatureTypeWith ")[1]; - if (restriction.equals("TopCardOfLibrary")) { - final List list = sourceController.getCardsIn(ZoneType.Library); - if (list.isEmpty() || !this.sharesCreatureTypeWith(list.get(0))) { - return false; - } - } if (restriction.equals("Enchanted")) { - for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { - final SpellAbility root = sa.getRootAbility(); - Card c = source.getEnchantingCard(); - if ((c == null) && (root != null) - && (root.getPaidList("Sacrificed") != null) - && !root.getPaidList("Sacrificed").isEmpty()) { - c = root.getPaidList("Sacrificed").get(0).getEnchantingCard(); - if (!this.sharesCreatureTypeWith(c)) { - return false; - } - } - } - } if (restriction.equals("Equipped")) { - if (source.isEquipping() && this.sharesCreatureTypeWith(source.getEquippingCard())) { - return true; - } - return false; - } if (restriction.equals("Remembered")) { - for (final Object rem : source.getRemembered()) { - if (rem instanceof Card) { - final Card card = (Card) rem; - if (this.sharesCreatureTypeWith(card)) { - return true; - } - } - } - return false; - } if (restriction.equals("AllRemembered")) { - for (final Object rem : source.getRemembered()) { - if (rem instanceof Card) { - final Card card = (Card) rem; - if (!this.sharesCreatureTypeWith(card)) { - return false; - } - } - } - } else { - boolean shares = false; - for (final Card card : sourceController.getCardsIn(ZoneType.Battlefield)) { - if (card.isValid(restriction, sourceController, source) && this.sharesCreatureTypeWith(card)) { - shares = true; - } - } - if (!shares) { - return false; - } - } - } - } else if (property.startsWith("sharesCardTypeWith")) { - if (property.equals("sharesCardTypeWith")) { - if (!this.sharesCardTypeWith(source)) { - return false; - } - } else { - final String restriction = property.split("sharesCardTypeWith ")[1]; - if (restriction.equals("Imprinted")) { - if (source.getImprinted().isEmpty() || !this.sharesCardTypeWith(source.getImprinted().get(0))) { - return false; - } - } else if (restriction.equals("Remembered")) { - for (final Object rem : source.getRemembered()) { - if (rem instanceof Card) { - final Card card = (Card) rem; - if (this.sharesCardTypeWith(card)) { - return true; - } - } - } - return false; - } else if (restriction.equals("EachTopLibrary")) { - final List list = new ArrayList(); - for (Player p : game.getPlayers()) { - final Card top = p.getCardsIn(ZoneType.Library).get(0); - list.add(top); - } - for (Card c : list) { - if (this.sharesCardTypeWith(c)) { - return true; - } - } - return false; - } - } - } else if (property.startsWith("sharesNameWith")) { - if (property.equals("sharesNameWith")) { - if (!this.getName().equals(source.getName())) { - return false; - } - } else { - final String restriction = property.split("sharesNameWith ")[1]; - if (restriction.equals("YourGraveyard")) { - for (final Card card : sourceController.getCardsIn(ZoneType.Graveyard)) { - if (this.getName().equals(card.getName())) { - return true; - } - } - return false; - } else if (restriction.equals(ZoneType.Graveyard.toString())) { - for (final Card card : game.getCardsIn(ZoneType.Graveyard)) { - if (this.getName().equals(card.getName())) { - return true; - } - } - return false; - } else if (restriction.equals(ZoneType.Battlefield.toString())) { - for (final Card card : game.getCardsIn(ZoneType.Battlefield)) { - if (this.getName().equals(card.getName())) { - return true; - } - } - return false; - } else if (restriction.equals("ThisTurnCast")) { - for (final Card card : CardUtil.getThisTurnCast("Card", source)) { - if (this.getName().equals(card.getName())) { - return true; - } - } - return false; - } else if (restriction.equals("Remembered")) { - for (final Object rem : source.getRemembered()) { - if (rem instanceof Card) { - final Card card = (Card) rem; - if (this.getName().equals(card.getName())) { - return true; - } - } - } - return false; - } else if (restriction.equals("Imprinted")) { - for (final Card card : source.getImprinted()) { - if (this.getName().equals(card.getName())) { - return true; - } - } - return false; - } else if (restriction.equals("MovedToGrave")) { - for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { - final SpellAbility root = sa.getRootAbility(); - if (root != null && (root.getPaidList("MovedToGrave") != null) - && !root.getPaidList("MovedToGrave").isEmpty()) { - List list = root.getPaidList("MovedToGrave"); - for (Card card : list) { - if (this.getName().equals(card.getName())) { - return true; - } - } - } - } - return false; - } else if (restriction.equals("NonToken")) { - final List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), - Presets.NON_TOKEN); - for (final Card card : list) { - if (this.getName().equals(card.getName())) { - return true; - } - } - return false; - } - } - } else if (property.startsWith("doesNotShareNameWith")) { - if (property.equals("doesNotShareNameWith")) { - if (this.getName().equals(source.getName())) { - return false; - } - } else { - final String restriction = property.split("doesNotShareNameWith ")[1]; - if (restriction.equals("Remembered")) { - for (final Object rem : source.getRemembered()) { - if (rem instanceof Card) { - final Card card = (Card) rem; - if (this.getName().equals(card.getName())) { - return false; - } - } - } - } - } - } else if (property.startsWith("sharesControllerWith")) { - if (property.equals("sharesControllerWith")) { - if (!this.sharesControllerWith(source)) { - return false; - } - } else { - final String restriction = property.split("sharesControllerWith ")[1]; - if (restriction.equals("Remembered")) { - for (final Object rem : source.getRemembered()) { - if (rem instanceof Card) { - final Card card = (Card) rem; - if (!this.sharesControllerWith(card)) { - return false; - } - } - } - } else if (restriction.equals("Imprinted")) { - for (final Card card : source.getImprinted()) { - if (!this.sharesControllerWith(card)) { - return false; - } - } - } - } - } else if (property.startsWith("sharesOwnerWith")) { - if (property.equals("sharesOwnerWith")) { - if (!this.getOwner().equals(source.getOwner())) { - return false; - } - } else { - final String restriction = property.split("sharesOwnerWith ")[1]; - if (restriction.equals("Remembered")) { - for (final Object rem : source.getRemembered()) { - if (rem instanceof Card) { - final Card card = (Card) rem; - if (!this.getOwner().equals(card.getOwner())) { - return false; - } - } - } - } - } - } else if (property.startsWith("SecondSpellCastThisTurn")) { - final List list = CardUtil.getThisTurnCast("Card", source); - if (list.size() < 2) { - return false; - } - else if (list.get(1) != this) { - return false; - } - } else if (property.equals("ThisTurnCast")) { - for (final Card card : CardUtil.getThisTurnCast("Card", source)) { - if (this.equals(card)) { - return true; - } - } - return false; - } else if (property.startsWith("ThisTurnEntered")) { - final String restrictions = property.split("ThisTurnEntered_")[1]; - final String[] res = restrictions.split("_"); - final ZoneType destination = ZoneType.smartValueOf(res[0]); - ZoneType origin = null; - if (res[1].equals("from")) { - origin = ZoneType.smartValueOf(res[2]); - } - List list = CardUtil.getThisTurnEntered(destination, - origin, "Card", source); - if (!list.contains(this)) { - return false; - } - } else if (property.startsWith("sharesTypeWith")) { - if (property.equals("sharesTypeWith")) { - if (!this.sharesTypeWith(source)) { - return false; - } - } else { - final String restriction = property.split("sharesTypeWith ")[1]; - if (restriction.equals("FirstImprinted")) { - if (source.getImprinted().isEmpty() || - !this.sharesTypeWith(source.getImprinted().get(0))) { - return false; - } - } - } - } else if (property.startsWith("withFlashback")) { - boolean fb = false; - if (this.hasStartOfUnHiddenKeyword("Flashback")) { - fb = true; - } - for (final SpellAbility sa : this.getSpellAbilities()) { - if (sa.isFlashBackAbility()) { - fb = true; - } - } - if (!fb) { - return false; - } - } else if (property.startsWith("with")) { - // ... Card keywords - if (property.startsWith("without") && this.hasStartOfUnHiddenKeyword(property.substring(7))) { - return false; - } - if (!property.startsWith("without") && !this.hasStartOfUnHiddenKeyword(property.substring(4))) { - return false; - } - } else if (property.startsWith("tapped")) { - if (!this.isTapped()) { - return false; - } - } else if (property.startsWith("untapped")) { - if (!this.isUntapped()) { - return false; - } - } else if (property.startsWith("faceDown")) { - if (!this.isFaceDown()) { - return false; - } - } else if (property.startsWith("faceUp")) { - if (this.isFaceDown()) { - return false; - } - } else if (property.startsWith("hasLevelUp")) { - for (final SpellAbility sa : this.getSpellAbilities()) { - if (sa.getApi() == ApiType.PutCounter && sa.hasParam("LevelUp")) { - return true; - } - } - return false; - } else if (property.startsWith("DrawnThisTurn")) { - if (!this.getDrawnThisTurn()) { - return false; - } - } else if (property.startsWith("enteredBattlefieldThisTurn")) { - if (!(this.getTurnInZone() == game.getPhaseHandler().getTurn())) { - return false; - } - } else if (property.startsWith("notEnteredBattlefieldThisTurn")) { - if (this.getTurnInZone() == game.getPhaseHandler().getTurn()) { - return false; - } - } else if (property.startsWith("firstTurnControlled")) { - if (!this.isFirstTurnControlled()) { - return false; - } - } else if (property.startsWith("notFirstTurnControlled")) { - if (this.isFirstTurnControlled()) { - return false; - } - } else if (property.startsWith("startedTheTurnUntapped")) { - if (!this.hasStartedTheTurnUntapped()) { - return false; - } - } else if (property.equals("attackedOrBlockedSinceYourLastUpkeep")) { - if (!this.getDamageHistory().hasAttackedSinceLastUpkeepOf(sourceController) - && !this.getDamageHistory().hasBlockedSinceLastUpkeepOf(sourceController)) { - return false; - } - } else if (property.equals("blockedOrBeenBlockedSinceYourLastUpkeep")) { - if (!this.getDamageHistory().hasBeenBlockedSinceLastUpkeepOf(sourceController) - && !this.getDamageHistory().hasBlockedSinceLastUpkeepOf(sourceController)) { - return false; - } - } else if (property.startsWith("dealtDamageToYouThisTurn")) { - if (!this.getDamageHistory().getThisTurnDamaged().contains(sourceController)) { - return false; - } - } else if (property.startsWith("dealtDamageToOppThisTurn")) { - if (!this.getDamageHistory().getThisTurnDamaged().contains(sourceController.getOpponent())) { - return false; - } - } else if (property.startsWith("controllerWasDealtCombatDamageByThisTurn")) { - if (!source.getDamageHistory().getThisTurnCombatDamaged().contains(this.getController())) { - return false; - } - } else if (property.startsWith("controllerWasDealtDamageByThisTurn")) { - if (!source.getDamageHistory().getThisTurnDamaged().contains(this.getController())) { - return false; - } - } else if (property.startsWith("wasDealtDamageThisTurn")) { - if ((this.getReceivedDamageFromThisTurn().keySet()).isEmpty()) { - return false; - } - } else if (property.equals("wasDealtDamageByHostThisTurn")) { - if (!this.getReceivedDamageFromThisTurn().keySet().contains(source)) { - return false; - } - } else if (property.equals("wasDealtDamageByEquipeeThisTurn")) { - Card equipee = source.getEquippingCard(); - if (equipee == null || this.getReceivedDamageFromThisTurn().keySet().isEmpty() - || !this.getReceivedDamageFromThisTurn().keySet().contains(equipee)) { - return false; - } - } else if (property.equals("wasDealtDamageByEnchantedThisTurn")) { - Card enchanted = source.getEnchantingCard(); - if (enchanted == null || this.getReceivedDamageFromThisTurn().keySet().isEmpty() - || !this.getReceivedDamageFromThisTurn().keySet().contains(enchanted)) { - return false; - } - } else if (property.startsWith("dealtDamageThisTurn")) { - if (this.getTotalDamageDoneBy() == 0) { - return false; - } - } else if (property.startsWith("attackedThisTurn")) { - if (!this.getDamageHistory().getCreatureAttackedThisTurn()) { - return false; - } - } else if (property.startsWith("attackedLastTurn")) { - return this.getDamageHistory().getCreatureAttackedLastTurnOf(this.getController()); - } else if (property.startsWith("blockedThisTurn")) { - if (!this.getDamageHistory().getCreatureBlockedThisTurn()) { - return false; - } - } else if (property.startsWith("gotBlockedThisTurn")) { - if (!this.getDamageHistory().getCreatureGotBlockedThisTurn()) { - return false; - } - } else if (property.startsWith("notAttackedThisTurn")) { - if (this.getDamageHistory().getCreatureAttackedThisTurn()) { - return false; - } - } else if (property.startsWith("notAttackedLastTurn")) { - return !this.getDamageHistory().getCreatureAttackedLastTurnOf(this.getController()); - } else if (property.startsWith("notBlockedThisTurn")) { - if (this.getDamageHistory().getCreatureBlockedThisTurn()) { - return false; - } - } else if (property.startsWith("greatestPower")) { - final List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); - for (final Card crd : list) { - if (crd.getNetAttack() > this.getNetAttack()) { - return false; - } - } - } else if (property.startsWith("yardGreatestPower")) { - final List list = CardLists.filter(sourceController.getCardsIn(ZoneType.Graveyard), Presets.CREATURES); - for (final Card crd : list) { - if (crd.getNetAttack() > this.getNetAttack()) { - return false; - } - } - } else if (property.startsWith("leastPower")) { - final List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); - for (final Card crd : list) { - if (crd.getNetAttack() < this.getNetAttack()) { - return false; - } - } - } else if (property.startsWith("leastToughness")) { - final List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); - for (final Card crd : list) { - if (crd.getNetDefense() < this.getNetDefense()) { - return false; - } - } - } else if (property.startsWith("greatestCMC")) { - List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); - if (property.contains("ControlledBy")) { - List p = AbilityUtils.getDefinedPlayers(source, property.split("ControlledBy")[1], null); - list = CardLists.filterControlledBy(list, p); - if (!list.contains(this)) { - return false; - } - } - for (final Card crd : list) { - if (crd.isSplitCard()) { - if (crd.getCMC(Card.SplitCMCMode.LeftSplitCMC) > this.getCMC() || crd.getCMC(Card.SplitCMCMode.RightSplitCMC) > this.getCMC()) { - return false; - } - } else { - if (crd.getCMC() > this.getCMC()) { - return false; - } - } - } - } else if (property.startsWith("greatestRememberedCMC")) { - List list = new ArrayList(); - for (final Object o : source.getRemembered()) { - if (o instanceof Card) { - list.add(game.getCardState((Card) o)); - } - } - if (!list.contains(this)) { - return false; - } - list = CardLists.getCardsWithHighestCMC(list); - if (!list.contains(this)) { - return false; - } - } else if (property.startsWith("lowestRememberedCMC")) { - List list = new ArrayList(); - for (final Object o : source.getRemembered()) { - if (o instanceof Card) { - list.add(game.getCardState((Card) o)); - } - } - if (!list.contains(this)) { - return false; - } - list = CardLists.getCardsWithLowestCMC(list); - if (!list.contains(this)) { - return false; - } - } else if (property.startsWith("lowestCMC")) { - final List list = game.getCardsIn(ZoneType.Battlefield); - for (final Card crd : list) { - if (!crd.isLand() && !crd.isImmutable()) { - if (crd.isSplitCard()) { - if (crd.getCMC(Card.SplitCMCMode.LeftSplitCMC) < this.getCMC() || crd.getCMC(Card.SplitCMCMode.RightSplitCMC) < this.getCMC()) { - return false; - } - } else { - if (crd.getCMC() < this.getCMC()) { - return false; - } - } - } - } - } else if (property.startsWith("enchanted")) { - if (!this.isEnchanted()) { - return false; - } - } else if (property.startsWith("unenchanted")) { - if (this.isEnchanted()) { - return false; - } - } else if (property.startsWith("enchanting")) { - if (!this.isEnchanting()) { - return false; - } - } else if (property.startsWith("equipped")) { - if (!this.isEquipped()) { - return false; - } - } else if (property.startsWith("unequipped")) { - if (this.isEquipped()) { - return false; - } - } else if (property.startsWith("equipping")) { - if (!this.isEquipping()) { - return false; - } - } else if (property.startsWith("token")) { - if (!this.isToken()) { - return false; - } - } else if (property.startsWith("nonToken")) { - if (this.isToken()) { - return false; - } - } else if (property.startsWith("hasXCost")) { - SpellAbility sa1 = this.getFirstSpellAbility(); - if (sa1 != null && !sa1.isXCost()) { - return false; - } - } else if (property.startsWith("suspended")) { - if (!this.hasSuspend() || !game.isCardExiled(this) - || !(this.getCounters(CounterType.getType("TIME")) >= 1)) { - return false; - } - - } else if (property.startsWith("power") || property.startsWith("toughness") - || property.startsWith("cmc") || property.startsWith("totalPT")) { - int x = 0; - int y = 0; - int y2 = -1; // alternative value for the second split face of a split card - String rhs = ""; - - if (property.startsWith("power")) { - rhs = property.substring(7); - y = this.getNetAttack(); - } else if (property.startsWith("toughness")) { - rhs = property.substring(11); - y = this.getNetDefense(); - } else if (property.startsWith("cmc")) { - rhs = property.substring(5); - if (isSplitCard() && getCurState() == CardCharacteristicName.Original) { - y = getState(CardCharacteristicName.LeftSplit).getManaCost().getCMC(); - y2 = getState(CardCharacteristicName.RightSplit).getManaCost().getCMC(); - } else { - y = getCMC(); - } - } else if (property.startsWith("totalPT")) { - rhs = property.substring(10); - y = this.getNetAttack() + this.getNetDefense(); - } - try { - x = Integer.parseInt(rhs); - } catch (final NumberFormatException e) { - x = CardFactoryUtil.xCount(source, source.getSVar(rhs)); - } - - if (y2 == -1) { - if (!Expressions.compare(y, property, x)) { - return false; - } - } else { - if (!Expressions.compare(y, property, x) && !Expressions.compare(y2, property, x)) { - return false; - } - } - } - - // syntax example: countersGE9 P1P1 or countersLT12TIME (greater number - // than 99 not supported) - /* - * slapshot5 - fair warning, you cannot use numbers with 2 digits - * (greater number than 9 not supported you can use X and the - * SVar:X:Number$12 to get two digits. This will need a better fix, and - * I have the beginnings of a regex below - */ - else if (property.startsWith("counters")) { - /* - * Pattern p = Pattern.compile("[a-z]*[A-Z][A-Z][X0-9]+.*$"); - * String[] parse = ??? - * System.out.println("Parsing completed of: "+Property); for (int i - * = 0; i < parse.length; i++) { - * System.out.println("parse["+i+"]: "+parse[i]); } - */ - - // TODO get a working regex out of this pattern so the amount of - // digits doesn't matter - int number = 0; - final String[] splitProperty = property.split("_"); - final String strNum = splitProperty[1].substring(2); - final String comparator = splitProperty[1].substring(0, 2); - String counterType = ""; - try { - number = Integer.parseInt(strNum); - } catch (final NumberFormatException e) { - number = CardFactoryUtil.xCount(source, source.getSVar(strNum)); - } - counterType = splitProperty[2]; - - final int actualnumber = this.getCounters(CounterType.getType(counterType)); - - if (!Expressions.compare(actualnumber, comparator, number)) { - return false; - } - } - // These predicated refer to ongoing combat. If no combat happens, they'll return false (meaning not attacking/blocking ATM) - else if (property.startsWith("attacking")) { - if (null == combat) return false; - if (property.equals("attacking")) return combat.isAttacking(this); - if (property.equals("attackingYou")) return combat.isAttacking(this, sourceController); - } else if (property.startsWith("notattacking")) { - return null == combat || !combat.isAttacking(this); - } else if (property.equals("attackedBySourceThisCombat")) { - if (null == combat) return false; - final GameEntity defender = combat.getDefenderByAttacker(source); - if (defender instanceof Card) { - if (!this.equals((Card) defender)) { - return false; - } - } - } else if (property.startsWith("blocking")) { - if (null == combat) return false; - String what = property.substring("blocking".length()); - - if (StringUtils.isEmpty(what)) return combat.isBlocking(this); - if (what.startsWith("Source")) return combat.isBlocking(this, source) ; - if (what.startsWith("CreatureYouCtrl")) { - for (final Card c : CardLists.filter(sourceController.getCardsIn(ZoneType.Battlefield), Presets.CREATURES)) - if (combat.isBlocking(this, c)) - return true; - return false; - } - if (what.startsWith("Remembered")) { - for (final Object o : source.getRemembered()) { - if (o instanceof Card && combat.isBlocking(this, (Card) o)) { - return true; - } - } - return false; - } - } else if (property.startsWith("sharesBlockingAssignmentWith")) { - if (null == combat) { return false; } - if (null == combat.getAttackersBlockedBy(source) || null == combat.getAttackersBlockedBy(this)) { return false; } - - List sourceBlocking = new ArrayList(combat.getAttackersBlockedBy(source)); - List thisBlocking = new ArrayList(combat.getAttackersBlockedBy(this)); - if (Collections.disjoint(sourceBlocking, thisBlocking)) { - return false; - } - } else if (property.startsWith("notblocking")) { - return null == combat || !combat.isBlocking(this); - } - // Nex predicates refer to past combat and don't need a reference to actual combat - else if (property.equals("blocked")) { - return null != combat && combat.isBlocked(this); - } else if (property.startsWith("blockedBySource")) { - return null != combat && combat.isBlocking(source, this); - } else if (property.startsWith("blockedThisTurn")) { - return this.getBlockedThisTurn() != null; - } else if (property.startsWith("blockedByThisTurn")) { - return this.getBlockedByThisTurn() != null; - } else if (property.startsWith("blockedBySourceThisTurn")) { - return source.getBlockedByThisTurn() != null && source.getBlockedByThisTurn().contains(this); - } else if (property.startsWith("blockedSource")) { - return null != combat && combat.isBlocking(this, source); - } else if (property.startsWith("isBlockedByRemembered")) { - if (null == combat) return false; - for (final Object o : source.getRemembered()) { - if (o instanceof Card && combat.isBlocking((Card) o, this)) { - return true; - } - } - return false; - } else if (property.startsWith("blockedRemembered")) { - Card rememberedcard; - for (final Object o : source.getRemembered()) { - if (o instanceof Card) { - rememberedcard = (Card) o; - if (this.getBlockedThisTurn() == null || !this.getBlockedThisTurn().contains(rememberedcard)) { - return false; - } - } - } - } else if (property.startsWith("blockedByRemembered")) { - Card rememberedcard; - for (final Object o : source.getRemembered()) { - if (o instanceof Card) { - rememberedcard = (Card) o; - if (this.getBlockedByThisTurn() == null || !this.getBlockedByThisTurn().contains(rememberedcard)) { - return false; - } - } - } - } else if (property.startsWith("unblocked")) { - if (game.getCombat() == null || !game.getCombat().isUnblocked(this)) { - return false; - } - } else if (property.equals("attackersBandedWith")) { - if (this.equals(source)) { - // You don't band with yourself - return false; - } - AttackingBand band = combat == null ? null : combat.getBandOfAttacker(source); - if (band == null || !band.getAttackers().contains(this)) { - return false; - } - } else if (property.startsWith("kicked")) { - if (property.equals("kicked")) { - if (this.getKickerMagnitude() == 0) { - return false; - } - } else { - String s = property.split("kicked ")[1]; - if ("1".equals(s) && !this.isOptionalCostPaid(OptionalCost.Kicker1)) return false; - if ("2".equals(s) && !this.isOptionalCostPaid(OptionalCost.Kicker2)) return false; - } - } else if (property.startsWith("notkicked")) { - if (this.getKickerMagnitude() > 0) { - return false; - } - } else if (property.startsWith("evoked")) { - if (!this.isEvoked()) { - return false; - } - } else if (property.equals("HasDevoured")) { - if (this.devouredCards.size() == 0) { - return false; - } - } else if (property.equals("HasNotDevoured")) { - if (this.devouredCards.size() != 0) { - return false; - } - } else if (property.equals("IsMonstrous")) { - if (!this.isMonstrous()) { - return false; - } - } else if (property.equals("IsNotMonstrous")) { - if (this.isMonstrous()) { - return false; - } - } else if (property.startsWith("non")) { - // ... Other Card types - if (this.isType(property.substring(3))) { - return false; - } - } else if (property.equals("CostsPhyrexianMana")) { - if (!this.getCharacteristics().getManaCost().hasPhyrexian()) { - return false; - } - } else if (property.equals("IsRemembered")) { - if (!source.getRemembered().contains(this)) { - return false; - } - } else if (property.equals("IsNotRemembered")) { - if (source.getRemembered().contains(this)) { - return false; - } - } else if (property.equals("IsImprinted")) { - if (!source.getImprinted().contains(this)) { - return false; - } - } else if (property.equals("IsNotImprinted")) { - if (source.getImprinted().contains(this)) { - return false; - } - } else if (property.equals("hasActivatedAbilityWithTapCost")) { - for (final SpellAbility sa : this.getSpellAbilities()) { - if (sa.isAbility() && (sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) { - return true; - } - } - return false; - } else if (property.equals("hasActivatedAbility")) { - for (final SpellAbility sa : this.getSpellAbilities()) { - if (sa.isAbility()) { - return true; - } - } - return false; - } else if (property.equals("hasManaAbility")) { - for (final SpellAbility sa : this.getSpellAbilities()) { - if (sa.isManaAbility()) { - return true; - } - } - return false; - } else if (property.equals("hasNonManaActivatedAbility")) { - for (final SpellAbility sa : this.getSpellAbilities()) { - if (sa.isAbility() && !sa.isManaAbility()) { - return true; - } - } - return false; - } else if (property.equals("NoAbilities")) { - if (!((this.getAbilityText().trim().equals("") || this.isFaceDown()) && (this.getUnhiddenKeyword().size() == 0))) { - return false; - } - } else if (property.equals("HasCounters")) { - if (!this.hasCounters()) { - return false; - } - } else if (property.startsWith("wasCastFrom")) { - final String strZone = property.substring(11); - final ZoneType realZone = ZoneType.smartValueOf(strZone); - if (realZone != this.getCastFrom()) { - return false; - } - } else if (property.startsWith("wasNotCastFrom")) { - final String strZone = property.substring(14); - final ZoneType realZone = ZoneType.smartValueOf(strZone); - if (realZone == this.getCastFrom()) { - return false; - } - } else if (property.startsWith("set")) { - final String setCode = property.substring(3, 6); - if (!this.getCurSetCode().equals(setCode)) { - return false; - } - } else if (property.startsWith("inZone")) { - final String strZone = property.substring(6); - final ZoneType realZone = ZoneType.smartValueOf(strZone); - if (!this.isInZone(realZone)) { - return false; - } - } else if (property.equals("ChosenType")) { - if (!this.isType(source.getChosenType())) { - return false; - } - } else if (property.equals("IsNotChosenType")) { - if (this.isType(source.getChosenType())) { - return false; - } - } else if (property.equals("IsCommander")) { - if (!this.isCommander) { - return false; - } - } else { - if (!this.isType(property)) { - return false; - } - } - return true; - } // hasProperty - - /** - *

- * setImmutable. - *

- * - * @param isImmutable - * a boolean. - */ - public final void setImmutable(final boolean isImmutable) { - this.isImmutable = isImmutable; - } - - /** - *

- * isImmutable. - *

- * - * @return a boolean. - */ - public final boolean isImmutable() { - return this.isImmutable; - } - - /* - * there are easy checkers for Color. The CardUtil functions should be made - * part of the Card class, so calling out is not necessary - */ - - public final boolean isOfColor(final String col) { return CardUtil.getColors(this).hasAnyColor(MagicColor.fromName(col)); } - public final boolean isBlack() { return CardUtil.getColors(this).hasBlack(); } - public final boolean isBlue() { return CardUtil.getColors(this).hasBlue(); } - public final boolean isRed() { return CardUtil.getColors(this).hasRed(); } - public final boolean isGreen() { return CardUtil.getColors(this).hasGreen(); } - public final boolean isWhite() { return CardUtil.getColors(this).hasWhite(); } - public final boolean isColorless() { return CardUtil.getColors(this).isColorless(); } - - /** - *

- * sharesColorWith. - *

- * - * @param c1 - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean sharesColorWith(final Card c1) { - boolean shares = false; - shares |= (this.isBlack() && c1.isBlack()); - shares |= (this.isBlue() && c1.isBlue()); - shares |= (this.isGreen() && c1.isGreen()); - shares |= (this.isRed() && c1.isRed()); - shares |= (this.isWhite() && c1.isWhite()); - return shares; - } - - /** - *

- * sharesCreatureTypeWith. - *

- * - * @param c1 - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean sharesCreatureTypeWith(final Card c1) { - - if (c1 == null) { - return false; - } - - for (final String type : this.getType()) { - if (type.equals("AllCreatureTypes") && c1.hasACreatureType()) { - return true; - } - if (forge.card.CardType.isACreatureType(type) && c1.isType(type)) { - return true; - } - } - return false; - } - - /** - *

- * sharesTypeWith. - *

- * - * @param c1 - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean sharesCardTypeWith(final Card c1) { - - for (final String type : this.getType()) { - if (forge.card.CardType.isACardType(type) && c1.isType(type)) { - return true; - } - } - return false; - } - - /** - *

- * sharesTypeWith. - *

- * - * @param c1 - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean sharesTypeWith(final Card c1) { - - for (final String type : this.getType()) { - if (c1.isType(type)) { - return true; - } - } - return false; - } - - /** - *

- * sharesControllerWith. - *

- * - * @param c1 - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean sharesControllerWith(final Card c1) { - - if (c1 == null) { - return false; - } - - return this.getController().equals(c1.getController()); - } - - /** - *

- * hasACreatureType. - *

- * - * @return a boolean. - */ - public final boolean hasACreatureType() { - for (final String type : this.getType()) { - if (forge.card.CardType.isACreatureType(type) || type.equals("AllCreatureTypes")) { - return true; - } - } - return false; - } - - /** - *

- * isUsedToPay. - *

- * - * @return a boolean. - */ - public final boolean isUsedToPay() { - return this.usedToPayCost; - } - - /** - *

- * setUsedToPay. - *

- * - * @param b - * a boolean. - */ - public final void setUsedToPay(final boolean b) { - this.usedToPayCost = b; - } - - // ///////////////////////// - // - // Damage code - // - // //////////////////////// - - /** - *

- * addReceivedDamageFromThisTurn. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param damage - * a int. - */ - public final void addReceivedDamageFromThisTurn(final Card c, final int damage) { - this.receivedDamageFromThisTurn.put(c, damage); - } - - /** - *

- * Setter for the field receivedDamageFromThisTurn. - *

- * - * @param receivedDamageList - * a Map object. - */ - public final void setReceivedDamageFromThisTurn(final Map receivedDamageList) { - this.receivedDamageFromThisTurn = receivedDamageList; - } - - /** - *

- * Getter for the field receivedDamageFromThisTurn. - *

- * - * @return a Map object. - */ - public final Map getReceivedDamageFromThisTurn() { - return this.receivedDamageFromThisTurn; - } - - /** - *

- * resetReceivedDamageFromThisTurn. - *

- */ - public final void resetReceivedDamageFromThisTurn() { - this.receivedDamageFromThisTurn.clear(); - } - - public final int getTotalDamageRecievedThisTurn() { - int total = 0; - for (int damage : this.receivedDamageFromThisTurn.values()) { - total += damage; - } - return total; - } - - /** - *

- * addDealtDamageToThisTurn. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param damage - * a int. - */ - public final void addDealtDamageToThisTurn(final Card c, final int damage) { - this.dealtDamageToThisTurn.put(c, damage); - } - - /** - *

- * Setter for the field dealtDamageToThisTurn. - *

- * - * @param dealtDamageList - * a {@link java.util.Map} object. - */ - public final void setDealtDamageToThisTurn(final Map dealtDamageList) { - this.dealtDamageToThisTurn = dealtDamageList; - } - - /** - *

- * Getter for the field dealtDamageToThisTurn. - *

- * - * @return a {@link java.util.Map} object. - */ - public final Map getDealtDamageToThisTurn() { - return this.dealtDamageToThisTurn; - } - - /** - *

- * resetDealtDamageToThisTurn. - *

- */ - public final void resetDealtDamageToThisTurn() { - this.dealtDamageToThisTurn.clear(); - } - - /** - *

- * addDealtDamageToPlayerThisTurn. - *

- * - * @param player - * player as name String. - * @param damage - * a int. - */ - public final void addDealtDamageToPlayerThisTurn(final String player, final int damage) { - this.dealtDamageToPlayerThisTurn.put(player, damage); - } - - /** - *

- * Setter for the field dealtDamageToPlayerThisTurn. - *

- * - * @param dealtDamageList - * a {@link java.util.Map} object. - */ - public final void setDealtDamageToPlayerThisTurn(final Map dealtDamageList) { - this.dealtDamageToPlayerThisTurn = dealtDamageList; - } - - /** - *

- * Getter for the field dealtDamageToPlayerThisTurn. - *

- * - * @return a {@link java.util.Map} object. - */ - public final Map getDealtDamageToPlayerThisTurn() { - return this.dealtDamageToPlayerThisTurn; - } - - /** - *

- * resetDealtDamageToPlayerThisTurn. - *

- */ - public final void resetDealtDamageToPlayerThisTurn() { - this.dealtDamageToPlayerThisTurn.clear(); - } - - // this is the minimal damage a trampling creature has to assign to a blocker - /** - *

- * getLethalDamage. - *

- * - * @return a int. - */ - public final int getLethalDamage() { - final int lethalDamage = this.getNetDefense() - this.getDamage() - this.getTotalAssignedDamage(); - - return lethalDamage; - } - - /** - *

- * Setter for the field damage. - *

- * - * @param n - * a int. - */ - public final void setDamage(final int n) { - int oldDamae = damage; - this.damage = n; - if (n != oldDamae) - getGame().fireEvent(new GameEventCardStatsChanged(this)); - } - - /** - *

- * Getter for the field damage. - *

- * - * @return a int. - */ - public final int getDamage() { - return this.damage; - } - - /** - *

- * addAssignedDamage. - *

- * - * @param damage - * a int. - * @param sourceCard - * a {@link forge.game.card.Card} object. - */ - public final void addAssignedDamage(int damage, final Card sourceCard) { - if (damage < 0) { - damage = 0; - } - - final int assignedDamage = damage; - - Log.debug(this + " - was assigned " + assignedDamage + " damage, by " + sourceCard); - if (!this.assignedDamageMap.containsKey(sourceCard)) { - this.assignedDamageMap.put(sourceCard, assignedDamage); - } else { - this.assignedDamageMap.put(sourceCard, this.assignedDamageMap.get(sourceCard) + assignedDamage); - } - } - - /** - *

- * clearAssignedDamage. - *

- */ - public final void clearAssignedDamage() { - this.assignedDamageMap.clear(); - } - - /** - *

- * getTotalAssignedDamage. - *

- * - * @return a int. - */ - public final int getTotalAssignedDamage() { - int total = 0; - - final Collection c = this.assignedDamageMap.values(); - - final Iterator itr = c.iterator(); - while (itr.hasNext()) { - total += itr.next(); - } - - return total; - } - - /** - *

- * Getter for the field assignedDamageMap. - *

- * - * @return a {@link java.util.Map} object. - */ - public final Map getAssignedDamageMap() { - return this.assignedDamageMap; - } - - /** - *

- * addCombatDamage. - *

- * - * @param map - * a {@link java.util.Map} object. - */ - public final void addCombatDamage(final Map map) { - final List list = new ArrayList(); - - for (final Entry entry : map.entrySet()) { - final Card source = entry.getKey(); - list.add(source); - int damageToAdd = entry.getValue(); - - damageToAdd = this.replaceDamage(damageToAdd, source, true); - damageToAdd = this.preventDamage(damageToAdd, source, true); - - map.put(source, damageToAdd); - } - - if (this.isInPlay()) { - this.addDamage(map); - } - } - - // This is used by the AI to forecast an effect (so it must not change the game state) - /** - *

- * staticDamagePrevention. - *

- * - * @param damage - * a int. - * @param possiblePrevention - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @param isCombat - * a boolean. - * @return a int. - */ - public final int staticDamagePrevention(final int damage, final int possiblePrevention, final Card source, - final boolean isCombat) { - - if (getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) { - return damage; - } - - for (final Card ca : getGame().getCardsIn(ZoneType.Battlefield)) { - for (final ReplacementEffect re : ca.getReplacementEffects()) { - Map params = re.getMapParams(); - if (!"DamageDone".equals(params.get("Event")) || !params.containsKey("PreventionEffect")) { - continue; - } - if (params.containsKey("ValidSource") - && !source.isValid(params.get("ValidSource"), ca.getController(), ca)) { - continue; - } - if (params.containsKey("ValidTarget") - && !this.isValid(params.get("ValidTarget"), ca.getController(), ca)) { - continue; - } - if (params.containsKey("IsCombat")) { - if (params.get("IsCombat").equals("True")) { - if (!isCombat) { - continue; - } - } else { - if (isCombat) { - continue; - } - } - - } - return 0; - } - } - - int restDamage = damage - possiblePrevention; - - restDamage = this.staticDamagePrevention(restDamage, source, isCombat, true); - - return restDamage; - } - - // This should be also usable by the AI to forecast an effect (so it must not change the game state) - /** - *

- * staticDamagePrevention. - *

- * - * @param damageIn - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @param isCombat - * a boolean. - * @return a int. - */ - @Override - public final int staticDamagePrevention(final int damageIn, final Card source, final boolean isCombat, final boolean isTest) { - - if (getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) { - return damageIn; - } - - if (isCombat && getGame().getPhaseHandler().isPreventCombatDamageThisTurn()) { - return 0; - } - - int restDamage = damageIn; - - if (this.hasProtectionFrom(source)) { - return 0; - } - - for (String kw : source.getKeyword()) { - if (isCombat) { - if (kw.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")) { - return 0; - } - if (kw.equals("Prevent all combat damage that would be dealt by CARDNAME.")) { - return 0; - } - } - if (kw.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) { - return 0; - } - if (kw.equals("Prevent all damage that would be dealt by CARDNAME.")) { - return 0; - } - } - for (String kw : this.getKeyword()) { - if (isCombat) { - if (kw.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")) { - return 0; - } - if (kw.equals("Prevent all combat damage that would be dealt to CARDNAME.")) { - return 0; - } - } - if (kw.equals("Prevent all damage that would be dealt to CARDNAME.")) { - return 0; - } - if (kw.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) { - return 0; - } - if (kw.startsWith("Absorb")) { - final int absorbed = this.getKeywordMagnitude("Absorb"); - if (restDamage > absorbed) { - restDamage = restDamage - absorbed; - } else { - return 0; - } - } - if (kw.startsWith("PreventAllDamageBy")) { - String valid = this.getKeyword().get(this.getKeywordPosition("PreventAllDamageBy")); - valid = valid.split(" ", 2)[1]; - if (source.isValid(valid, this.getController(), this)) { - return 0; - } - } - } - - // Prevent Damage static abilities - for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { - final ArrayList staticAbilities = ca.getStaticAbilities(); - for (final StaticAbility stAb : staticAbilities) { - restDamage = stAb.applyAbility("PreventDamage", source, this, restDamage, isCombat, isTest); - } - } - - // specific Cards - if (this.isCreature()) { // and not a planeswalker - - if (source.isCreature() && getGame().isCardInPlay("Well-Laid Plans") - && source.sharesColorWith(this)) { - return 0; - } - } // Creature end - - - return restDamage > 0 ? restDamage : 0; - } - - /** - *

- * preventDamage. - *

- * - * @param damage - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @param isCombat - * a boolean. - * @return a int. - */ - @Override - public final int preventDamage(final int damage, final Card source, final boolean isCombat) { - - if (getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) - || source.hasKeyword("Damage that would be dealt by CARDNAME can't be prevented.")) { - return damage; - } - - int restDamage = damage; - - boolean DEBUGShieldsWithEffects = false; - while (!this.getPreventNextDamageWithEffect().isEmpty() && restDamage != 0) { - TreeMap> shieldMap = this.getPreventNextDamageWithEffect(); - List preventionEffectSources = new ArrayList(shieldMap.keySet()); - Card shieldSource = preventionEffectSources.get(0); - if (preventionEffectSources.size() > 1) { - Map choiceMap = new TreeMap(); - List choices = new ArrayList(); - for (final Card key : preventionEffectSources) { - String effDesc = shieldMap.get(key).get("EffectString"); - int descIndex = effDesc.indexOf("SpellDescription"); - effDesc = effDesc.substring(descIndex + 18); - String shieldDescription = key.toString() + " - " + shieldMap.get(key).get("ShieldAmount") - + " shields - " + effDesc; - choices.add(shieldDescription); - choiceMap.put(shieldDescription, key); - } - shieldSource = this.getController().getController().chooseProtectionShield(this, choices, choiceMap); - } - if (DEBUGShieldsWithEffects) { - System.out.println("Prevention shield source: " + shieldSource); - } - - int shieldAmount = Integer.valueOf(shieldMap.get(shieldSource).get("ShieldAmount")); - int dmgToBePrevented = Math.min(restDamage, shieldAmount); - if (DEBUGShieldsWithEffects) { - System.out.println("Selected source initial shield amount: " + shieldAmount); - System.out.println("Incoming damage: " + restDamage); - System.out.println("Damage to be prevented: " + dmgToBePrevented); - } - - //Set up ability - SpellAbility shieldSA = null; - String effectAbString = shieldMap.get(shieldSource).get("EffectString"); - effectAbString = effectAbString.replace("PreventedDamage", Integer.toString(dmgToBePrevented)); - effectAbString = effectAbString.replace("ShieldEffectTarget", shieldMap.get(shieldSource).get("ShieldEffectTarget")); - if (DEBUGShieldsWithEffects) { - System.out.println("Final shield ability string: " + effectAbString); - } - shieldSA = AbilityFactory.getAbility(effectAbString, shieldSource); - if (shieldSA.usesTargeting()) { - System.err.println(shieldSource + " - Targeting for prevention shield's effect should be done with initial spell"); - } - - boolean apiIsEffect = (shieldSA.getApi() == ApiType.Effect); - List cardsInCommand = null; - if (apiIsEffect) { - cardsInCommand = this.getGame().getCardsIn(ZoneType.Command); - } - - this.getController().getController().playSpellAbilityNoStack(shieldSA, true); - if (apiIsEffect) { - List newCardsInCommand = this.getGame().getCardsIn(ZoneType.Command); - newCardsInCommand.removeAll(cardsInCommand); - if (!newCardsInCommand.isEmpty()) { - newCardsInCommand.get(0).setSVar("PreventedDamage", "Number$" + Integer.toString(dmgToBePrevented)); - } - } - this.subtractPreventNextDamageWithEffect(shieldSource, restDamage); - restDamage = restDamage - dmgToBePrevented; - - if (DEBUGShieldsWithEffects) { - System.out.println("Remaining shields: " - + (shieldMap.containsKey(shieldSource) ? shieldMap.get(shieldSource).get("ShieldAmount") : "all shields used")); - System.out.println("Remaining damage: " + restDamage); - } - } - - - final HashMap repParams = new HashMap(); - repParams.put("Event", "DamageDone"); - repParams.put("Affected", this); - repParams.put("DamageSource", source); - repParams.put("DamageAmount", damage); - repParams.put("IsCombat", isCombat); - repParams.put("Prevention", true); - - if (getGame().getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { - return 0; - } - - restDamage = this.staticDamagePrevention(restDamage, source, isCombat, false); - - if (restDamage == 0) { - return 0; - } - - if (this.hasKeyword("If damage would be dealt to CARDNAME, " - + "prevent that damage. Remove a +1/+1 counter from CARDNAME.")) { - restDamage = 0; - this.subtractCounter(CounterType.P1P1, 1); - } - - if (restDamage >= this.getPreventNextDamage()) { - restDamage = restDamage - this.getPreventNextDamage(); - this.setPreventNextDamage(0); - } else { - this.setPreventNextDamage(this.getPreventNextDamage() - restDamage); - restDamage = 0; - } - - return restDamage; - } - - // This is used by the AI to forecast an effect (so it must not change the game state) - /** - *

- * staticReplaceDamage. - *

- * - * @param damage - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @param isCombat - * a boolean. - * @return a int. - */ - @Override - public final int staticReplaceDamage(final int damage, final Card source, final boolean isCombat) { - - int restDamage = damage; - for (Card c : getGame().getCardsIn(ZoneType.Battlefield)) { - if (c.getName().equals("Sulfuric Vapors")) { - if (source.isSpell() && source.isRed()) { - restDamage += 1; - } - } else if (c.getName().equals("Pyromancer's Swath")) { - if (c.getController().equals(source.getController()) && (source.isInstant() || source.isSorcery()) - && this.isCreature()) { - restDamage += 2; - } - } else if (c.getName().equals("Furnace of Rath")) { - if (this.isCreature()) { - restDamage += restDamage; - } - } else if (c.getName().equals("Gratuitous Violence")) { - if (c.getController().equals(source.getController()) && source.isCreature() && this.isCreature()) { - restDamage += restDamage; - } - } else if (c.getName().equals("Fire Servant")) { - if (c.getController().equals(source.getController()) && source.isRed() - && (source.isInstant() || source.isSorcery())) { - restDamage *= 2; - } - } else if (c.getName().equals("Gisela, Blade of Goldnight")) { - if (!c.getController().equals(this.getController())) { - restDamage *= 2; - } - } else if (c.getName().equals("Inquisitor's Flail")) { - if (isCombat && c.getEquippingCard() != null - && (c.getEquippingCard().equals(this) || c.getEquippingCard().equals(source))) { - restDamage *= 2; - } - } else if (c.getName().equals("Ghosts of the Innocent")) { - if (this.isCreature()) { - restDamage = restDamage / 2; - } - } else if (c.getName().equals("Benevolent Unicorn")) { - if (source.isSpell() && this.isCreature()) { - restDamage -= 1; - } - } else if (c.getName().equals("Divine Presence")) { - if (restDamage > 3 && this.isCreature()) { - restDamage = 3; - } - } else if (c.getName().equals("Lashknife Barrier")) { - if (c.getController().equals(this.getController()) && this.isCreature()) { - restDamage -= 1; - } - } - } - - if (this.getName().equals("Phytohydra")) { - return 0; - } - - return restDamage; - } - - /** - *

- * replaceDamage. - *

- * - * @param damageIn - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @param isCombat - * a boolean. - * @return a int. - */ - @Override - public final int replaceDamage(final int damageIn, final Card source, final boolean isCombat) { - // Replacement effects - final HashMap repParams = new HashMap(); - repParams.put("Event", "DamageDone"); - repParams.put("Affected", this); - repParams.put("DamageSource", source); - repParams.put("DamageAmount", damageIn); - repParams.put("IsCombat", isCombat); - - if (getGame().getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { - return 0; - } - - return damageIn; - } - - /** - *

- * addDamage. - *

- * - * @param sourcesMap - * a {@link java.util.Map} object. - */ - public final void addDamage(final Map sourcesMap) { - for (final Entry entry : sourcesMap.entrySet()) { - // damage prevention is already checked! - this.addDamageAfterPrevention(entry.getValue(), entry.getKey(), true); - } - } - - /** - *

- * addDamageAfterPrevention. - *

- * This function handles damage after replacement and prevention effects are - * applied. - * - * @param damageIn - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @param isCombat - * a boolean. - * @return whether or not damage as dealt - */ - @Override - public final boolean addDamageAfterPrevention(final int damageIn, final Card source, final boolean isCombat) { - final int damageToAdd = damageIn; - - if (damageToAdd == 0) { - return false; // Rule 119.8 - } - - this.addReceivedDamageFromThisTurn(source, damageToAdd); - source.addDealtDamageToThisTurn(this, damageToAdd); - - if (source.hasKeyword("Lifelink")) { - source.getController().gainLife(damageToAdd, source); - } - - // Run triggers - final Map runParams = new TreeMap(); - runParams.put("DamageSource", source); - runParams.put("DamageTarget", this); - runParams.put("DamageAmount", damageToAdd); - runParams.put("IsCombatDamage", isCombat); - getGame().getTriggerHandler().runTrigger(TriggerType.DamageDone, runParams, false); - - GameEventCardDamaged.DamageType damageType = DamageType.Normal; - if (this.isPlaneswalker()) { - this.subtractCounter(CounterType.LOYALTY, damageToAdd); - damageType = DamageType.LoyaltyLoss; - } - else { - final Game game = source.getGame(); - - final String s = this + " - destroy"; - - final int amount = this.getAmountOfKeyword("When CARDNAME is dealt damage, destroy it."); - if (amount > 0) { - final Ability abDestroy = new Ability(source, ManaCost.ZERO){ - @Override public void resolve() { game.getAction().destroy(Card.this, this); } - }; - abDestroy.setStackDescription(s + ", it cannot be regenerated."); - - for (int i = 0; i < amount; i++) { - game.getStack().addSimultaneousStackEntry(abDestroy); - } - } - - final int amount2 = this.getAmountOfKeyword("When CARDNAME is dealt damage, destroy it. It can't be regenerated."); - if (amount2 > 0) { - final Ability abDestoryNoRegen = new Ability(source, ManaCost.ZERO){ - @Override public void resolve() { game.getAction().destroyNoRegeneration(Card.this, this); } - }; - abDestoryNoRegen.setStackDescription(s); - - for (int i = 0; i < amount2; i++) { - game.getStack().addSimultaneousStackEntry(abDestoryNoRegen); - } - } - - boolean wither = (getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.alwaysWither) - || source.hasKeyword("Wither") || source.hasKeyword("Infect")); - - if (this.isInPlay()) { - if (wither) { - this.addCounter(CounterType.M1M1, damageToAdd, true); - damageType = DamageType.M1M1Counters; - } else - this.damage += damageToAdd; - } - - if (source.hasKeyword("Deathtouch") && this.isCreature()) { - getGame().getAction().destroy(this, null); - damageType = DamageType.Deathtouch; - } - - // Play the Damage sound - game.fireEvent(new GameEventCardDamaged(this, source, damageToAdd, damageType)); - } - - return true; - } - - /** - *

- * Setter for the field curSetCode. - *

- * - * @param setCode - * a {@link java.lang.String} object. - */ - public final void setCurSetCode(final String setCode) { - this.getCharacteristics().setCurSetCode(setCode); - } - - /** - *

- * Getter for the field curSetCode. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getCurSetCode() { - return this.getCharacteristics().getCurSetCode(); - } - - /** - *

- * getCurSetRarity. - *

- * - * @return a {@link java.lang.String} object. - */ - public final CardRarity getRarity() { - return this.getCharacteristics().getRarity(); - } - - public final void setRarity(CardRarity r) { - this.getCharacteristics().setRarity(r); - } - - /** - *

- * getMostRecentSet. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getMostRecentSet() { - return StaticData.instance().getCommonCards().getCard(this.getName()).getEdition(); - } - - public final void setImageKey(final String iFN) { - this.getCharacteristics().setImageKey(iFN); - } - - public final String getImageKey() { - return this.getCharacteristics().getImageKey(); - } - - public String getImageKey(CardCharacteristicName state) { - CardCharacteristics c = this.characteristicsMap.get(state); - return (c != null ? c.getImageKey() : ""); - } - - /** - *

- * Setter for the field evoked. - *

- * - * @param evokedIn - * a boolean. - */ - public final void setEvoked(final boolean evokedIn) { - this.evoked = evokedIn; - } - - /** - *

- * isEvoked. - *

- * - * @return a boolean. - */ - public final boolean isEvoked() { - return this.evoked; - } - - /** - *

- * Setter for the field tributed. - *

- * - * @param b - * a boolean. - */ - public final void setTributed(final boolean b) { - this.tributed = b; - } - - /** - *

- * Setter for the field monstrous. - *

- * - * @param monstrous - * a boolean. - */ - public final void setMonstrous(final boolean monstrous) { - this.monstrous = monstrous; - } - - /** - *

- * isMonstrous. - *

- * - * @return a boolean. - */ - public final boolean isMonstrous() { - return this.monstrous; - } - - /** - *

- * animateBestow. - *

- */ - public final void animateBestow() { - this.bestowTimestamp = this.getGame().getNextTimestamp(); - this.addChangedCardTypes(new ArrayList(Arrays.asList("Aura")), - new ArrayList(Arrays.asList("Creature")), false, false, false, true, bestowTimestamp); - this.addChangedCardKeywords(Arrays.asList("Enchant creature"), new ArrayList(), false, bestowTimestamp); - } - - /** - *

- * unanimateBestow. - *

- */ - public final void unanimateBestow() { - this.removeChangedCardKeywords(bestowTimestamp); - this.removeChangedCardTypes(bestowTimestamp); - bestowTimestamp = -1; - } - - /** - *

- * isBestowed. - *

- * - * @return a boolean. - */ - public final boolean isBestowed() { - return this.bestowTimestamp != -1; - } - - /** - *

- * Setter for the field monstrosityNum. - *

- * - * @param num - * an int. - */ - public final void setMonstrosityNum(final int num) { - this.monstrosityNum = num; - } - - /** - *

- * getMonstrosityNum. - *

- * - * @return a int. - */ - public final int getMonstrosityNum() { - return this.monstrosityNum; - } - /** - * - * TODO Write javadoc for this method. - * - * @param t - * a long - */ - public final void setTimestamp(final long t) { - this.timestamp = t; - } - - /** - * - * TODO Write javadoc for this method. - * - * @return a long - */ - public final long getTimestamp() { - return this.timestamp; - } - - /** - * - * TODO Write javadoc for this method. - * - * @return an int - */ - public final int getFoil() { - final String foil = this.getCharacteristics().getSVar("Foil"); - if (!foil.isEmpty()) { - return Integer.parseInt(foil); - } - return 0; - } - - /** - * Assign a random foil finish depending on the card edition. - * - * @param remove if true, a random foil is assigned, otherwise it is - * removed. - */ - public final void setRandomFoil() { - CardEdition.FoilType foilType = CardEdition.FoilType.NOT_SUPPORTED; - if (this.getCurSetCode() != null && StaticData.instance().getEditions().get(this.getCurSetCode()) != null) { - foilType = StaticData.instance().getEditions().get(this.getCurSetCode()).getFoilType(); - } - if (foilType != CardEdition.FoilType.NOT_SUPPORTED) { - this.setFoil(foilType == CardEdition.FoilType.MODERN ? MyRandom.getRandom().nextInt(9) + 1 : MyRandom.getRandom().nextInt(9) + 11); - } - } - - /** - * - * TODO Write javadoc for this method. - * - * @param f an int - */ - public final void setFoil(final int f) { - this.getCharacteristics().setSVar("Foil", Integer.toString(f)); - } - - /** - * Adds the haunted by. - * - * @param c - * the c - */ - public final void addHauntedBy(final Card c) { - this.hauntedBy.add(c); - if (c != null) { - c.setHaunting(this); - } - } - - /** - * Gets the haunted by. - * - * @return the haunted by - */ - public final List getHauntedBy() { - return this.hauntedBy; - } - - /** - * Removes the haunted by. - * - * @param c - * the c - */ - public final void removeHauntedBy(final Card c) { - this.hauntedBy.remove(c); - } - - /** - * Gets the pairing. - * - * @return the pairedWith - */ - public final Card getPairedWith() { - return this.pairedWith; - } - - /** - * Sets the pairing. - * - * @param c - * the new pairedWith - */ - public final void setPairedWith(final Card c) { - this.pairedWith = c; - } - - /** - *

- * isPaired. - *

- * - * @return a boolean. - */ - public final boolean isPaired() { - return this.pairedWith != null; - } - - /** - * Gets the haunting. - * - * @return the haunting - */ - public final Card getHaunting() { - return this.haunting; - } - - /** - * Sets the haunting. - * - * @param c - * the new haunting - */ - public final void setHaunting(final Card c) { - this.haunting = c; - } - - /** - * Gets the damage done this turn. - * - * @return the damage done this turn - */ - public final int getDamageDoneThisTurn() { - int sum = 0; - for (final Card c : this.dealtDamageToThisTurn.keySet()) { - sum += this.dealtDamageToThisTurn.get(c); - } - - return sum; - } - - /** - * Gets the damage done to a player by card this turn. - * - * @param player - * the player name - * @return the damage done to player p this turn - */ - public final int getDamageDoneToPlayerBy(final String player) { - int sum = 0; - for (final String p : this.dealtDamageToPlayerThisTurn.keySet()) { - if (p.equals(player)) { - sum += this.dealtDamageToPlayerThisTurn.get(p); - } - } - - return sum; - } - - /** - * Gets the total damage done by card this turn (after prevention and redirects). - * - * @return the damage done to player p this turn - */ - public final int getTotalDamageDoneBy() { - int sum = 0; - for (final Card c : this.dealtDamageToThisTurn.keySet()) { - sum += this.dealtDamageToThisTurn.get(c); - } - for (final String p : this.dealtDamageToPlayerThisTurn.keySet()) { - sum += this.dealtDamageToPlayerThisTurn.get(p); - } - - return sum; - } - - - /* - * (non-Javadoc) - * - * @see forge.GameEntity#hasProtectionFrom(forge.Card) - */ - @Override - public boolean hasProtectionFrom(final Card source) { - if (source == null) { - return false; - } - - if (this.isImmutable()) { - return true; - } - - if (this.getKeyword() != null) { - final List list = this.getKeyword(); - - String kw = ""; - for (int i = 0; i < list.size(); i++) { - kw = list.get(i); - if (!kw.startsWith("Protection")) { - continue; - } - if (kw.equals("Protection from white")) { - if (source.isWhite() && !source.getName().equals("White Ward") - && !source.getName().contains("Pledge of Loyalty")) { - return true; - } - } else if (kw.equals("Protection from blue")) { - if (source.isBlue() && !source.getName().equals("Blue Ward") - && !source.getName().contains("Pledge of Loyalty")) { - return true; - } - } else if (kw.equals("Protection from black")) { - if (source.isBlack() && !source.getName().equals("Black Ward") - && !source.getName().contains("Pledge of Loyalty")) { - return true; - } - } else if (kw.equals("Protection from red")) { - if (source.isRed() && !source.getName().equals("Red Ward") - && !source.getName().contains("Pledge of Loyalty")) { - return true; - } - } else if (kw.equals("Protection from green")) { - if (source.isGreen() && !source.getName().equals("Green Ward") - && !source.getName().contains("Pledge of Loyalty")) { - return true; - } - } else if (kw.equals("Protection from creatures")) { - if (source.isCreature()) { - return true; - } - } else if (kw.equals("Protection from artifacts")) { - if (source.isArtifact()) { - return true; - } - } else if (kw.equals("Protection from enchantments")) { - if (source.isEnchantment() && !source.getName().contains("Tattoo Ward")) { - return true; - } - } else if (kw.equals("Protection from everything")) { - return true; - } else if (kw.startsWith("Protection:")) { // uses isValid - final String characteristic = kw.split(":")[1]; - final String[] characteristics = characteristic.split(","); - if (source.isValid(characteristics, this.getController(), this) - && !source.getName().contains("Flickering Ward") && !source.getName().contains("Pentarch Ward") - && !source.getName().contains("Cho-Manno's Blessing") && !source.getName().contains("Floating Shield") - && !source.getName().contains("Ward of Lights")) { - return true; - } - } else if (kw.equals("Protection from colored spells")) { - if (source.isSpell() && !source.isColorless()) { - return true; - } - } else if (kw.equals("Protection from Dragons")) { - if (source.isType("Dragon")) { - return true; - } - } else if (kw.equals("Protection from Demons")) { - if (source.isType("Demon")) { - return true; - } - } else if (kw.equals("Protection from Goblins")) { - if (source.isType("Goblin")) { - return true; - } - } else if (kw.equals("Protection from Clerics")) { - if (source.isType("Cleric")) { - return true; - } - } else if (kw.equals("Protection from Gorgons")) { - if (source.isType("Gorgon")) { - return true; - } - } else if (kw.equals("Protection from the chosen player")) { - if (source.getController().equals(this.chosenPlayer)) { - return true; - } - } - } - } - return false; - } - - /** - * - * is In Zone. - * - * @param zone - * Constant.Zone - * @return boolean - */ - public boolean isInZone(final ZoneType zone) { - Zone z = this.getZone(); - return z != null && z.getZoneType() == zone; - } - - public Zone getZone() { - return currentZone; - } - - public void setZone(Zone zone) { - currentZone = zone; - } - - public final boolean canBeDestroyed() { - return isInPlay() && (!hasKeyword("Indestructible") || (isCreature() && getNetDefense() <= 0)); - } - - /** - * Can target. - * - * @param sa - * the sa - * @return a boolean - */ - @Override - public final boolean canBeTargetedBy(final SpellAbility sa) { - - if (sa == null) { - return true; - } - - // CantTarget static abilities - for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { - final ArrayList staticAbilities = ca.getStaticAbilities(); - for (final StaticAbility stAb : staticAbilities) { - if (stAb.applyAbility("CantTarget", this, sa)) { - return false; - } - } - } - - // keywords don't work outside battlefield - if (!this.isInZone(ZoneType.Battlefield)) { - return true; - } - - if (this.hasProtectionFrom(sa.getSourceCard())) { - return false; - } - - if (this.isPhasedOut()) { - return false; - } - - final Card source = sa.getSourceCard(); - - if (this.getKeyword() != null) { - for (String kw : this.getKeyword()) { - if (kw.equals("Shroud")) { - return false; - } - - if (kw.equals("Hexproof")) { - if (sa.getActivatingPlayer().getOpponents().contains(this.getController())) { - if (!sa.getActivatingPlayer().getKeywords().contains("Spells and abilities you control can target hexproof creatures")) { - return false; - } - } - } - - if (kw.equals("CARDNAME can't be the target of Aura spells.")) { - if (source.isAura() && sa.isSpell()) { - return false; - } - } - - if (kw.equals("CARDNAME can't be enchanted.")) { - if (source.isAura()) { - return false; - } - } //Sets source as invalid enchant target for computer player only. - - if (kw.equals("CARDNAME can't be equipped.")) { - if (source.isEquipment()) { - return false; - } - } //Sets source as invalid equip target for computer player only. - - if (kw.equals("CARDNAME can't be the target of red spells or abilities from red sources.")) { - if (source.isRed()) { - return false; - } - } - - if (kw.equals("CARDNAME can't be the target of black spells.")) { - if (source.isBlack() && sa.isSpell()) { - return false; - } - } - - if (kw.equals("CARDNAME can't be the target of blue spells.")) { - if (source.isBlue() && sa.isSpell()) { - return false; - } - } - - if (kw.equals("CARDNAME can't be the target of spells.")) { - if (sa.isSpell()) { - return false; - } - } - } - } - if (sa.isSpell() && source.hasStartOfKeyword("SpellCantTarget")) { - final int keywordPosition = source.getKeywordPosition("SpellCantTarget"); - final String parse = source.getKeyword().get(keywordPosition).toString(); - final String[] k = parse.split(":"); - final String[] restrictions = k[1].split(","); - if (this.isValid(restrictions, source.getController(), source)) { - return false; - } - } - return true; - } - - /** - * canBeEnchantedBy. - * - * @param aura - * a Card - * @return a boolean - */ - public final boolean canBeEnchantedBy(final Card aura) { - SpellAbility sa = aura.getFirstSpellAbility(); - if (aura.isBestowed()) { - for (SpellAbility s : aura.getSpellAbilities()) { - if (s.getApi() == ApiType.Attach && s.hasParam("Bestow")) { - sa = s; - break; - } - } - } - TargetRestrictions tgt = null; - if (sa != null) { - tgt = sa.getTargetRestrictions(); - } - - if (this.hasProtectionFrom(aura) - || (this.hasKeyword("CARDNAME can't be enchanted.") && !aura.getName().equals("Anti-Magic Aura") - && !(aura.getName().equals("Consecrate Land") && aura.isInZone(ZoneType.Battlefield))) - || ((tgt != null) && !this.isValid(tgt.getValidTgts(), aura.getController(), aura))) { - return false; - } - return true; - } - - /** - * canBeEquippedBy. - * - * @param equip - * a Card - * @return a boolean - */ - public final boolean canBeEquippedBy(final Card equip) { - if (equip.hasStartOfKeyword("CantEquip")) { - final int keywordPosition = equip.getKeywordPosition("CantEquip"); - final String parse = equip.getKeyword().get(keywordPosition).toString(); - final String[] k = parse.split(" ", 2); - final String[] restrictions = k[1].split(","); - if (this.isValid(restrictions, equip.getController(), equip)) { - return false; - } - } - if (this.hasProtectionFrom(equip) - || this.hasKeyword("CARDNAME can't be equipped.") - || !this.isValid("Creature", equip.getController(), equip)) { - return false; - } - return true; - } - - /** - * Gets the replacement effects. - * - * @return the replacement effects - */ - public List getReplacementEffects() { - return this.getCharacteristics().getReplacementEffects(); - } - - /** - * Sets the replacement effects. - * - * @param res - * the new replacement effects - */ - public void setReplacementEffects(final List res) { - this.getCharacteristics().getReplacementEffects().clear(); - for (final ReplacementEffect replacementEffect : res) { - if (replacementEffect.isIntrinsic()) { - this.addReplacementEffect(replacementEffect); - } - } - } - - /** - * Adds the replacement effect. - * - * @param replacementEffect - * the rE - */ - public ReplacementEffect addReplacementEffect(final ReplacementEffect replacementEffect) { - final ReplacementEffect replacementEffectCopy = replacementEffect.getCopy(); // doubtful - every caller provides a newly parsed instance, why copy? - replacementEffectCopy.setHostCard(this); - this.getCharacteristics().getReplacementEffects().add(replacementEffectCopy); - return replacementEffectCopy; - } - - /** - * Returns what zone this card was cast from (from what zone it was moved to - * the stack). - * - * @return the castFrom - */ - public ZoneType getCastFrom() { - return this.castFrom; - } - - /** - * @param castFrom0 - * the castFrom to set - */ - public void setCastFrom(final ZoneType castFrom0) { - this.castFrom = castFrom0; - } - - /** - * @return CardDamageHistory - */ - public CardDamageHistory getDamageHistory() { - return damageHistory; - } - - /** - * @return the effectSource - */ - public Card getEffectSource() { - return effectSource; - } - - /** - * @param src the effectSource to set - */ - public void setEffectSource(Card src) { - this.effectSource = src; - } - - /** - * @return the startsGameInPlay - */ - public boolean isStartsGameInPlay() { - return startsGameInPlay; - } - - /** - * @param startsGameInPlay the startsGameInPlay to set - */ - public void setStartsGameInPlay(boolean startsGameInPlay) { - this.startsGameInPlay = startsGameInPlay; - } - - /** @return boolean */ - public boolean isInPlay() { - return this.isInZone(ZoneType.Battlefield); - } - - public void onCleanupPhase(final Player turn) { - setDamage(0); - resetPreventNextDamage(); - resetPreventNextDamageWithEffect(); - resetReceivedDamageFromThisTurn(); - resetDealtDamageToThisTurn(); - resetDealtDamageToPlayerThisTurn(); - getDamageHistory().newTurn(); - setRegeneratedThisTurn(0); - setBecameTargetThisTurn(false); - clearMustBlockCards(); - getDamageHistory().setCreatureAttackedLastTurnOf(turn, getDamageHistory().getCreatureAttackedThisTurn()); - getDamageHistory().setCreatureAttackedThisTurn(false); - getDamageHistory().setCreatureAttacksThisTurn(0); - getDamageHistory().setCreatureBlockedThisTurn(false); - getDamageHistory().setCreatureGotBlockedThisTurn(false); - clearBlockedByThisTurn(); - clearBlockedThisTurn(); - } - - /** - *

- * hasETBTrigger. - *

- * - * @param card - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public boolean hasETBTrigger() { - for (final Trigger tr : this.getTriggers()) { - final HashMap params = tr.getMapParams(); - if (tr.getMode() != TriggerType.ChangesZone) { - continue; - } - - if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) { - continue; - } - - if (params.containsKey("ValidCard") && !params.get("ValidCard").contains("Self")) { - continue; - } - return true; - } - return false; - } - - /** - *

- * hasETBTrigger. - *

- * - * @return a boolean. - */ - public boolean hasETBReplacement() { - for (final ReplacementEffect re : getReplacementEffects()) { - final Map params = re.getMapParams(); - if (!(re instanceof ReplaceMoved)) { - continue; - } - - if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) { - continue; - } - - if (params.containsKey("ValidCard") && !params.get("ValidCard").contains("Self")) { - continue; - } - return true; - } - return false; - } - - public boolean canBeShownTo(final Player viewer) { - if (!isFaceDown()) { - return true; - } - if (getController() == viewer && isInZone(ZoneType.Battlefield)) { - return true; - } - final Game game = this.getGame(); - if (getController() == viewer && hasKeyword("You may look at this card.")) { - return true; - } - if (getController().isOpponentOf(viewer) && hasKeyword("Your opponent may look at this card.")) { - return true; - } - for (Card host : game.getCardsIn(ZoneType.Battlefield)) { - final ArrayList staticAbilities = host.getStaticAbilities(); - for (final StaticAbility stAb : staticAbilities) { - if (stAb.applyAbility("MayLookAt", this, viewer)) { - return true; - } - } - } - return false; - } - - /** - *

- * getConvertedManaCost. - *

- * - * @return a int. - */ - public int getCMC() { - return getCMC(SplitCMCMode.CurrentSideCMC); - } - - public int getCMC(SplitCMCMode mode) { - if (isToken() && !isCopiedToken()) { - return 0; - } - - int xPaid = 0; - - // 2012-07-22 - If a card is on the stack, count the xManaCost in with it's CMC - if (getGame().getCardsIn(ZoneType.Stack).contains(this) && getManaCost() != null) { - xPaid = getXManaCostPaid() * getManaCost().countX(); - } - - int requestedCMC = 0; - - if (isSplitCard()) { - switch(mode) { - case CurrentSideCMC: - // TODO: test if this returns combined CMC for the full face (then get rid of CombinedCMC mode?) - requestedCMC = getManaCost().getCMC() + xPaid; - break; - case LeftSplitCMC: - requestedCMC = getState(CardCharacteristicName.LeftSplit).getManaCost().getCMC() + xPaid; - break; - case RightSplitCMC: - requestedCMC = getState(CardCharacteristicName.RightSplit).getManaCost().getCMC() + xPaid; - break; - case CombinedCMC: - requestedCMC += getState(CardCharacteristicName.LeftSplit).getManaCost().getCMC(); - requestedCMC += getState(CardCharacteristicName.RightSplit).getManaCost().getCMC(); - requestedCMC += xPaid; - break; - default: - System.out.println(String.format("Illegal Split Card CMC mode %s passed to getCMC!", mode.toString())); - break; - } - } else { - requestedCMC = getManaCost().getCMC() + xPaid; - } - - return requestedCMC; - } - - public final boolean canBeSacrificedBy(final SpellAbility source) - { - if (isImmutable()) { - System.out.println("Trying to sacrifice immutables: " + this); - return false; - } - if (isPhasedOut()) { - return false; - } - if (source != null && getController().isOpponentOf(source.getActivatingPlayer()) - && getController().hasKeyword("Spells and abilities your opponents control can't cause you to sacrifice permanents.")) { - return false; - } - return true; - } - - CardRules cardRules; - public CardRules getRules() { - return cardRules; - } - public void setRules(CardRules r) { - cardRules = r; - } - - public boolean isCommander() { - // TODO - have a field - return this.isCommander; - } - - public void setCommander(boolean b) { - this.isCommander = b; - } - - public void setSplitStateToPlayAbility(SpellAbility sa) { - if (!isSplitCard()) return; // just in case - // Split card support - for (SpellAbility a : getState(CardCharacteristicName.LeftSplit).getSpellAbility()) { - if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { - setState(CardCharacteristicName.LeftSplit); - return; - } - } - for (SpellAbility a : getState(CardCharacteristicName.RightSplit).getSpellAbility()) { - if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { - setState(CardCharacteristicName.RightSplit); - return; - } - } - if (sa.getSourceCard().hasKeyword("Fuse")) // it's ok that such card won't change its side - return; - - throw new RuntimeException("Not found which part to choose for ability " + sa + " from card " + this); - } - - // Optional costs paid - private final EnumSet costsPaid = EnumSet.noneOf(OptionalCost.class); - public void clearOptionalCostsPaid() { costsPaid.clear(); } - public void addOptionalCostPaid(OptionalCost cost) { costsPaid.add(cost); } - public Iterable getOptionalCostsPaid() { return costsPaid; } - public boolean isOptionalCostPaid(OptionalCost cost) { return costsPaid.contains(cost); } - - /** - * Fetch GameState for this card from references to players who may own or control this card. - */ - @Override - public Game getGame() { - Player controller = getController(); - if (null != controller) - return controller.getGame(); - - Player owner = getOwner(); - if (null != owner) - return owner.getGame(); - - throw new IllegalStateException("Card " + toString() + " has no means to determine the game it belongs to!"); - } - - /** - * TODO: Write javadoc for this method. - * @param card - * @param game TODO - * @param player - * @return - */ - public List getAllPossibleAbilities(Player player, boolean removeUnplayable) { - // this can only be called by the Human - - final List abilities = new ArrayList(); - for (SpellAbility sa : getSpellAbilities()) { - //add alternative costs as additional spell abilities - abilities.add(sa); - abilities.addAll(GameActionUtil.getAlternativeCosts(sa)); - } - - for (int i = abilities.size() - 1; i >= 0; i--) { - SpellAbility sa = abilities.get(i); - sa.setActivatingPlayer(player); - if (removeUnplayable && !sa.canPlay()) { - abilities.remove(i); - } - else if (!sa.isPossible()) { - abilities.remove(i); - } - } - - if (isLand() && player.canPlayLand(this)) { - Ability.PLAY_LAND_SURROGATE.setSourceCard(this); - abilities.add(Ability.PLAY_LAND_SURROGATE); - } - - return abilities; - } - - public static Card fromPaperCard(IPaperCard pc, Player owner) { - return CardFactory.getCard(pc, owner); - } - - private static final Map cp2card = new HashMap(); - public static Card getCardForUi(IPaperCard pc) { - if (pc instanceof PaperCard) { - Card res = cp2card.get(pc); - if (res == null) { - res = fromPaperCard(pc, null); - cp2card.put((PaperCard) pc, res); - } - return res; - } - return fromPaperCard(pc, null); - } - - // Fetch from Forge's Card instance. Well, there should be no errors, but - // we'll still check - public PaperCard getPaperCard() { - final String name = getName(); - final String set = getCurSetCode(); - - if (StringUtils.isNotBlank(set)) { - PaperCard cp = StaticData.instance().getVariantCards().tryGetCard(name, set); - return cp == null ? StaticData.instance().getCommonCards().getCard(name, set) : cp; - } - PaperCard cp = StaticData.instance().getVariantCards().tryGetCard(name, true); - return cp == null ? StaticData.instance().getCommonCards().getCard(name) : cp; - } - - /** - * Update Card instance for the given PaperCard if any - * @param pc - */ - public static void updateCard(PaperCard pc) { - Card res = cp2card.get(pc); - if (res != null) { - cp2card.put(pc, fromPaperCard(pc, null)); - } - } - - /** return staticCommanderList */ - public List getStaticCommandList() { - return staticCommandList; - } - - /** return staticCommanderList */ - public void addStaticCommandList(Object[] objects) { - this.staticCommandList.add(objects); - } -} // end Card class +/* + * 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 . + */ +package forge.game.card; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; + +import com.esotericsoftware.minlog.Log; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +import forge.Command; +import forge.Singletons; +import forge.card.CardCharacteristicName; +import forge.card.CardEdition; +import forge.card.CardRarity; +import forge.card.CardRules; +import forge.card.ColorSet; +import forge.card.MagicColor; +import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostParser; +import forge.game.Game; +import forge.game.GameActionUtil; +import forge.game.GameEntity; +import forge.game.GameLogEntryType; +import forge.game.GlobalRuleChange; +import forge.game.ability.AbilityFactory; +import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; +import forge.game.card.CardPredicates.Presets; +import forge.game.combat.AttackingBand; +import forge.game.combat.Combat; +import forge.game.cost.Cost; +import forge.game.event.GameEventCardDamaged; +import forge.game.event.GameEventCardAttachment.AttachMethod; +import forge.game.event.GameEventCardDamaged.DamageType; +import forge.game.event.GameEventCardAttachment; +import forge.game.event.GameEventCardCounters; +import forge.game.event.GameEventCardPhased; +import forge.game.event.GameEventCardStatsChanged; +import forge.game.event.GameEventCardTapped; +import forge.game.player.Player; +import forge.game.replacement.ReplaceMoved; +import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementResult; +import forge.game.spellability.Ability; +import forge.game.spellability.AbilityTriggered; +import forge.game.spellability.OptionalCost; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellPermanent; +import forge.game.spellability.TargetRestrictions; +import forge.game.staticability.StaticAbility; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerType; +import forge.game.trigger.ZCTrigger; +import forge.game.zone.Zone; +import forge.game.zone.ZoneType; +import forge.item.IPaperCard; +import forge.item.PaperCard; +import forge.util.Expressions; +import forge.util.Lang; +import forge.util.MyRandom; +import forge.util.TextUtil; + +/** + *

+ * Card class. + *

+ * + * Can now be used as keys in Tree data structures. The comparison is based + * entirely on getUniqueNumber(). + * + * @author Forge + * @version $Id$ + */ +public class Card extends GameEntity implements Comparable { + private final int uniqueNumber; + + private final Map characteristicsMap + = new EnumMap(CardCharacteristicName.class); + private CardCharacteristicName curCharacteristics = CardCharacteristicName.Original; + private CardCharacteristicName preTFDCharacteristic = CardCharacteristicName.Original; + + private ZoneType castFrom = null; + + private final CardDamageHistory damageHistory = new CardDamageHistory(); + private Map counters = new TreeMap(); + private Map> countersAddedBy = new TreeMap>(); + private ArrayList extrinsicKeyword = new ArrayList(); + // Hidden keywords won't be displayed on the card + private final ArrayList hiddenExtrinsicKeyword = new ArrayList(); + + // which equipment cards are equipping this card? + private ArrayList equippedBy = new ArrayList(); + // equipping size will always be 0 or 1 + // if this card is of the type equipment, what card is it currently equipping? + private ArrayList equipping = new ArrayList(); + + // which fortification cards are fortifying this card? + private ArrayList fortifiedBy = new ArrayList(); + // fortifying size will always be 0 or 1 + // if this card is of the type fortification, what card is it currently fortifying? + private ArrayList fortifying = new ArrayList(); + + // which auras enchanted this card? + // if this card is an Aura, what Entity is it enchanting? + private GameEntity enchanting = null; + + // changes by AF animate and continuous static effects - timestamp is the key of maps + private Map changedCardTypes = new ConcurrentSkipListMap(); + private Map changedCardKeywords = new ConcurrentSkipListMap(); + + private final ArrayList rememberedObjects = new ArrayList(); + private final ArrayList imprintedCards = new ArrayList(); + private final ArrayList encodedCards = new ArrayList(); + private final List devouredCards = new ArrayList(); + private Map flipResult = new TreeMap(); + + private Map receivedDamageFromThisTurn = new TreeMap(); + private Map dealtDamageToThisTurn = new TreeMap(); + private Map dealtDamageToPlayerThisTurn = new TreeMap(); + private final Map assignedDamageMap = new TreeMap(); + private List blockedThisTurn = null; + private List blockedByThisTurn = null; + + private boolean isCommander = false; + private boolean startsGameInPlay = false; + private boolean drawnThisTurn = false; + private boolean becameTargetThisTurn = false; + private boolean startedTheTurnUntapped = false; + private boolean tapped = false; + private boolean sickness = true; // summoning sickness + private boolean token = false; + private boolean copiedToken = false; + private boolean copiedSpell = false; + + private ArrayList mustBlockCards = null; + + private boolean canCounter = true; + private boolean evoked = false; + + private boolean unearthed; + + private boolean monstrous = false; + private int monstrosityNum = 0; + + private long bestowTimestamp = -1; + private boolean suspendCast = false; + private boolean suspend = false; + private boolean tributed = false; + + private boolean phasedOut = false; + private boolean directlyPhasedOut = true; + + private boolean usedToPayCost = false; + + // for Vanguard / Manapool / Emblems etc. + private boolean isImmutable = false; + + private long timestamp = -1; // permanents on the battlefield + + // stack of set power/toughness + private ArrayList newPT = new ArrayList(); + private int baseLoyalty = 0; + private String baseAttackString = null; + private String baseDefenseString = null; + + private int damage; + + // regeneration + private List nShield = new ArrayList(); + private int regeneratedThisTurn = 0; + + private int turnInZone; + + private int tempAttackBoost = 0; + private int tempDefenseBoost = 0; + + private int semiPermanentAttackBoost = 0; + private int semiPermanentDefenseBoost = 0; + + private int xManaCostPaid = 0; + + private int sunburstValue = 0; + private byte colorsPaid = 0; + + private Player owner = null; + private Player controller = null; + private long controllerTimestamp = 0; + private TreeMap tempControllers = new TreeMap(); + + private String text = ""; + private String echoCost = ""; + private Cost miracleCost = null; + private String chosenType = ""; + private List chosenColor = new ArrayList(); + private String namedCard = ""; + private int chosenNumber; + private Player chosenPlayer; + private List chosenCard = new ArrayList(); + + private Card cloneOrigin = null; + private final List clones = new ArrayList(); + private final List gainControlTargets = new ArrayList(); + + private final List zcTriggers = new ArrayList(); + private final List untapCommandList = new ArrayList(); + private final List changeControllerCommandList = new ArrayList(); + private final List staticCommandList = new ArrayList(); + + private final static ImmutableList storableSVars = ImmutableList.of("ChosenX"); + + private final List hauntedBy = new ArrayList(); + private Card haunting = null; + private Card effectSource = null; + + // Soulbond pairing card + private Card pairedWith = null; + + // Zone-changing spells should store card's zone here + private Zone currentZone = null; + + private int countersAdded = 0; + + // Enumeration for CMC request types + public enum SplitCMCMode { + CurrentSideCMC, + CombinedCMC, + LeftSplitCMC, + RightSplitCMC + } + + /** + * Instantiates a new card. + */ + public Card(int id) { + this.uniqueNumber = id; + this.characteristicsMap.put(CardCharacteristicName.Original, new CardCharacteristics()); + this.characteristicsMap.put(CardCharacteristicName.FaceDown, CardUtil.getFaceDownCharacteristic()); + } + + /** + * Sets the state. + * + * @param state + * the state + * @return true, if successful + */ + public boolean changeToState(final CardCharacteristicName state) { + + CardCharacteristicName cur = this.curCharacteristics; + + if (!setState(state)) { + return false; + } + + if ((cur == CardCharacteristicName.Original && state == CardCharacteristicName.Transformed) + || (cur == CardCharacteristicName.Transformed && state == CardCharacteristicName.Original)) { + HashMap runParams = new HashMap(); + runParams.put("Transformer", this); + getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false); + } + + return true; + } + + /** + * Sets the state. + * + * @param state + * the state + * @return true, if successful + */ + public boolean setState(final CardCharacteristicName state) { + if (state == CardCharacteristicName.FaceDown && this.isDoubleFaced()) { + return false; // Doublefaced cards can't be turned face-down. + } + + if (!this.characteristicsMap.containsKey(state)) { + System.out.println(this.getName() + " tried to switch to non-existant state \"" + state + "\"!"); + return false; // Nonexistant state. + } + + if (state.equals(this.curCharacteristics)) { + return false; + } + + this.curCharacteristics = state; + + return true; + } + + /** + * Gets the states. + * + * @return the states + */ + public Set getStates() { + return this.characteristicsMap.keySet(); + } + + /** + * Gets the cur state. + * + * @return the cur state + */ + public CardCharacteristicName getCurState() { + return this.curCharacteristics; + } + + /** + * Switch states. + * + * @param from + * the from + * @param to + * the to + */ + public void switchStates(final CardCharacteristicName from, final CardCharacteristicName to) { + final CardCharacteristics tmp = this.characteristicsMap.get(from); + this.characteristicsMap.put(from, this.characteristicsMap.get(to)); + this.characteristicsMap.put(to, tmp); + } + + /** + * Clear states. + * + * @param state + * the state + */ + public void clearStates(final CardCharacteristicName state) { + this.characteristicsMap.remove(state); + } + + public void setPreFaceDownCharacteristic(CardCharacteristicName preCharacteristic) { + this.preTFDCharacteristic = preCharacteristic; + } + + /** + * Turn face down. + * + * @return true, if successful + */ + public boolean turnFaceDown() { + if (!this.isDoubleFaced()) { + this.preTFDCharacteristic = this.curCharacteristics; + return this.setState(CardCharacteristicName.FaceDown); + } + + return false; + } + + /** + * Turn face up. + * + * @return true, if successful + */ + public boolean turnFaceUp() { + if (this.curCharacteristics == CardCharacteristicName.FaceDown) { + boolean result = this.setState(this.preTFDCharacteristic); + if (result) { + // Run replacement effects + HashMap repParams = new HashMap(); + repParams.put("Event", "TurnFaceUp"); + repParams.put("Affected", this); + getGame().getReplacementHandler().run(repParams); + + // Run triggers + final Map runParams = new TreeMap(); + runParams.put("Card", this); + getGame().getTriggerHandler().runTrigger(TriggerType.TurnFaceUp, runParams, false); + } + return result; + } + + return false; + } + + /** + * Gets the state. + * + * @param state + * the state + * @return the state + */ + public CardCharacteristics getState(final CardCharacteristicName state) { + return this.characteristicsMap.get(state); + } + + /** + * Gets the characteristics. + * + * @return the characteristics + */ + public CardCharacteristics getCharacteristics() { + return this.characteristicsMap.get(this.curCharacteristics); + } + + /** + * addAlternateState. + * + * @param state + * the state + */ + public final void addAlternateState(final CardCharacteristicName state) { + this.characteristicsMap.put(state, new CardCharacteristics()); + } + + /* + * (non-Javadoc) + * + * @see forge.GameEntity#getName() + */ + @Override + public final String getName() { + return this.getCharacteristics().getName(); + } + + /* + * (non-Javadoc) + * + * @see forge.GameEntity#setName(java.lang.String) + */ + @Override + public final void setName(final String name0) { + this.getCharacteristics().setName(name0); + } + + /** + * + * isInAlternateState. + * + * @return boolean + */ + public final boolean isInAlternateState() { + return this.curCharacteristics != CardCharacteristicName.Original + && this.curCharacteristics != CardCharacteristicName.Cloned; + } + + /** + * + * hasAlternateState. + * + * @return boolean + */ + public final boolean hasAlternateState() { + return this.characteristicsMap.keySet().size() > 2; + } + + public final boolean isDoubleFaced() { + return characteristicsMap.containsKey(CardCharacteristicName.Transformed); + } + + public final boolean isFlipCard() { + return characteristicsMap.containsKey(CardCharacteristicName.Flipped); + } + + public final boolean isSplitCard() { + return characteristicsMap.containsKey(CardCharacteristicName.LeftSplit); + } + + /** + * Checks if is cloned. + * + * @return true, if is cloned + */ + public boolean isCloned() { + return characteristicsMap.containsKey(CardCharacteristicName.Cloner); + } + + /** + * + * TODO Write javadoc for this method. + * + * @return a String array + */ + public static List getStorableSVars() { + return Card.storableSVars; + } + + /** + * + * TODO Write javadoc for this method. + * + * @param c + * a Card object + */ + public final void addDevoured(final Card c) { + this.devouredCards.add(c); + } + + /** + * + * TODO Write javadoc for this method. + */ + public final void clearDevoured() { + this.devouredCards.clear(); + } + + /** + * + * TODO Write javadoc for this method. + * + * @return a List object + */ + public final List getDevoured() { + return this.devouredCards; + } + + /** + *

+ * addRemembered. + *

+ * + * @param o + * a {@link java.lang.Object} object. + */ + public final void addRemembered(final Object o) { + this.rememberedObjects.add(o); + } + + /** + *

+ * removeRemembered. + *

+ * + * @param o + * a {@link java.lang.Object} object. + */ + public final void removeRemembered(final Object o) { + this.rememberedObjects.remove(o); + } + + /** + *

+ * getRemembered. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getRemembered() { + return this.rememberedObjects; + } + + /** + *

+ * clearRemembered. + *

+ */ + public final void clearRemembered() { + this.rememberedObjects.clear(); + } + + /** + *

+ * addImprinted. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + */ + public final void addImprinted(final Card c) { + this.imprintedCards.add(c); + } + + /** + *

+ * addImprinted. + *

+ * + * @param list + * a {@link java.util.ArrayList} object. + */ + public final void addImprinted(final ArrayList list) { + this.imprintedCards.addAll(list); + } + + /** + * TODO: Write javadoc for this method. + */ + public final void removeImprinted(final Object o) { + this.imprintedCards.remove(o); + } + + /** + *

+ * getImprinted. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getImprinted() { + return this.imprintedCards; + } + + /** + *

+ * clearImprinted. + *

+ */ + public final void clearImprinted() { + this.imprintedCards.clear(); + } + + /** + *

+ * addEncoded. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + */ + public final void addEncoded(final Card c) { + this.encodedCards.add(c); + } + + /** + *

+ * addEncoded. + *

+ * + * @param list + * a {@link java.util.ArrayList} object. + */ + public final void addEncoded(final ArrayList list) { + this.encodedCards.addAll(list); + } + + /** + * TODO: Write javadoc for this method. + */ + public final void removeEncoded(final Object o) { + this.encodedCards.remove(o); + } + + /** + *

+ * getEncoded. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getEncoded() { + return this.encodedCards; + } + + /** + *

+ * clearEncoded. + *

+ */ + public final void clearEncoded() { + this.encodedCards.clear(); + } + + /** + *

+ * addFlipResult. + *

+ * + * @param flipper The Player who flipped the coin. + * @param result The result of the coin flip as a String. + */ + public final void addFlipResult(final Player flipper, final String result) { + this.flipResult.put(flipper, result); + } + + /** + *

+ * getFlipResult. + *

+ * + * @param flipper The Player who flipped the coin. + * @return a String result - Heads or Tails. + */ + public final String getFlipResult(final Player flipper) { + return this.flipResult.get(flipper); + } + + /** + *

+ * clearFlipResult. + *

+ */ + public final void clearFlipResult() { + this.flipResult.clear(); + } + + /** + *

+ * addTrigger. + *

+ * + * @param t + * a {@link forge.game.trigger.Trigger} object. + * @return a {@link forge.game.trigger.Trigger} object. + */ + public final Trigger addTrigger(final Trigger t) { + final Trigger newtrig = t.getCopyForHostCard(this); + this.getCharacteristics().getTriggers().add(newtrig); + return newtrig; + } + + /** + * + * moveTrigger. + * + * @param t + * a Trigger + */ + public final void moveTrigger(final Trigger t) { + t.setHostCard(this); + if (!this.getCharacteristics().getTriggers().contains(t)) { + this.getCharacteristics().getTriggers().add(t); + } + } + + /** + *

+ * removeTrigger. + *

+ * + * @param t + * a {@link forge.game.trigger.Trigger} object. + */ + public final void removeTrigger(final Trigger t) { + this.getCharacteristics().getTriggers().remove(t); + } + + /** + *

+ * removeTrigger. + *

+ * + * @param t + * a {@link forge.game.trigger.Trigger} object. + * + * @param state + * a {@link forge.card.CardCharacteristicName} object. + */ + public final void removeTrigger(final Trigger t, final CardCharacteristicName state) { + CardCharacteristics stateCharacteristics = this.getState(state); + stateCharacteristics.getTriggers().remove(t); + } + + /** + *

+ * Getter for the field triggers. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final List getTriggers() { + return this.getCharacteristics().getTriggers(); + } + + /** + *

+ * Setter for the field triggers. + *

+ * + * @param trigs + * a {@link java.util.ArrayList} object. + */ + public final void setTriggers(final List trigs, boolean intrinsicOnly) { + final List copyList = new CopyOnWriteArrayList(); + for (final Trigger t : trigs) { + if (!intrinsicOnly || t.isIntrinsic()) { + copyList.add(t.getCopyForHostCard(this)); + } + } + + this.getCharacteristics().setTriggers(copyList); + } + + /** + *

+ * clearTriggersNew. + *

+ */ + public final void clearTriggersNew() { + this.getCharacteristics().getTriggers().clear(); + } + + /** + *

+ * getTriggeringObject. + *

+ * + * @param typeIn + * a {@link java.lang.String} object. + * @return a {@link java.lang.Object} object. + */ + public final Object getTriggeringObject(final String typeIn) { + Object triggered = null; + if (!this.getCharacteristics().getTriggers().isEmpty()) { + for (final Trigger t : this.getCharacteristics().getTriggers()) { + final SpellAbility sa = t.getTriggeredSA(); + triggered = sa.getTriggeringObject(typeIn); + if (triggered != null) { + break; + } + } + } + return triggered; + } + + /** + *

+ * Getter for the field sunburstValue. + *

+ * + * @return a int. + */ + public final int getSunburstValue() { + return this.sunburstValue; + } + + // TODO: Append colors instead of replacing + public final void setColorsPaid(final byte s) { + this.colorsPaid = s; + } + + /** + *

+ * Getter for the field colorsPaid. + *

+ * + * @return a String. + */ + public final byte getColorsPaid() { + return this.colorsPaid; + } + + /** + *

+ * Setter for the field sunburstValue. + *

+ * + * @param valueIn + * a int. + */ + public final void setSunburstValue(final int valueIn) { + this.sunburstValue = valueIn; + } + + /** + *

+ * addXManaCostPaid. + *

+ * + * @param n + * a int. + */ + public final void addXManaCostPaid(final int n) { + this.xManaCostPaid += n; + } + + /** + *

+ * Setter or the field xManaCostPaid. + *

+ * + * @param n + * a int. + */ + public final void setXManaCostPaid(final int n) { + this.xManaCostPaid = n; + } + + /** + *

+ * Getter for the field xManaCostPaid. + *

+ * + * @return a int. + */ + public final int getXManaCostPaid() { + return this.xManaCostPaid; + } + + /** + * @return the blockedThisTurn + */ + public List getBlockedThisTurn() { + return blockedThisTurn; + } + + /** + * @param attacker the blockedThisTurn to set + */ + public void addBlockedThisTurn(Card attacker) { + if (this.blockedThisTurn == null) { + this.blockedThisTurn = new ArrayList(); + } + this.blockedThisTurn.add(attacker); + } + + /** + *

+ * clearBlockedThisTurn. + *

+ */ + public void clearBlockedThisTurn() { + this.blockedThisTurn = null; + } + + /** + * @return the blockedByThisTurn + */ + public List getBlockedByThisTurn() { + return blockedByThisTurn; + } + + /** + * @param blocker the blockedByThisTurn to set + */ + public void addBlockedByThisTurn(Card blocker) { + if (this.blockedByThisTurn == null) { + this.blockedByThisTurn = new ArrayList(); + } + this.blockedByThisTurn.add(blocker); + } + + /** + *

+ * clearBlockedByThisTurn. + *

+ */ + public void clearBlockedByThisTurn() { + this.blockedByThisTurn = null; + } + + /** + * a Card that this Card must block if able in an upcoming combat. This is + * cleared at the end of each turn. + * + * @param c + * Card to block + * + * @since 1.1.6 + */ + public final void addMustBlockCard(final Card c) { + if (mustBlockCards == null) { + mustBlockCards = new ArrayList(); + } + this.mustBlockCards.add(c); + } + + public final void addMustBlockCards(final List attackersToBlock) { + if (mustBlockCards == null) { + mustBlockCards = new ArrayList(); + } + this.mustBlockCards.addAll(attackersToBlock); + } + + /** + * get the Card that this Card must block this combat. + * + * @return the Cards to block (if able) + * + * @since 1.1.6 + */ + public final ArrayList getMustBlockCards() { + return this.mustBlockCards; + } + + /** + * clear the list of Cards that this Card must block this combat. + * + * @since 1.1.6 + */ + public final void clearMustBlockCards() { + this.mustBlockCards = null; + } + + /** + *

+ * Getter for the field clones. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final List getClones() { + return this.clones; + } + + /** + *

+ * Setter for the field clones. + *

+ * + * @param c + * a {@link java.util.ArrayList} object. + */ + public final void setClones(final Collection c) { + this.clones.clear(); + this.clones.addAll(c); + } + + /** + *

+ * addClone. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + */ + public final void addClone(final Card c) { + this.clones.add(c); + } + + /** + *

+ * clearClones. + *

+ */ + public final void clearClones() { + this.clones.clear(); + } + + /** + *

+ * Getter for the field cloneOrigin. + *

+ * + * @return a {@link forge.game.card.Card} object. + */ + public final Card getCloneOrigin() { + return this.cloneOrigin; + } + + /** + *

+ * Setter for the field cloneOrigin. + *

+ * + * @param name + * a {@link forge.game.card.Card} object. + */ + public final void setCloneOrigin(final Card name) { + this.cloneOrigin = name; + } + + /** + *

+ * hasFirstStrike. + *

+ * + * @return a boolean. + */ + public final boolean hasFirstStrike() { + return this.hasKeyword("First Strike"); + } + + /** + *

+ * hasDoubleStrike. + *

+ * + * @return a boolean. + */ + public final boolean hasDoubleStrike() { + return this.hasKeyword("Double Strike"); + } + + /** + *

+ * hasSecondStrike. + *

+ * + * @return a boolean. + */ + public final boolean hasSecondStrike() { + return this.hasDoubleStrike() || !this.hasFirstStrike(); + } + + /** + * Can have counters placed on it. + * + * @param type + * the counter name + * @return true, if successful + */ + public final boolean canReceiveCounters(final CounterType type) { + if (this.hasKeyword("CARDNAME can't have counters placed on it.")) { + return false; + } + if (this.isCreature() && type == CounterType.M1M1) { + for (final Card c : this.getController().getCreaturesInPlay()) { // look for Melira, Sylvok Outcast + if (c.hasKeyword("Creatures you control can't have -1/-1 counters placed on them.")) { + return false; + } + } + } else if (type == CounterType.DREAM) { + if (this.hasKeyword("CARDNAME can't have more than seven dream counters on it.") && this.getCounters(CounterType.DREAM) > 6) { + return false; + } + } + return true; + } + + public final int getTotalCountersToAdd() { + return countersAdded; + } + + public final void setTotalCountersToAdd(int value) { + countersAdded = value; + } + + public final void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier) { + int addAmount = n; + final HashMap repParams = new HashMap(); + repParams.put("Event", "AddCounter"); + repParams.put("Affected", this); + repParams.put("CounterType", counterType); + repParams.put("CounterNum", addAmount); + repParams.put("EffectOnly", applyMultiplier); + if (this.getGame().getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { + return; + } + if (this.canReceiveCounters(counterType)) { + if (counterType == CounterType.DREAM && this.hasKeyword("CARDNAME can't have more than seven dream counters on it.")) { + addAmount = Math.min(7 - this.getCounters(CounterType.DREAM), addAmount); + } + } else { + addAmount = 0; + } + + if (addAmount == 0) { + return; + } + this.setTotalCountersToAdd(addAmount); + + Integer oldValue = this.counters.get(counterType); + int newValue = addAmount + (oldValue == null ? 0 : oldValue.intValue()); + this.counters.put(counterType, Integer.valueOf(newValue)); + + // play the Add Counter sound + getGame().fireEvent(new GameEventCardCounters(this, counterType, oldValue == null ? 0 : oldValue.intValue(), newValue)); + + // Run triggers + final Map runParams = new TreeMap(); + runParams.put("Card", this); + runParams.put("CounterType", counterType); + for (int i = 0; i < addAmount; i++) { + getGame().getTriggerHandler().runTrigger(TriggerType.CounterAdded, runParams, false); + } + } + + /** + *

+ * addCountersAddedBy. + *

+ * @param source the card adding the counters to this card + * @param counterType the counter type added + * @param counterAmount the amount of counters added + */ + public final void addCountersAddedBy(final Card source, final CounterType counterType, final int counterAmount) { + final Map counterMap = new TreeMap(); + counterMap.put(counterType, counterAmount); + this.countersAddedBy.put(source, counterMap); + } + + /** + *

+ * getCountersAddedBy. + *

+ * @param source the card the counters were added by + * @param counterType the counter type added + * @return the amount of counters added. + */ + public final int getCountersAddedBy(final Card source, final CounterType counterType) { + int counterAmount = 0; + if (this.countersAddedBy.containsKey(source)) { + final Map counterMap = this.countersAddedBy.get(source); + counterAmount = counterMap.containsKey(counterType) ? counterMap.get(counterType) : 0; + this.countersAddedBy.remove(source); + } + return counterAmount; + } + + /** + *

+ * subtractCounter. + *

+ * + * @param counterName + * a {@link forge.game.card.CounterType} object. + * @param n + * a int. + */ + public final void subtractCounter(final CounterType counterName, final int n) { + Integer oldValue = this.counters.get(counterName); + int newValue = oldValue == null ? 0 : Math.max(oldValue.intValue() - n, 0); + + final int delta = (oldValue == null ? 0 : oldValue.intValue()) - newValue; + if (delta == 0) { + return; + } + + if (newValue > 0) { + this.counters.put(counterName, Integer.valueOf(newValue)); + } else { + this.counters.remove(counterName); + } + + // Play the Subtract Counter sound + getGame().fireEvent(new GameEventCardCounters(this, counterName, oldValue == null ? 0 : oldValue.intValue(), newValue)); + + // Run triggers + int curCounters = oldValue == null ? 0 : oldValue.intValue(); + for (int i = 0; i < delta && curCounters != 0; i++) { + final Map runParams = new TreeMap(); + runParams.put("Card", this); + runParams.put("CounterType", counterName); + runParams.put("NewCounterAmount", --curCounters); + getGame().getTriggerHandler().runTrigger(TriggerType.CounterRemoved, runParams, false); + } + } + + /** + *

+ * Getter for the field counters. + *

+ * + * @param counterName + * a {@link forge.game.card.CounterType} object. + * @return a int. + */ + public final int getCounters(final CounterType counterName) { + Integer value = this.counters.get(counterName); + return value == null ? 0 : value.intValue(); + } + + // get all counters from a card + /** + *

+ * Getter for the field counters. + *

+ * + * @return a Map object. + * @since 1.0.15 + */ + public final Map getCounters() { + return this.counters; + } + + /** + *

+ * hasCounters. + *

+ * + * @return a boolean. + */ + public final boolean hasCounters() { + return !this.counters.isEmpty(); + } + + /** + *

+ * Setter for the field counters. + *

+ * + * @param allCounters + * a Map object. + * @since 1.0.15 + */ + public final void setCounters(final Map allCounters) { + this.counters = allCounters; + } + + /** + *

+ * clearCounters. + *

+ * + * @since 1.0.15 + */ + public final void clearCounters() { + this.counters.clear(); + } + + /** + *

+ * getSVar. + *

+ * + * @param var + * a {@link java.lang.String} object. + * @return a {@link java.lang.String} object. + */ + public final String getSVar(final String var) { + return this.getCharacteristics().getSVar(var); + } + + /** + *

+ * hasSVar. + *

+ * + * @param var + * a {@link java.lang.String} object. + */ + public final boolean hasSVar(final String var) { + return this.getCharacteristics().hasSVar(var); + } + + /** + *

+ * setSVar. + *

+ * + * @param var + * a {@link java.lang.String} object. + * @param str + * a {@link java.lang.String} object. + */ + public final void setSVar(final String var, final String str) { + this.getCharacteristics().setSVar(var, str); + } + + /** + *

+ * getSVars. + *

+ * + * @return a Map object. + */ + public final Map getSVars() { + return this.getCharacteristics().getSVars(); + } + + /** + *

+ * setSVars. + *

+ * + * @param newSVars + * a Map object. + */ + public final void setSVars(final Map newSVars) { + this.getCharacteristics().setSVars(newSVars); + } + + /** + *

+ * sumAllCounters. + *

+ * + * @return a int. + */ + public final int sumAllCounters() { + int count = 0; + for (final Integer value2 : this.counters.values()) { + count += value2.intValue(); + } + return count; + } + + + /** + *

+ * Getter for the field turnInZone. + *

+ * + * @return a int. + */ + public final int getTurnInZone() { + return this.turnInZone; + } + + /** + *

+ * Setter for the field turnInZone. + *

+ * + * @param turn + * a int. + */ + public final void setTurnInZone(final int turn) { + this.turnInZone = turn; + } + + /** + *

+ * Setter for the field echoCost. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void setEchoCost(final String s) { + this.echoCost = s; + } + + public final String getEchoCost() { + return this.echoCost; + } + + /** + *

+ * Setter for the field manaCost. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void setManaCost(final ManaCost s) { + this.getCharacteristics().setManaCost(s); + } + + /** + *

+ * Getter for the field manaCost. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final ManaCost getManaCost() { + return this.getCharacteristics().getManaCost(); + } + + + /** + *

+ * addColor. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void addColor(String s) { + if (s.equals("")) { + s = "0"; + } + ManaCost mc = new ManaCost(new ManaCostParser(s)); + this.getCharacteristics().getCardColor().add(new CardColor(mc.getColorProfile())); + } + + /** + *

+ * addColor. + *

+ * + * @param s + * a {@link java.lang.String} object. + * @param c + * a {@link forge.game.card.Card} object. + * @param addToColors + * a boolean. + * @param bIncrease + * a boolean. + * @return a long. + */ + public final long addColor(final String s, final boolean addToColors, final boolean bIncrease) { + if (bIncrease) { + CardColor.increaseTimestamp(); + } + this.getCharacteristics().getCardColor().add(new CardColor(s, addToColors)); + return CardColor.getTimestamp(); + } + + /** + *

+ * removeColor. + *

+ * + * @param s + * a {@link java.lang.String} object. + * @param c + * a {@link forge.game.card.Card} object. + * @param addTo + * a boolean. + * @param timestampIn + * a long. + */ + public final void removeColor(final String s, final Card c, final boolean addTo, final long timestampIn) { + CardColor removeCol = null; + for (final CardColor cc : this.getCharacteristics().getCardColor()) { + if (cc.equals(s, c, addTo, timestampIn)) { + removeCol = cc; + } + } + + if (removeCol != null) { + this.getCharacteristics().getCardColor().remove(removeCol); + } + } + + /** + *

+ * setColor. + *

+ * + * @param colors + * a {@link java.util.ArrayList} object. + */ + public final void setColor(final Iterable colors) { + this.getCharacteristics().setCardColor(colors); + } + + /** + *

+ * getColor. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final List getColor() { + return this.getCharacteristics().getCardColor(); + } + + /** + * + * TODO Write javadoc for this method. + * + * @param globalChanges + * an ArrayList + * @return a CardColor + */ + public final ColorSet determineColor() { + if (this.isImmutable()) { + return ColorSet.getNullColor(); + } + + List colorList = this.getCharacteristics().getCardColor(); + + byte colors = 0; + for (int i = colorList.size() - 1;i >= 0;i--) { + final CardColor cc = colorList.get(i); + colors |= cc.getColorMask(); + if (!cc.isAdditional()) { + return ColorSet.fromMask(colors); + } + } + return ColorSet.fromMask(colors); + } + + /** + *

+ * Getter for the field chosenPlayer. + *

+ * + * @return a Player + * @since 1.1.6 + */ + public final Player getChosenPlayer() { + return this.chosenPlayer; + } + + /** + *

+ * Setter for the field chosenNumber. + *

+ * + * @param p + * an int + * @since 1.1.6 + */ + public final void setChosenPlayer(final Player p) { + this.chosenPlayer = p; + } + + /** + *

+ * Getter for the field chosenNumber. + *

+ * + * @return an int + */ + public final int getChosenNumber() { + return this.chosenNumber; + } + + /** + *

+ * Setter for the field chosenNumber. + *

+ * + * @param i + * an int + */ + public final void setChosenNumber(final int i) { + this.chosenNumber = i; + } + + // used for cards like Belbe's Portal, Conspiracy, Cover of Darkness, etc. + /** + *

+ * Getter for the field chosenType. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getChosenType() { + return this.chosenType; + } + + /** + *

+ * Setter for the field chosenType. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void setChosenType(final String s) { + this.chosenType = s; + } + + /** + *

+ * Getter for the field chosenColor. + *

+ * + * @return an ArrayList object. + */ + public final List getChosenColor() { + return this.chosenColor; + } + + /** + *

+ * Setter for the field chosenColor. + *

+ * + * @param s + * an ArrayList object. + */ + public final void setChosenColor(final List s) { + this.chosenColor = s; + } + + /** + *

+ * Getter for the field chosenCard. + *

+ * + * @return an ArrayList object. + */ + public final List getChosenCard() { + return this.chosenCard; + } + + /** + *

+ * Setter for the field chosenCard. + *

+ * + * @param c + * an ArrayList object. + */ + public final void setChosenCard(final List c) { + this.chosenCard = c; + } + + // used for cards like Meddling Mage... + /** + *

+ * Getter for the field namedCard. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getNamedCard() { + return this.namedCard; + } + + /** + *

+ * Setter for the field namedCard. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void setNamedCard(final String s) { + this.namedCard = s; + } + + /** + *

+ * Setter for the field drawnThisTurn. + *

+ * + * @param b + * a boolean. + */ + public final void setDrawnThisTurn(final boolean b) { + this.drawnThisTurn = b; + } + + /** + *

+ * Getter for the field drawnThisTurn. + *

+ * + * @return a boolean. + */ + public final boolean getDrawnThisTurn() { + return this.drawnThisTurn; + } + + /** + * get a list of Cards this card has gained control of. + *

+ * used primarily with AbilityFactory_GainControl + * + * @return a list of cards this card has gained control of + */ + public final List getGainControlTargets() { + return this.gainControlTargets; + } + + /** + * add a Card to the list of Cards this card has gained control of. + *

+ * used primarily with AbilityFactory_GainControl + * + * @param c + * a {@link forge.game.card.Card} object. + */ + public final void addGainControlTarget(final Card c) { + this.gainControlTargets.add(c); + } + + /** + * clear the list of Cards this card has gained control of. + *

+ * used primarily with AbilityFactory_GainControl + */ + public final void removeGainControlTargets(final Card c) { + this.gainControlTargets.remove(c); + } + + /** + *

+ * getSpellText. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getSpellText() { + return this.text; + } + + /** + *

+ * Setter for the field text. + *

+ * + * @param t + * a {@link java.lang.String} object. + */ + public final void setText(final String t) { + this.text = t; + } + + // get the text that should be displayed + /** + *

+ * Getter for the field text. + *

+ * + * @return a {@link java.lang.String} object. + */ + public String getText() { + final StringBuilder sb = new StringBuilder(); + + // Vanguard Modifiers + if (this.isType("Vanguard")) { + sb.append("Hand Modifier: ").append(getRules().getHand()); + sb.append("\r\nLife Modifier: ").append(getRules().getLife()); + sb.append("\r\n\r\n"); + } + if (this.isCommander) + { + sb.append(getOwner() + "'s Commander\r\n\r\n"); + } + if (this.getName().equals("Commander effect")) + { + sb.append("Zone: " + getOwner().getCommander().getZone().toString() + "\r\n"); + sb.append(CardFactoryUtil.getCommanderInfo(getOwner())); + } + sb.append(this.getAbilityText()); + + String nonAbilityText = this.getNonAbilityText(); + if (this.getAmountOfKeyword("CARDNAME can block an additional creature.") > 1) { + final StringBuilder ab = new StringBuilder(); + ab.append("CARDNAME can block an additional "); + ab.append(this.getAmountOfKeyword("CARDNAME can block an additional creature.")); + ab.append(" creatures."); + nonAbilityText = nonAbilityText.replaceFirst("CARDNAME can block an additional creature.", ab.toString()); + nonAbilityText = nonAbilityText.replaceAll("CARDNAME can block an additional creature.", ""); + nonAbilityText = nonAbilityText.replaceAll("\r\n\r\n\r\n", ""); + } + if (nonAbilityText.length() > 0) { + sb.append("\r\n \r\nNon ability features: \r\n"); + sb.append(nonAbilityText.replaceAll("CARDNAME", this.getName())); + } + + // Remembered cards + if (this.rememberedObjects.size() > 0) { + sb.append("\r\nRemembered: \r\n"); + for (final Object o : this.rememberedObjects) { + if (o instanceof Card) { + final Card c = (Card) o; + if (c.isFaceDown()) { + sb.append("Face Down "); + } else { + sb.append(c.getName()); + } + sb.append("("); + sb.append(c.getUniqueNumber()); + sb.append(")"); + } else if (o != null) { + sb.append(o.toString()); + } + sb.append("\r\n"); + } + } + + if (this.chosenPlayer != null) { + sb.append("\r\n[Chosen player: "); + sb.append(this.getChosenPlayer()); + sb.append("]\r\n"); + } + + if (this.hauntedBy.size() != 0) { + sb.append("Haunted by: "); + for (final Card c : this.hauntedBy) { + sb.append(c).append(","); + } + sb.deleteCharAt(sb.length() - 1); + sb.append("\r\n"); + } + + if (this.haunting != null) { + sb.append("Haunting: ").append(this.haunting); + sb.append("\r\n"); + } + + if (this.pairedWith != null) { + sb.append("\r\n \r\nPaired With: ").append(this.pairedWith); + sb.append("\r\n"); + } + + if (this.characteristicsMap.get(CardCharacteristicName.Cloner) != null) { + sb.append("\r\nCloned by: ").append(this.characteristicsMap.get(CardCharacteristicName.Cloner).getName()).append(" (") + .append(this.getUniqueNumber()).append(")"); + } + + return sb.toString(); + } + + // get the text that does not belong to a cards abilities (and is not really + // there rules-wise) + /** + *

+ * getNonAbilityText. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getNonAbilityText() { + final StringBuilder sb = new StringBuilder(); + final ArrayList keyword = this.getHiddenExtrinsicKeyword(); + + sb.append(this.keywordsToText(keyword)); + + return sb.toString(); + } + + // convert a keyword list to the String that should be displayed ingame + /** + *

+ * keywordsToText. + *

+ * + * @param keywords + * a {@link java.util.ArrayList} object. + * @return a {@link java.lang.String} object. + */ + public final String keywordsToText(final ArrayList keywords) { + final StringBuilder sb = new StringBuilder(); + final StringBuilder sbLong = new StringBuilder(); + final StringBuilder sbMana = new StringBuilder(); + + for (int i = 0; i < keywords.size(); i++) { + String keyword = keywords.get(i); + if (keyword.startsWith("Permanents don't untap during their controllers' untap steps") + || keyword.startsWith("PreventAllDamageBy") + || keyword.startsWith("CantBlock") + || keyword.startsWith("CantEquip") + || keyword.startsWith("SpellCantTarget")) { + continue; + } + if (keyword.startsWith("etbCounter")) { + final String[] p = keyword.split(":"); + final StringBuilder s = new StringBuilder(); + if (p.length > 4) { + s.append(p[4]); + } else { + final CounterType counter = CounterType.valueOf(p[1]); + final String numCounters = p[2]; + s.append(this.getName()); + s.append(" enters the battlefield with "); + s.append(numCounters); + s.append(" "); + s.append(counter.getName()); + s.append(" counter"); + if ("1" != numCounters) { + s.append("s"); + } + s.append(" on it."); + } + sbLong.append(s).append("\r\n"); + } else if (keyword.startsWith("Protection:")) { + final String[] k = keyword.split(":"); + sbLong.append(k[2]).append("\r\n"); + } else if (keyword.startsWith("Creatures can't attack unless their controller pays")) { + final String[] k = keyword.split(":"); + if (!k[3].equals("no text")) { + sbLong.append(k[3]).append("\r\n"); + } + } else if (keyword.startsWith("Enchant")) { + String k = keyword; + k = k.replace("Curse", ""); + sbLong.append(k).append("\r\n"); + } else if (keyword.startsWith("Fading") || keyword.startsWith("Ripple") || keyword.startsWith("Vanishing")) { + sbLong.append(keyword.replace(":", " ")).append("\r\n"); + } else if (keyword.startsWith("Unearth") || keyword.startsWith("Madness")) { + String[] parts = keyword.split(":"); + sbLong.append(parts[0] + " " + ManaCostParser.parse(parts[1])).append("\r\n"); + } else if (keyword.startsWith("Devour")) { + final String[] parts = keyword.split(":"); + final String extra = parts.length > 2 ? parts[2] : ""; + final String devour = "Devour " + parts[1] + extra; + sbLong.append(devour).append("\r\n"); + } else if (keyword.startsWith("Morph")) { + sbLong.append("Morph"); + if (keyword.contains(":")) { + final Cost mCost = new Cost(keyword.substring(6), true); + if (!mCost.isOnlyManaCost()) { + sbLong.append(" -"); + } + sbLong.append(" ").append(mCost.toString()).delete(sbLong.length() - 2, sbLong.length()); + if (!mCost.isOnlyManaCost()) { + sbLong.append("."); + } + sbLong.append("\r\n"); + } + } else if (keyword.startsWith("Echo")) { + sbLong.append("Echo "); + final String[] upkeepCostParams = keyword.split(":"); + sbLong.append(upkeepCostParams.length > 2 ? "- " + upkeepCostParams[2] : ManaCostParser.parse(upkeepCostParams[1])); + sbLong.append("\r\n"); + } else if (keyword.startsWith("Cumulative upkeep")) { + sbLong.append("Cumulative upkeep "); + final String[] upkeepCostParams = keyword.split(":"); + sbLong.append(upkeepCostParams.length > 2 ? "- " + upkeepCostParams[2] : ManaCostParser.parse(upkeepCostParams[1])); + sbLong.append("\r\n"); + } else if (keyword.startsWith("Amplify")) { + sbLong.append("Amplify "); + final String[] ampParams = keyword.split(":"); + final String magnitude = ampParams[1]; + sbLong.append(magnitude); + sbLong.append("(As this creature enters the battlefield, put a +1/+1 counter on it for each "); + sbLong.append(ampParams[2].replace(",", " and/or ")).append(" card you reveal in your hand.)"); + sbLong.append("\r\n"); + } else if (keyword.startsWith("Alternative Cost")) { + sbLong.append("Has alternative cost."); + } else if (keyword.startsWith("AlternateAdditionalCost")) { + final String costString1 = keyword.split(":")[1]; + final String costString2 = keyword.split(":")[2]; + final Cost cost1 = new Cost(costString1, false); + final Cost cost2 = new Cost(costString2, false); + sbLong.append("As an additional cost to cast " + this.getName() + ", " + cost1.toSimpleString() + + " or pay " + cost2.toSimpleString() + ".\r\n"); + } else if (keyword.startsWith("Kicker")) { + final Cost cost = new Cost(keyword.substring(7), false); + sbLong.append("Kicker " + cost.toSimpleString() + "\r\n"); + } else if (keyword.endsWith(".") && !keyword.startsWith("Haunt")) { + sbLong.append(keyword.toString()).append("\r\n"); + } else if (keyword.contains("At the beginning of your upkeep, ") + && keyword.contains(" unless you pay")) { + sbLong.append(keyword.toString()).append("\r\n"); + } else if (keyword.toString().contains("tap: add ")) { + sbMana.append(keyword.toString()).append("\r\n"); + } else if (keyword.startsWith("Modular") || keyword.startsWith("Soulshift") || keyword.startsWith("Bloodthirst") + || keyword.startsWith("ETBReplacement") || keyword.startsWith("MayEffectFromOpeningHand")) { + continue; + } else if (keyword.startsWith("Provoke")) { + sbLong.append(keyword); + sbLong.append(" (When this attacks, you may have target creature "); + sbLong.append("defending player controls untap and block it if able.)"); + } else if (keyword.contains("Haunt")) { + sb.append("\r\nHaunt ("); + if (this.isCreature()) { + sb.append("When this creature dies, exile it haunting target creature."); + } else { + sb.append("When this spell card is put into a graveyard after resolving, "); + sb.append("exile it haunting target creature."); + } + sb.append(")"); + continue; + } else if (keyword.equals("Convoke")) { + if (sb.length() != 0) { + sb.append("\r\n"); + } + sb.append("Convoke (Each creature you tap while casting this spell reduces its cost by 1 or by one mana of that creature's color.)"); + } else if (keyword.endsWith(" offering")) { + String offeringType = keyword.split(" ")[0]; + if (sb.length() != 0) { + sb.append("\r\n"); + } + sbLong.append(keyword); + sbLong.append(" (You may cast this card any time you could cast an instant by sacrificing a "); + sbLong.append(offeringType); + sbLong.append("and paying the difference in mana costs between this and the sacrificed "); + sbLong.append(offeringType); + sbLong.append(". Mana cost includes color.)"); + } else if (keyword.startsWith("Soulbond")) { + sbLong.append(keyword); + sbLong.append(" (You may pair this creature "); + sbLong.append("with another unpaired creature when either "); + sbLong.append("enters the battlefield. They remain paired for "); + sbLong.append("as long as you control both of them)"); + } else if (keyword.startsWith("Equip") || keyword.startsWith("Fortify")) { + // keyword parsing takes care of adding a proper description + continue; + } else if (keyword.startsWith("CantBeBlockedBy")) { + sbLong.append(this.getName()).append(" can't be blocked "); + if (keyword.startsWith("CantBeBlockedByAmount")) + sbLong.append(getTextForKwCantBeBlockedByAmount(keyword)); + else + sbLong.append(getTextForKwCantBeBlockedByType(keyword)); + } else if (keyword.equals("Unblockable")) { + sbLong.append(this.getName()).append(" can't be blocked.\r\n"); + } + else { + if ((i != 0) && (sb.length() != 0)) { + sb.append(", "); + } + sb.append(keyword); + } + } + if (sb.length() > 0) { + sb.append("\r\n"); + if (sbLong.length() > 0) { + sb.append("\r\n"); + } + } + if (sbLong.length() > 0) { + sbLong.append("\r\n"); + } + sb.append(sbLong); + sb.append(sbMana); + + return sb.toString(); + } + + private String getTextForKwCantBeBlockedByAmount(String keyword) { + String restriction = keyword.split(" ", 2)[1]; + boolean isLT = "LT".equals(restriction.substring(0,2)); + final String byClause = isLT ? "except by " : "by more than "; + int cnt = Integer.parseInt(restriction.substring(2)); + return byClause + Lang.nounWithNumeral(cnt, isLT ? "or more creature" : "creature"); + } + + private String getTextForKwCantBeBlockedByType(String keyword) { + boolean negative = true; + List subs = Lists.newArrayList(TextUtil.split(keyword.split(" ", 2)[1], ',')); + List> subsAnd = Lists.newArrayList(); + List orClauses = new ArrayList(); + for (int iOr = 0; iOr < subs.size(); iOr++) { + String expession = subs.get(iOr); + List parts = Lists.newArrayList(expession.split("[.+]")); + for (int p = 0; p < parts.size(); p++) { + String part = parts.get(p); + if (part.equalsIgnoreCase("creature")) { + parts.remove(p--); + continue; + } + // based on suppossition that each expression has at least 1 predicate except 'creature' + negative &= part.contains("non") || part.contains("without"); + } + subsAnd.add(parts); + } + + final boolean allNegative = negative; + final String byClause = allNegative ? "except by " : "by "; + + final Function, String> withToString = new Function, String>() { + @Override + public String apply(Pair inp) { + boolean useNon = inp.getKey().booleanValue() == allNegative; + return (useNon ? "*NO* " : "") + inp.getRight(); + } + }; + + for (int iOr = 0; iOr < subsAnd.size(); iOr++) { + List andOperands = subsAnd.get(iOr); + List> prependedAdjectives = Lists.newArrayList(); + List> postponedAdjectives = Lists.newArrayList(); + String creatures = null; + + for (String part : andOperands) { + boolean positive = true; + if (part.startsWith("non")) { + part = part.substring(3); + positive = false; + } + if (part.startsWith("with")) { + positive = !part.startsWith("without"); + postponedAdjectives.add(Pair.of(positive, part.substring(positive ? 4 : 7))); + } else if (part.startsWith("power")) { + int kwLength = 5; + String opName = Expressions.operatorName(part.substring(kwLength, kwLength + 2)); + String operand = part.substring(kwLength + 2); + postponedAdjectives.add(Pair.of(true, "power" + opName + operand)); + } else if (forge.card.CardType.isACreatureType(part)) { + creatures = StringUtils.capitalize(Lang.getPlural(part)) + (creatures == null ? "" : " or " + creatures); + } else { + prependedAdjectives.add(Pair.of(positive, part.toLowerCase())); + } + } + + StringBuilder sbShort = new StringBuilder(); + if (allNegative) { + boolean isFirst = true; + for (Pair pre : prependedAdjectives) { + if (isFirst) isFirst = false; + else sbShort.append(" and/or "); + + boolean useNon = pre.getKey().booleanValue() == allNegative; + if (useNon) sbShort.append("non-"); + sbShort.append(pre.getValue()).append(" ").append(creatures == null ? "creatures" : creatures); + } + if (prependedAdjectives.isEmpty()) + sbShort.append(creatures == null ? "creatures" : creatures); + + if (!postponedAdjectives.isEmpty()) { + if (!prependedAdjectives.isEmpty()) { + sbShort.append(" and/or creatures"); + } + + sbShort.append(" with "); + sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and")); + } + + } else { + for (Pair pre : prependedAdjectives) { + boolean useNon = pre.getKey().booleanValue() == allNegative; + if (useNon) sbShort.append("non-"); + sbShort.append(pre.getValue()).append(" "); + } + sbShort.append(creatures == null ? "creatures" : creatures); + + if (!postponedAdjectives.isEmpty()) { + sbShort.append(" with "); + sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and")); + } + + } + orClauses.add(sbShort.toString()); + } + return byClause + StringUtils.join(orClauses, " or "); + } + + // get the text of the abilities of a card + /** + *

+ * getAbilityText. + *

+ * + * @return a {@link java.lang.String} object. + */ + public String getAbilityText() { + if (this.isInstant() || this.isSorcery()) { + final StringBuilder sb = this.abilityTextInstantSorcery(); + + if (this.haunting != null) { + sb.append("Haunting: ").append(this.haunting); + sb.append("\r\n"); + } + + while (sb.toString().endsWith("\r\n")) { + sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); + } + + return sb.toString().replaceAll("CARDNAME", this.getName()); + } + + final StringBuilder sb = new StringBuilder(); + final ArrayList keyword = this.getUnhiddenKeyword(); + + if (this.monstrous) { + sb.append("Monstrous\r\n"); + } + sb.append(this.keywordsToText(keyword)); + + // Give spellText line breaks for easier reading + sb.append("\r\n"); + sb.append(this.text.replaceAll("\\\\r\\\\n", "\r\n")); + sb.append("\r\n"); + + // Triggered abilities + for (final Trigger trig : this.getCharacteristics().getTriggers()) { + if (!trig.isSecondary()) { + sb.append(trig.toString() + "\r\n"); + } + } + + // Replacement effects + for (final ReplacementEffect replacementEffect : this.getCharacteristics().getReplacementEffects()) { + if (!replacementEffect.isSecondary()) { + sb.append(replacementEffect.toString() + "\r\n"); + } + } + + // static abilities + for (final StaticAbility stAb : this.getCharacteristics().getStaticAbilities()) { + sb.append(stAb.toString() + "\r\n"); + } + + final ArrayList addedManaStrings = new ArrayList(); + boolean primaryCost = true; + for (final SpellAbility sa : this.getSpellAbilities()) { + // only add abilities not Spell portions of cards + if (!this.isPermanent()) { + continue; + } + + if ((sa instanceof SpellPermanent) && primaryCost && !this.isAura()) { + // For Alt costs, make sure to display the cost! + primaryCost = false; + continue; + } + + final String sAbility = sa.toString(); + + if (sa.getManaPart() != null) { + if (addedManaStrings.contains(sAbility)) { + continue; + } + addedManaStrings.add(sAbility); + } + + if ((sa instanceof SpellPermanent) && !this.isAura()) { + sb.insert(0, "\r\n"); + sb.insert(0, sAbility); + } else if (!sAbility.endsWith(this.getName())) { + sb.append(sAbility); + sb.append("\r\n"); + } + } + + // NOTE: + if (sb.toString().contains(" (NOTE: ")) { + sb.insert(sb.indexOf("(NOTE: "), "\r\n"); + } + if (sb.toString().contains("(NOTE: ") && sb.toString().contains(".) ")) { + sb.insert(sb.indexOf(".) ") + 3, "\r\n"); + } + + // replace triple line feeds with double line feeds + int start; + final String s = "\r\n\r\n\r\n"; + while (sb.toString().contains(s)) { + start = sb.lastIndexOf(s); + if ((start < 0) || (start >= sb.length())) { + break; + } + sb.replace(start, start + 4, "\r\n"); + } + + return sb.toString().replaceAll("CARDNAME", this.getName()).trim(); + } // getText() + + /** + * TODO: Write javadoc for this method. + * + * @return + */ + private StringBuilder abilityTextInstantSorcery() { + final String s = this.getSpellText(); + final StringBuilder sb = new StringBuilder(); + + // Give spellText line breaks for easier reading + sb.append(s.replaceAll("\\\\r\\\\n", "\r\n")); + + // NOTE: + if (sb.toString().contains(" (NOTE: ")) { + sb.insert(sb.indexOf("(NOTE: "), "\r\n"); + } + if (sb.toString().contains("(NOTE: ") && sb.toString().endsWith(".)") && !sb.toString().endsWith("\r\n")) { + sb.append("\r\n"); + } + + // Add SpellAbilities + for (final SpellAbility element : this.getSpellAbilities()) { + sb.append(element.toString() + "\r\n"); + } + + // Add Keywords + final List kw = this.getKeyword(); + + // Triggered abilities + for (final Trigger trig : this.getCharacteristics().getTriggers()) { + if (!trig.isSecondary()) { + sb.append(trig.toString() + "\r\n"); + } + } + + // Replacement effects + for (final ReplacementEffect replacementEffect : this.getCharacteristics().getReplacementEffects()) { + sb.append(replacementEffect.toString() + "\r\n"); + } + + // static abilities + for (final StaticAbility stAb : this.getCharacteristics().getStaticAbilities()) { + final String stAbD = stAb.toString(); + if (!stAbD.equals("")) { + sb.append(stAbD + "\r\n"); + } + } + + // keyword descriptions + for (int i = 0; i < kw.size(); i++) { + final String keyword = kw.get(i); + if ((keyword.startsWith("Ripple") && !sb.toString().contains("Ripple")) + || (keyword.startsWith("Dredge") && !sb.toString().contains("Dredge")) + || (keyword.startsWith("CARDNAME is ") && !sb.toString().contains("CARDNAME is "))) { + sb.append(keyword.replace(":", " ")).append("\r\n"); + } + else if ((keyword.startsWith("Madness") && !sb.toString().contains("Madness")) + || (keyword.startsWith("Recover") && !sb.toString().contains("Recover")) + || (keyword.startsWith("Miracle") && !sb.toString().contains("Miracle"))) { + String[] parts = keyword.split(":"); + sb.append(parts[0] + " " + ManaCostParser.parse(parts[1])).append("\r\n"); + } + else if (keyword.equals("CARDNAME can't be countered.") + || keyword.startsWith("May be played") || keyword.startsWith("Conspire") + || keyword.startsWith("Cascade") || keyword.startsWith("Wither") + || (keyword.startsWith("Epic") && !sb.toString().contains("Epic")) + || (keyword.startsWith("Split second") && !sb.toString().contains("Split second")) + || (keyword.startsWith("Multikicker") && !sb.toString().contains("Multikicker"))) { + sb.append(keyword).append("\r\n"); + } + else if (keyword.startsWith("Flashback")) { + sb.append("Flashback"); + if (keyword.contains(" ")) { + final Cost fbCost = new Cost(keyword.substring(10), true); + if (!fbCost.isOnlyManaCost()) { + sb.append(" -"); + } + sb.append(" " + fbCost.toString()).delete(sb.length() - 2, sb.length()); + if (!fbCost.isOnlyManaCost()) { + sb.append("."); + } + } + sb.append("\r\n"); + } else if (keyword.startsWith("Splice")) { + final Cost cost = new Cost(keyword.substring(19), false); + sb.append("Splice onto Arcane " + cost.toSimpleString() + "\r\n"); + } else if (keyword.startsWith("Buyback")) { + final Cost cost = new Cost(keyword.substring(8), false); + sb.append("Buyback " + cost.toSimpleString() + "\r\n"); + } else if (keyword.startsWith("Entwine")) { + final Cost cost = new Cost(keyword.substring(8), false); + sb.append("Entwine " + cost.toSimpleString() + "\r\n"); + } else if (keyword.startsWith("Kicker")) { + final Cost cost = new Cost(keyword.substring(7), false); + sb.append("Kicker " + cost.toSimpleString() + "\r\n"); + } else if (keyword.startsWith("AlternateAdditionalCost")) { + final String costString1 = keyword.split(":")[1]; + final String costString2 = keyword.split(":")[2]; + final Cost cost1 = new Cost(costString1, false); + final Cost cost2 = new Cost(costString2, false); + sb.append("As an additional cost to cast " + this.getName() + ", " + cost1.toSimpleString() + + " or pay " + cost2.toSimpleString() + ".\r\n"); + } else if (keyword.startsWith("Storm")) { + if (sb.toString().contains("Target") || sb.toString().contains("target")) { + sb.insert( + sb.indexOf("Storm (When you cast this spell, copy it for each spell cast before it this turn.") + 81, + " You may choose new targets for the copies."); + } + } else if (keyword.contains("Replicate") && !sb.toString().contains("you paid its replicate cost.")) { + if (sb.toString().endsWith("\r\n\r\n")) { + sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); + } + sb.append(keyword); + sb.append(" (When you cast this spell, copy it for each time you paid its replicate cost."); + if (sb.toString().contains("Target") || sb.toString().contains("target")) { + sb.append(" You may choose new targets for the copies."); + } + sb.append(")\r\n"); + } else if (keyword.startsWith("Haunt")) { + if (sb.toString().endsWith("\r\n\r\n")) { + sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); + } + sb.append("Haunt ("); + if (this.isCreature()) { + sb.append("When this creature dies, exile it haunting target creature."); + } else { + sb.append("When this spell card is put into a graveyard after resolving, "); + sb.append("exile it haunting target creature."); + } + sb.append(")\r\n"); + } else if (keyword.equals("Convoke")) { + if (sb.toString().endsWith("\r\n\r\n")) { + sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); + } + sb.append("Convoke (Each creature you tap while casting this spell reduces its cost by 1 or by one mana of that creature's color.)\r\n"); + } else if (keyword.equals("Delve")) { + if (sb.toString().endsWith("\r\n\r\n")) { + sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); + } + sb.append("Delve (You may exile any number of cards from your graveyard as you cast this spell. It costs 1 less to cast for each card exiled this way.)\r\n"); + } else if (keyword.endsWith(" offering")) { + if (sb.toString().endsWith("\r\n\r\n")) { + sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); + } + String offeringType = keyword.split(" ")[0]; + sb.append(keyword); + sb.append(" (You may cast this card any time you could cast an instant by sacrificing a "); + sb.append(offeringType); + sb.append("and paying the difference in mana costs between this and the sacrificed "); + sb.append(offeringType); + sb.append(". Mana cost includes color.)"); + } else if (keyword.equals("Remove CARDNAME from your deck before playing if you're not playing for ante.")) { + if (sb.toString().endsWith("\r\n\r\n")) { + sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); + } + sb.append("Remove CARDNAME from your deck before playing if you're not playing for ante.\r\n"); + } else if (keyword.equals("Rebound")) { + sb.append(keyword) + .append(" (If you cast this spell from your hand, exile it as it resolves. At the beginning of your next upkeep, you may cast this card from exile without paying its mana cost.)\r\n"); + } + } + return sb; + } + + /** + *

+ * Getter for the field manaAbility. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final List getManaAbility() { + return Collections.unmodifiableList(this.getCharacteristics().getManaAbility()); + } + + + /** + *

+ * clearFirstSpellAbility. + *

+ */ + public final void clearFirstSpell() { + for (int i = 0; i < this.getCharacteristics().getSpellAbility().size(); i++) { + if (this.getCharacteristics().getSpellAbility().get(i).isSpell()) { + this.getCharacteristics().getSpellAbility().remove(i); + return; + } + } + } + + /** + *

+ * getFirstSpellAbility. + *

+ * + * @return a SpellAbility object. + */ + public final SpellAbility getFirstSpellAbility() { + final List sas = this.getCharacteristics().getSpellAbility(); + return sas.isEmpty() ? null : sas.get(0); + } + + + /** + *

+ * getSpellPermanent. + *

+ * + * @return a {@link forge.game.spellability.SpellPermanent} object. + */ + public final SpellPermanent getSpellPermanent() { + for (final SpellAbility sa : this.getCharacteristics().getSpellAbility()) { + if (sa instanceof SpellPermanent) { + return (SpellPermanent) sa; + } + } + return null; + } + + /** + *

+ * addSpellAbility. + *

+ * + * @param a + * a {@link forge.game.spellability.SpellAbility} object. + */ + public final void addSpellAbility(final SpellAbility a) { + + a.setSourceCard(this); + if (a.isManaAbility()) { + this.getCharacteristics().getManaAbility().add(a); + } else { + this.getCharacteristics().getSpellAbility().add(a); + } + } + + + /** + *

+ * removeSpellAbility. + *

+ * + * @param a + * a {@link forge.game.spellability.SpellAbility} object. + */ + public final void removeSpellAbility(final SpellAbility a) { + if (a.isManaAbility()) { + // if (a.isExtrinsic()) //never remove intrinsic mana abilities, is + // this the way to go?? + this.getCharacteristics().getManaAbility().remove(a); + } else { + this.getCharacteristics().getSpellAbility().remove(a); + } + } + + /** + *

+ * getSpellAbilities. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getSpellAbilities() { + final ArrayList res = new ArrayList(this.getManaAbility()); + res.addAll(this.getCharacteristics().getSpellAbility()); + return res; + } + + /** + *

+ * getNonManaSpellAbilities. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final List getNonManaSpellAbilities() { + return this.getCharacteristics().getSpellAbility(); + } + + /** + * + * getAllSpellAbilities. + * + * @return ArrayList + */ + public final ArrayList getAllSpellAbilities() { + final ArrayList res = new ArrayList(); + + for (final CardCharacteristicName key : this.characteristicsMap.keySet()) { + res.addAll(this.getState(key).getSpellAbility()); + res.addAll(this.getState(key).getManaAbility()); + } + + return res; + } + + /** + *

+ * getSpells. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final List getSpells() { + final List res = new ArrayList(); + for (final SpellAbility sa : this.getCharacteristics().getSpellAbility()) { + if (!sa.isSpell()) { + continue; + } + res.add(sa); + } + return res; + } + + /** + *

+ * getBasicSpells. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final List getBasicSpells() { + final ArrayList res = new ArrayList(); + + for (final SpellAbility sa : this.getCharacteristics().getSpellAbility()) { + if (sa.isSpell() && sa.isBasicSpell()) { + res.add(sa); + } + } + return res; + } + + // shield = regeneration + /** + *

+ * getShield. + *

+ * + * @return a int. + */ + public final List getShield() { + return this.nShield; + } + + /** + *

+ * addShield. + *

+ */ + public final void addShield(final CardShields shield) { + this.nShield.add(shield); + } + + /** + *

+ * subtractShield. + *

+ */ + public final void subtractShield(CardShields shield) { + if (shield != null && shield.hasTrigger()) { + this.getGame().getStack().addSimultaneousStackEntry(shield.getTriggerSA()); + } + this.nShield.remove(shield); + } + + /** + * Adds the regenerated this turn. + */ + public final void addRegeneratedThisTurn() { + this.regeneratedThisTurn += 1; + } + + /** + * Gets the regenerated this turn. + * + * @return the regenerated this turn + */ + public final int getRegeneratedThisTurn() { + return this.regeneratedThisTurn; + } + + /** + * Sets the regenerated this turn. + * + * @param n + * the new regenerated this turn + */ + public final void setRegeneratedThisTurn(final int n) { + this.regeneratedThisTurn = n; + } + + /** + *

+ * resetShield. + *

+ */ + public final void resetShield() { + this.nShield.clear();; + } + + /** + *

+ * canBeShielded. + *

+ * + * @return a boolean. + */ + public final boolean canBeShielded() { + return !this.hasKeyword("CARDNAME can't be regenerated."); + } + + // is this "Card" supposed to be a token? + /** + *

+ * Setter for the field token. + *

+ * + * @param b + * a boolean. + */ + public final void setToken(final boolean b) { + this.token = b; + } + + /** + *

+ * isToken. + *

+ * + * @return a boolean. + */ + public final boolean isToken() { + return this.token; + } + + /** + *

+ * Setter for the field copiedToken. + *

+ * + * @param b + * a boolean. + */ + public final void setCopiedToken(final boolean b) { + this.copiedToken = b; + } + + /** + *

+ * isCopiedToken. + *

+ * + * @return a boolean. + */ + public final boolean isCopiedToken() { + return this.copiedToken; + } + + /** + *

+ * Setter for the field copiedSpell. + *

+ * + * @param b + * a boolean. + */ + public final void setCopiedSpell(final boolean b) { + this.copiedSpell = b; + } + + /** + *

+ * isCopiedSpell. + *

+ * + * @return a boolean. + */ + public final boolean isCopiedSpell() { + return this.copiedSpell; + } + + /** + *

+ * isFaceDown. + *

+ * + * @return a boolean. + */ + public final boolean isFaceDown() { + return this.curCharacteristics == CardCharacteristicName.FaceDown; + } + + /** + *

+ * setCanCounter. + *

+ * + * @param b + * a boolean. + */ + public final void setCanCounter(final boolean b) { + this.canCounter = b; + } + + /** + *

+ * getCanCounter. + *

+ * + * @return a boolean. + */ + public final boolean getCanCounter() { + return this.canCounter; + } + + + /** + *

+ * addTrigger. + *

+ * + * @param c + * a {@link forge.Command} object. + * @param typeIn + * a {@link forge.game.trigger.ZCTrigger} object. + */ + public final void addTrigger(final Command c, final ZCTrigger typeIn) { + this.zcTriggers.add(new AbilityTriggered(this, c, typeIn)); + } + + /** + *

+ * removeTrigger. + *

+ * + * @param c + * a {@link forge.Command} object. + * @param typeIn + * a {@link forge.game.trigger.ZCTrigger} object. + */ + public final void removeTrigger(final Command c, final ZCTrigger typeIn) { + this.zcTriggers.remove(new AbilityTriggered(this, c, typeIn)); + } + + /** + *

+ * executeTrigger. + *

+ * + * @param type + * a {@link forge.game.trigger.ZCTrigger} object. + */ + public final void executeTrigger(final ZCTrigger type) { + for (final AbilityTriggered t : this.zcTriggers) { + if (t.getTrigger().equals(type) && t.isBasic()) { + t.run(); + } + } + } + + /** + *

+ * clearTriggers. + *

+ */ + public final void clearTriggers() { + this.zcTriggers.clear(); + } + + /** + *

+ * addComesIntoPlayCommand. + *

+ * + * @param c + * a {@link forge.Command} object. + */ + public final void addComesIntoPlayCommand(final Command c) { + this.addTrigger(c, ZCTrigger.ENTERFIELD); + } + + + + /** + *

+ * addDestroyCommand. + *

+ * + * @param c + * a {@link forge.Command} object. + */ + public final void addDestroyCommand(final Command c) { + this.addTrigger(c, ZCTrigger.DESTROY); + } + + + /** + *

+ * addLeavesPlayCommand. + *

+ * + * @param c + * a {@link forge.Command} object. + */ + public final void addLeavesPlayCommand(final Command c) { + this.addTrigger(c, ZCTrigger.LEAVEFIELD); + } + + /** + *

+ * addUntapCommand. + *

+ * + * @param c + * a {@link forge.Command} object. + */ + public final void addUntapCommand(final Command c) { + this.untapCommandList.add(c); + } + + /** + *

+ * addChangeControllerCommand. + *

+ * + * @param c + * a {@link forge.Command} object. + */ + public final void addChangeControllerCommand(final Command c) { + this.changeControllerCommandList.add(c); + } + + public final void runChangeControllerCommands() { + for (final Command c : this.changeControllerCommandList) { + c.run(); + } + } + + /** + *

+ * Setter for the field sickness. + *

+ * + * @param b + * a boolean. + */ + public final void setSickness(final boolean b) { + this.sickness = b; + } + + /** @return boolean */ + public final boolean isFirstTurnControlled() { + return this.sickness; + } + + /** + *

+ * hasSickness. + *

+ * + * @return a boolean. + */ + public final boolean hasSickness() { + return this.sickness && !this.hasKeyword("Haste"); + } + + /** + * + * isSick. + * + * @return boolean + */ + public final boolean isSick() { + return this.sickness && this.isCreature() && !this.hasKeyword("Haste"); + } + + /** + * @return the becameTargetThisTurn + */ + public boolean hasBecomeTargetThisTurn() { + return becameTargetThisTurn; + } + + /** + * @param becameTargetThisTurn0 the becameTargetThisTurn to set + */ + public void setBecameTargetThisTurn(boolean becameTargetThisTurn) { + this.becameTargetThisTurn = becameTargetThisTurn; + } + + /** + * @return the startedTheTurnUntapped + */ + public boolean hasStartedTheTurnUntapped() { + return startedTheTurnUntapped; + } + + /** + * @param startedTheTurnUntapped0 the startedTheTurnUntapped to set + */ + public void setStartedTheTurnUntapped(boolean untapped) { + this.startedTheTurnUntapped = untapped; + } + + /** + *

+ * Getter for the field owner. + *

+ * + * @return a {@link forge.game.player.Player} object. + */ + public final Player getOwner() { + return this.owner; + } + + /** + * Get the controller for this card. + * + * @return a {@link forge.game.player.Player} object. + */ + public final Player getController() { + Entry lastEntry = this.tempControllers.lastEntry(); + if (lastEntry != null) { + final long lastTimestamp = lastEntry.getKey(); + if (lastTimestamp > this.controllerTimestamp) { + return lastEntry.getValue(); + } + } + if (this.controller != null) { + return this.controller; + } + return this.owner; + } + + public final void addTempController(final Player player, final long tstamp) { + this.tempControllers.put(tstamp, player); + } + + public final void removeTempController(final long tstamp) { + this.tempControllers.remove(tstamp); + } + + public final void clearTempControllers() { + this.tempControllers.clear(); + } + + public final void clearControllers() { + clearTempControllers(); + this.controller = null; + } + + public final void setController(final Player player, final long tstamp) { + clearTempControllers(); + this.controller = player; + this.controllerTimestamp = tstamp; + } + + /** + *

+ * Setter for the field owner. + *

+ * + * @param player + * a {@link forge.game.player.Player} object. + */ + public final void setOwner(final Player player) { + this.owner = player; + } + + /** + *

+ * Getter for the field equippedBy. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getEquippedBy() { + return this.equippedBy; + } + + /** + *

+ * Getter for the field fortifiedBy. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getFortifiedBy() { + return this.fortifiedBy; + } + + /** + *

+ * Setter for the field equippedBy. + *

+ * + * @param list + * a {@link java.util.ArrayList} object. + */ + public final void setEquippedBy(final ArrayList list) { + this.equippedBy = list; + } + + /** + *

+ * Setter for the field fortifiedBy. + *

+ * + * @param list + * a {@link java.util.ArrayList} object. + */ + public final void setFortifiedBy(final ArrayList list) { + this.fortifiedBy = list; + } + + /** + *

+ * Getter for the field equipping. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getEquipping() { + return this.equipping; + } + + /** + *

+ * Getter for the field fortifying. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getFortifying() { + return this.fortifying; + } + + /** + *

+ * getEquippingCard. + *

+ * + * @return a {@link forge.game.card.Card} object. + */ + public final Card getEquippingCard() { + if (this.equipping.isEmpty()) { + return null; + } + return this.equipping.get(0); + } + + /** + *

+ * getFortifyingCard. + *

+ * + * @return a {@link forge.game.card.Card} object. + */ + public final Card getFortifyingCard() { + if (this.fortifying.isEmpty()) { + return null; + } + return this.fortifying.get(0); + } + + /** + *

+ * Setter for the field equipping. + *

+ * + * @param list + * a {@link java.util.ArrayList} object. + */ + public final void setEquipping(final ArrayList list) { + this.equipping = list; + } + + /** + *

+ * Setter for the field fortifying. + *

+ * + * @param list + * a {@link java.util.ArrayList} object. + */ + public final void setFortifying(final ArrayList list) { + this.fortifying = list; + } + + /** + *

+ * isEquipped. + *

+ * + * @return a boolean. + */ + public final boolean isEquipped() { + return !this.equippedBy.isEmpty(); + } + + /** + *

+ * isFortified. + *

+ * + * @return a boolean. + */ + public final boolean isFortified() { + return !this.fortifiedBy.isEmpty(); + } + + /** + *

+ * isEquipping. + *

+ * + * @return a boolean. + */ + public final boolean isEquipping() { + return this.equipping.size() != 0; + } + + /** + *

+ * isFortifying. + *

+ * + * @return a boolean. + */ + public final boolean isFortifying() { + return !this.fortifying.isEmpty(); + } + + /** + *

+ * equipCard. + *

+ * equipment.equipCard(cardToBeEquipped) + * + * @param c + * a {@link forge.game.card.Card} object. + */ + public final void equipCard(final Card c) { + if (c.hasKeyword("CARDNAME can't be equipped.")) { + getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, "Trying to equip " + c.getName() + " but it can't be equipped."); + return; + } + if (this.hasStartOfKeyword("CantEquip")) { + final int keywordPosition = this.getKeywordPosition("CantEquip"); + final String parse = this.getKeyword().get(keywordPosition).toString(); + final String[] k = parse.split(" ", 2); + final String[] restrictions = k[1].split(","); + if (c.isValid(restrictions, this.getController(), this)) { + getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, "Trying to equip " + c.getName() + " but it can't be equipped."); + return; + } + } + + Card oldTarget = null; + if (this.isEquipping()) { + oldTarget = this.getEquipping().get(0); + this.unEquipCard(oldTarget); + } + + // They use double links... it's doubtful + this.equipping.add(c); + this.setTimestamp(this.getGame().getNextTimestamp()); + c.equippedBy.add(this); + + // Play the Equip sound + getGame().fireEvent(new GameEventCardAttachment(this, oldTarget, c, AttachMethod.Equip)); + + // run trigger + final HashMap runParams = new HashMap(); + runParams.put("AttachSource", this); + runParams.put("AttachTarget", c); + this.getController().getGame().getTriggerHandler().runTrigger(TriggerType.Attached, runParams, false); + } + + /** + *

+ * fortifyCard. + *

+ * fortification.fortifyCard(cardToBeFortified) + * + * @param c + * a {@link forge.game.card.Card} object. + */ + public final void fortifyCard(final Card c) { + Card oldTarget = null; + if (this.isFortifying()) { + oldTarget = this.getFortifying().get(0); + this.unFortifyCard(oldTarget); + } + + this.fortifying.add(c); + this.setTimestamp(this.getGame().getNextTimestamp()); + c.fortifiedBy.add(this); + + // Play the Equip sound + getGame().fireEvent(new GameEventCardAttachment(this, oldTarget, c, AttachMethod.Fortify)); + // run trigger + final HashMap runParams = new HashMap(); + runParams.put("AttachSource", this); + runParams.put("AttachTarget", c); + this.getController().getGame().getTriggerHandler().runTrigger(TriggerType.Attached, runParams, false); + } + + /** + *

+ * unEquipCard. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + */ + public final void unEquipCard(final Card c) { // equipment.unEquipCard(equippedCard); + this.equipping.remove(c); + c.equippedBy.remove(this); + + getGame().fireEvent(new GameEventCardAttachment(this, c, null, AttachMethod.Equip)); + + // Run triggers + final Map runParams = new TreeMap(); + runParams.put("Equipment", this); + runParams.put("Card", c); + getGame().getTriggerHandler().runTrigger(TriggerType.Unequip, runParams, false); + } + + /** + *

+ * unFortifyCard. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + */ + public final void unFortifyCard(final Card c) { // fortification.unEquipCard(fortifiedCard); + this.fortifying.remove(c); + c.fortifiedBy.remove(this); + + getGame().fireEvent(new GameEventCardAttachment(this, c, null, AttachMethod.Fortify)); + } + + /** + *

+ * unEquipAllCards. + *

+ */ + public final void unEquipAllCards() { + // while there exists equipment, unequip the first one + while (this.equippedBy.size() > 0) { + this.equippedBy.get(0).unEquipCard(this); + } + } + + /** + *

+ * Getter for the field enchanting. + *

+ * + * @return a {@link forge.game.GameEntity} object. + */ + public final GameEntity getEnchanting() { + return this.enchanting; + } + + /** + *

+ * getEnchantingCard. + *

+ * + * @return a {@link forge.game.card.Card} object. + */ + public final Card getEnchantingCard() { + if (this.enchanting instanceof Card) { + return (Card) this.enchanting; + } + return null; + } + + /** + *

+ * getEnchantingPlayer. + *

+ * + * @return a {@link forge.game.player.Player} object. + */ + public final Player getEnchantingPlayer() { + if (this.enchanting instanceof Player) { + return (Player) this.enchanting; + } + return null; + } + + /** + *

+ * Setter for the field enchanting. + *

+ * + * @param e + * a GameEntity object. + */ + public final void setEnchanting(final GameEntity e) { + this.enchanting = e; + } + + /** + *

+ * isEnchanting. + *

+ * + * @return a boolean. + */ + public final boolean isEnchanting() { + return this.enchanting != null; + } + + /** + *

+ * isEnchanting. + *

+ * + * @return a boolean. + */ + public final boolean isEnchantingCard() { + return this.getEnchantingCard() != null; + } + + /** + *

+ * isEnchanting. + *

+ * + * @return a boolean. + */ + public final boolean isEnchantingPlayer() { + return this.getEnchantingPlayer() != null; + } + + /** + * checks to see if this card is enchanted by an aura with a given name. + * + * @param cardName + * the name of the aura + * @return true if this card is enchanted by an aura with the given name, + * false otherwise + */ + public final boolean isEnchantedBy(final String cardName) { + final ArrayList allAuras = this.getEnchantedBy(); + for (final Card aura : allAuras) { + if (aura.getName().equals(cardName)) { + return true; + } + } + return false; + } + + /** + *

+ * removeEnchanting. + *

+ * + * @param e + * a {@link forge.game.GameEntity} object. + */ + public final void removeEnchanting(final GameEntity e) { + if (this.enchanting.equals(e)) { + this.enchanting = null; + } + } + + /** + *

+ * enchant. + *

+ * + * @param entity + * a {@link forge.game.GameEntity} object. + */ + public final void enchantEntity(final GameEntity entity) { + if (entity.hasKeyword("CARDNAME can't be enchanted.")) { + getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, "Trying to enchant " + entity.getName() + + " but it can't be enchanted."); + return; + } + this.enchanting = entity; + this.setTimestamp(this.getGame().getNextTimestamp()); + entity.addEnchantedBy(this); + + getGame().fireEvent(new GameEventCardAttachment(this, null, entity, AttachMethod.Enchant)); + + // run trigger + final HashMap runParams = new HashMap(); + runParams.put("AttachSource", this); + runParams.put("AttachTarget", entity); + this.getController().getGame().getTriggerHandler().runTrigger(TriggerType.Attached, runParams, false); + } + + /** + *

+ * unEnchant. + *

+ * + * @param gameEntity + * a {@link forge.game.GameEntity} object. + */ + public final void unEnchantEntity(final GameEntity entity) { + if (this.enchanting == null || !this.enchanting.equals(entity)) + return; + + this.enchanting = null; + entity.removeEnchantedBy(this); + getGame().fireEvent(new GameEventCardAttachment(this, entity, null, AttachMethod.Enchant)); + } + + /** + *

+ * Setter for the field type. + *

+ * + * @param a + * a {@link java.util.ArrayList} object. + */ + public final void setType(final List a) { + this.getCharacteristics().setType(new ArrayList(a)); + } + + /** + *

+ * addType. + *

+ * + * @param a + * a {@link java.lang.String} object. + */ + public final void addType(final String a) { + this.getCharacteristics().getType().add(a); + } + + + /** + *

+ * Getter for the field type. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getType() { + + // see if type changes are in effect + final ArrayList newType = new ArrayList(this.getCharacteristics().getType()); + for (final CardType ct : this.changedCardTypes.values()) { + final ArrayList removeTypes = new ArrayList(); + if (ct.getRemoveType() != null) { + removeTypes.addAll(ct.getRemoveType()); + } + // remove old types + for (int i = 0; i < newType.size(); i++) { + final String t = newType.get(i); + if (ct.isRemoveSuperTypes() && forge.card.CardType.isASuperType(t)) { + removeTypes.add(t); + } + if (ct.isRemoveCardTypes() && forge.card.CardType.isACardType(t)) { + removeTypes.add(t); + } + if (ct.isRemoveSubTypes() && forge.card.CardType.isASubType(t)) { + removeTypes.add(t); + } + if (ct.isRemoveCreatureTypes() && (forge.card.CardType.isACreatureType(t) || t.equals("AllCreatureTypes"))) { + removeTypes.add(t); + } + } + newType.removeAll(removeTypes); + // add new types + if (ct.getType() != null) { + newType.addAll(ct.getType()); + } + + } + + return newType; + } + + /** + * + * TODO Write javadoc for this method. + * + * @param types + * ArrayList + * @param removeTypes + * ArrayList + * @param removeSuperTypes + * boolean + * @param removeCardTypes + * boolean + * @param removeSubTypes + * boolean + * @param removeCreatureTypes + * boolean + * @param timestamp + * long + */ + public final void addChangedCardTypes(final ArrayList types, final ArrayList removeTypes, + final boolean removeSuperTypes, final boolean removeCardTypes, final boolean removeSubTypes, + final boolean removeCreatureTypes, final long timestamp) { + + this.changedCardTypes.put(timestamp, new CardType(types, removeTypes, removeSuperTypes, removeCardTypes, removeSubTypes, removeCreatureTypes)); + } + + /** + * + * TODO Write javadoc for this method. + * + * @param types + * String[] + * @param removeTypes + * String[] + * @param removeSuperTypes + * boolean + * @param removeCardTypes + * boolean + * @param removeSubTypes + * boolean + * @param removeCreatureTypes + * boolean + * @param timestamp + * long + */ + public final void addChangedCardTypes(final String[] types, final String[] removeTypes, + final boolean removeSuperTypes, final boolean removeCardTypes, final boolean removeSubTypes, + final boolean removeCreatureTypes, final long timestamp) { + ArrayList typeList = null; + ArrayList removeTypeList = null; + if (types != null) { + typeList = new ArrayList(Arrays.asList(types)); + } + + if (removeTypes != null) { + removeTypeList = new ArrayList(Arrays.asList(removeTypes)); + } + + this.addChangedCardTypes(typeList, removeTypeList, removeSuperTypes, removeCardTypes, removeSubTypes, + removeCreatureTypes, timestamp); + } + + /** + * + * TODO Write javadoc for this method. + * + * @param timestamp + * long + */ + public final void removeChangedCardTypes(final long timestamp) { + this.changedCardTypes.remove(Long.valueOf(timestamp)); + } + + // values that are printed on card + /** + *

+ * Getter for the field baseLoyalty. + *

+ * + * @return a int. + */ + public final int getBaseLoyalty() { + return this.baseLoyalty; + } + + // values that are printed on card + /** + *

+ * Setter for the field baseLoyalty. + *

+ * + * @param n + * a int. + */ + public final void setBaseLoyalty(final int n) { + this.baseLoyalty = n; + } + + // values that are printed on card + /** + *

+ * Getter for the field baseAttack. + *

+ * + * @return a int. + */ + public final int getBaseAttack() { + return this.getCharacteristics().getBaseAttack(); + } + + /** + *

+ * Getter for the field baseDefense. + *

+ * + * @return a int. + */ + public final int getBaseDefense() { + return this.getCharacteristics().getBaseDefense(); + } + + // values that are printed on card + /** + *

+ * Setter for the field baseAttack. + *

+ * + * @param n + * a int. + */ + public final void setBaseAttack(final int n) { + this.getCharacteristics().setBaseAttack(n); + } + + /** + *

+ * Setter for the field baseDefense. + *

+ * + * @param n + * a int. + */ + public final void setBaseDefense(final int n) { + this.getCharacteristics().setBaseDefense(n); + } + + // values that are printed on card + /** + *

+ * Getter for the field baseAttackString. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getBaseAttackString() { + return (null == this.baseAttackString) ? "" + this.getBaseAttack() : this.baseAttackString; + } + + /** + *

+ * Getter for the field baseDefenseString. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getBaseDefenseString() { + return (null == this.baseDefenseString) ? "" + this.getBaseDefense() : this.baseDefenseString; + } + + // values that are printed on card + /** + *

+ * Setter for the field baseAttackString. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void setBaseAttackString(final String s) { + this.baseAttackString = s; + } + + /** + *

+ * Setter for the field baseDefenseString. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void setBaseDefenseString(final String s) { + this.baseDefenseString = s; + } + + /** + * + * TODO Write javadoc for this method. + * + * @return int + */ + public final int getSetPower() { + if (this.newPT.isEmpty()) { + return -1; + } + + final CardPowerToughness latestPT = this.getLatestPT(); + + return latestPT.getPower(); + } + + /** + * + * TODO Write javadoc for this method. + * + * @return int + */ + public final int getSetToughness() { + if (this.newPT.isEmpty()) { + return -1; + } + + final CardPowerToughness latestPT = this.getLatestPT(); + + return latestPT.getToughness(); + } + + /** + * + * TODO Write javadoc for this method. + * + * @return CardPowerToughness + */ + public final CardPowerToughness getLatestPT() { + CardPowerToughness latestPT = new CardPowerToughness(-1, -1, -2); + long max = -2; + + for (final CardPowerToughness pt : this.newPT) { + if (pt.getTimestamp() >= max) { + max = pt.getTimestamp(); + latestPT = pt; + } + } + + return latestPT; + } + + /** + * + * TODO Write javadoc for this method. + * + * @param power + * int + * @param toughness + * int + * @param timestamp + * int + */ + public final void addNewPT(final int power, final int toughness, final long timestamp) { + this.newPT.add(new CardPowerToughness(power, toughness, timestamp)); + } + + /** + * + * TODO Write javadoc for this method. + * + * @param timestamp + * long + */ + public final void removeNewPT(final long timestamp) { + for (int i = 0; i < this.newPT.size(); i++) { + final CardPowerToughness cardPT = this.newPT.get(i); + if (cardPT.getTimestamp() == timestamp) { + this.newPT.remove(cardPT); + } + } + } + + /** + * + * TODO Write javadoc for this method. + * + * @return int + */ + public final int getCurrentPower() { + int total = this.getBaseAttack(); + final int setPower = this.getSetPower(); + if (setPower != -1) { + total = setPower; + } + + return total; + } + + /** + *

+ * getUnswitchedAttack. + *

+ * + * @return a int. + */ + public final int getUnswitchedPower() { + int total = this.getCurrentPower(); + + total += this.getTempAttackBoost() + this.getSemiPermanentAttackBoost() + getPowerBonusFromCounters(); + return total; + } + + public final int getPowerBonusFromCounters() { + int total = 0; + + total += this.getCounters(CounterType.P1P1) + this.getCounters(CounterType.P1P2) + this.getCounters(CounterType.P1P0) + - this.getCounters(CounterType.M1M1) + 2 * this.getCounters(CounterType.P2P2) - 2 * this.getCounters(CounterType.M2M1) + - 2 * this.getCounters(CounterType.M2M2) - this.getCounters(CounterType.M1M0) + 2 * this.getCounters(CounterType.P2P0); + return total; + } + + /** + *

+ * getNetAttack. + *

+ * + * @return a int. + */ + public final int getNetAttack() { + if (this.getAmountOfKeyword("CARDNAME's power and toughness are switched") % 2 != 0) { + return this.getUnswitchedToughness(); + } + return this.getUnswitchedPower(); + } + + /** + * + * TODO Write javadoc for this method. + * + * @return an int + */ + public final int getCurrentToughness() { + int total = this.getBaseDefense(); + + final int setToughness = this.getSetToughness(); + if (setToughness != -1) { + total = setToughness; + } + + return total; + } + + /** + *

+ * getUnswitchedDefense. + *

+ * + * @return a int. + */ + public final int getUnswitchedToughness() { + int total = this.getCurrentToughness(); + + total += this.getTempDefenseBoost() + this.getSemiPermanentDefenseBoost() + getToughnessBonusFromCounters(); + return total; + } + + public final int getToughnessBonusFromCounters() { + int total = 0; + + total += this.getCounters(CounterType.P1P1) + 2 * this.getCounters(CounterType.P1P2) - this.getCounters(CounterType.M1M1) + + this.getCounters(CounterType.P0P1) - 2 * this.getCounters(CounterType.M0M2) + 2 * this.getCounters(CounterType.P2P2) + - this.getCounters(CounterType.M0M1) - this.getCounters(CounterType.M2M1) - 2 * this.getCounters(CounterType.M2M2) + + 2 * this.getCounters(CounterType.P0P2); + return total; + } + + /** + *

+ * getNetDefense. + *

+ * + * @return a int. + */ + public final int getNetDefense() { + if (this.getAmountOfKeyword("CARDNAME's power and toughness are switched") % 2 != 0) { + return this.getUnswitchedPower(); + } + return this.getUnswitchedToughness(); + } + + // How much combat damage does the card deal + /** + *

+ * getNetCombatDamage. + *

+ * + * @return a int. + */ + public final int getNetCombatDamage() { + if (this.hasKeyword("CARDNAME assigns no combat damage")) { + return 0; + } + + if (getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.toughnessAssignsDamage)) { + return this.getNetDefense(); + } + return this.getNetAttack(); + } + + private int multiKickerMagnitude = 0; + public final void addMultiKickerMagnitude(final int n) { this.multiKickerMagnitude += n; } + public final void setKickerMagnitude(final int n) { this.multiKickerMagnitude = n; } + public final int getKickerMagnitude() { + if (this.multiKickerMagnitude > 0) + return multiKickerMagnitude; + boolean hasK1 = costsPaid.contains(OptionalCost.Kicker1); + return hasK1 == costsPaid.contains(OptionalCost.Kicker2) ? (hasK1 ? 2 : 0) : 1; + } + + private int pseudoKickerMagnitude = 0; + public final void addPseudoMultiKickerMagnitude(final int n) { this.pseudoKickerMagnitude += n; } + public final void setPseudoMultiKickerMagnitude(final int n) { this.pseudoKickerMagnitude = n; } + public final int getPseudoKickerMagnitude() { return pseudoKickerMagnitude; } + + // for cards like Giant Growth, etc. + /** + *

+ * Getter for the field tempAttackBoost. + *

+ * + * @return a int. + */ + public final int getTempAttackBoost() { + return this.tempAttackBoost; + } + + /** + *

+ * Getter for the field tempDefenseBoost. + *

+ * + * @return a int. + */ + public final int getTempDefenseBoost() { + return this.tempDefenseBoost; + } + + /** + *

+ * addTempAttackBoost. + *

+ * + * @param n + * a int. + */ + public final void addTempAttackBoost(final int n) { + this.tempAttackBoost += n; + } + + /** + *

+ * addTempDefenseBoost. + *

+ * + * @param n + * a int. + */ + public final void addTempDefenseBoost(final int n) { + this.tempDefenseBoost += n; + } + + // for cards like Glorious Anthem, etc. + /** + *

+ * Getter for the field semiPermanentAttackBoost. + *

+ * + * @return a int. + */ + public final int getSemiPermanentAttackBoost() { + return this.semiPermanentAttackBoost; + } + + /** + *

+ * Getter for the field semiPermanentDefenseBoost. + *

+ * + * @return a int. + */ + public final int getSemiPermanentDefenseBoost() { + return this.semiPermanentDefenseBoost; + } + + /** + *

+ * addSemiPermanentAttackBoost. + *

+ * + * @param n + * a int. + */ + public final void addSemiPermanentAttackBoost(final int n) { + this.semiPermanentAttackBoost += n; + } + + /** + *

+ * addSemiPermanentDefenseBoost. + *

+ * + * @param n + * a int. + */ + public final void addSemiPermanentDefenseBoost(final int n) { + this.semiPermanentDefenseBoost += n; + } + + /** + *

+ * Setter for the field semiPermanentAttackBoost. + *

+ * + * @param n + * a int. + */ + public final void setSemiPermanentAttackBoost(final int n) { + this.semiPermanentAttackBoost = n; + } + + /** + *

+ * Setter for the field semiPermanentDefenseBoost. + *

+ * + * @param n + * a int. + */ + public final void setSemiPermanentDefenseBoost(final int n) { + this.semiPermanentDefenseBoost = n; + } + + /** + *

+ * isUntapped. + *

+ * + * @return a boolean. + */ + public final boolean isUntapped() { + return !this.tapped; + } + + /** + *

+ * isTapped. + *

+ * + * @return a boolean. + */ + public final boolean isTapped() { + return this.tapped; + } + + /** + *

+ * Setter for the field tapped. + *

+ * + * @param b + * a boolean. + */ + public final void setTapped(final boolean b) { + this.tapped = b; + } + + /** + *

+ * tap. + *

+ */ + public final void tap() { + if (this.isTapped()) + return; + + // Run triggers + final Map runParams = new TreeMap(); + runParams.put("Card", this); + getGame().getTriggerHandler().runTrigger(TriggerType.Taps, runParams, false); + + this.setTapped(true); + getGame().fireEvent(new GameEventCardTapped(this, true)); + } + + /** + *

+ * untap. + *

+ */ + public final void untap() { + if (this.isUntapped()) + return; + // Run Replacement effects + final HashMap repRunParams = new HashMap(); + repRunParams.put("Event", "Untap"); + repRunParams.put("Affected", this); + + if (getGame().getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) { + return; + } + + // Run triggers + final Map runParams = new TreeMap(); + runParams.put("Card", this); + getGame().getTriggerHandler().runTrigger(TriggerType.Untaps, runParams, false); + + for (final Command var : this.untapCommandList) { + var.run(); + } + this.setTapped(false); + getGame().fireEvent(new GameEventCardTapped(this, false)); + } + + // keywords are like flying, fear, first strike, etc... + /** + *

+ * getKeyword. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final List getKeyword() { + final ArrayList keywords = this.getUnhiddenKeyword(); + keywords.addAll(this.getHiddenExtrinsicKeyword()); + + return keywords; + } + + /** + * Gets the keyword amount. + * + * @param keyword + * the keyword + * @return the keyword amount + */ + public final int getKeywordAmount(final String keyword) { + int res = 0; + for (final String k : this.getKeyword()) { + if (k.equals(keyword)) { + res++; + } + } + + return res; + } + + + /** + * Adds the changed card keywords. + * + * @param keywords + * the keywords + * @param removeKeywords + * the remove keywords + * @param removeAllKeywords + * the remove all keywords + * @param timestamp + * the timestamp + */ + public final void addChangedCardKeywords(final List keywords, final List removeKeywords, + final boolean removeAllKeywords, final long timestamp) { + keywords.removeAll(this.getCantHaveOrGainKeyword()); + // if the key already exists - merge entries + if (changedCardKeywords.containsKey(timestamp)) { + List kws = keywords; + List rkws = removeKeywords; + boolean remAll = removeAllKeywords; + CardKeywords cks = changedCardKeywords.get(timestamp); + kws.addAll(cks.getKeywords()); + rkws.addAll(cks.getRemoveKeywords()); + remAll |= cks.isRemoveAllKeywords(); + this.changedCardKeywords.put(timestamp, new CardKeywords(kws, rkws, remAll)); + return; + } + + this.changedCardKeywords.put(timestamp, new CardKeywords(keywords, removeKeywords, removeAllKeywords)); + } + + /** + * Adds the changed card keywords. + * + * @param keywords + * the keywords + * @param removeKeywords + * the remove keywords + * @param removeAllKeywords + * the remove all keywords + * @param timestamp + * the timestamp + */ + public final void addChangedCardKeywords(final String[] keywords, final String[] removeKeywords, + final boolean removeAllKeywords, final long timestamp) { + ArrayList keywordsList = new ArrayList(); + ArrayList removeKeywordsList = new ArrayList(); + if (keywords != null) { + keywordsList = new ArrayList(Arrays.asList(keywords)); + } + + if (removeKeywords != null) { + removeKeywordsList = new ArrayList(Arrays.asList(removeKeywords)); + } + + this.addChangedCardKeywords(keywordsList, removeKeywordsList, removeAllKeywords, timestamp); + } + + /** + * Removes the changed card keywords. + * + * @param timestamp + * the timestamp + */ + public final void removeChangedCardKeywords(final long timestamp) { + changedCardKeywords.remove(Long.valueOf(timestamp)); + } + + // Hidden keywords will be left out + /** + *

+ * getUnhiddenKeyword. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getUnhiddenKeyword() { + final ArrayList keywords = new ArrayList(); + keywords.addAll(this.getIntrinsicKeyword()); + keywords.addAll(this.getExtrinsicKeyword()); + + // see if keyword changes are in effect + for (final CardKeywords ck : this.changedCardKeywords.values()) { + + if (ck.isRemoveAllKeywords()) { + keywords.clear(); + } else if (ck.getRemoveKeywords() != null) { + keywords.removeAll(ck.getRemoveKeywords()); + } + + if (ck.getKeywords() != null) { + keywords.addAll(ck.getKeywords()); + } + } + + return keywords; + } + + /** + *

+ * getIntrinsicAbilities. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final List getUnparsedAbilities() { + return this.getCharacteristics().getUnparsedAbilities(); + } + + /** + *

+ * Getter for the field intrinsicKeyword. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final List getIntrinsicKeyword() { + // will not create a copy here - due to performance reasons. + // Most of other code checks for contains, or creates copy by itself + return this.getCharacteristics().getIntrinsicKeyword(); + } + + /** + *

+ * Setter for the field intrinsicKeyword. + *

+ * + * @param a + * a {@link java.util.ArrayList} object. + */ + public final void setIntrinsicKeyword(final List a) { + this.getCharacteristics().setIntrinsicKeyword(new ArrayList(a)); + } + + + /** + *

+ * setIntrinsicAbilities. + *

+ * + * @param a + * a {@link java.util.ArrayList} object. + */ + public final void setIntrinsicAbilities(final List a) { + this.getCharacteristics().setUnparsedAbilities(new ArrayList(a)); + } + + /** + *

+ * addIntrinsicKeyword. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void addIntrinsicKeyword(final String s) { + if (s.trim().length() != 0) { + this.getCharacteristics().getIntrinsicKeyword().add(s); + } + } + + /** + *

+ * addIntrinsicAbility. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void addIntrinsicAbility(final String s) { + if (s.trim().length() != 0) { + this.getCharacteristics().getUnparsedAbilities().add(s); + } + } + + /** + *

+ * removeIntrinsicKeyword. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void removeIntrinsicKeyword(final String s) { + this.getCharacteristics().getIntrinsicKeyword().remove(s); + } + + + /** + *

+ * Getter for the field extrinsicKeyword. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public ArrayList getExtrinsicKeyword() { + return this.extrinsicKeyword; + } + + /** + *

+ * Setter for the field extrinsicKeyword. + *

+ * + * @param a + * a {@link java.util.ArrayList} object. + */ + public final void setExtrinsicKeyword(final ArrayList a) { + this.extrinsicKeyword = new ArrayList(a); + } + + /** + *

+ * addExtrinsicKeyword. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public void addExtrinsicKeyword(final String s) { + if (s.startsWith("HIDDEN")) { + this.addHiddenExtrinsicKeyword(s); + } else { + this.extrinsicKeyword.add(s); + } + } + + /** + *

+ * removeExtrinsicKeyword. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public void removeExtrinsicKeyword(final String s) { + if (s.startsWith("HIDDEN")) { + this.removeHiddenExtrinsicKeyword(s); + } else { + this.extrinsicKeyword.remove(s); + } + } + + /** + * Removes the all extrinsic keyword. + * + * @param s + * the s + */ + public void removeAllExtrinsicKeyword(final String s) { + final ArrayList strings = new ArrayList(); + strings.add(s); + this.hiddenExtrinsicKeyword.removeAll(strings); + this.extrinsicKeyword.removeAll(strings); + } + + + // Hidden Keywords will be returned without the indicator HIDDEN + /** + *

+ * getHiddenExtrinsicKeyword. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final ArrayList getHiddenExtrinsicKeyword() { + while (true) { + try { + final ArrayList keywords = new ArrayList(); + for (int i = 0; i < this.hiddenExtrinsicKeyword.size(); i++) { + String keyword = this.hiddenExtrinsicKeyword.get(i); + if (keyword.startsWith("HIDDEN")) { + keyword = keyword.substring(7); + } + keywords.add(keyword); + } + return keywords; + } catch (IndexOutOfBoundsException ex) { + // Do nothing and let the while loop retry + } + } + } + + + /** + *

+ * addHiddenExtrinsicKeyword. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void addHiddenExtrinsicKeyword(final String s) { + this.hiddenExtrinsicKeyword.add(s); + } + + /** + *

+ * removeHiddenExtrinsicKeyword. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void removeHiddenExtrinsicKeyword(final String s) { + this.hiddenExtrinsicKeyword.remove(s); + } + + /** + *

+ * getCantHaveOrGainKeyword. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final List getCantHaveOrGainKeyword() { + final List cantGain = new ArrayList(); + for (String s : this.hiddenExtrinsicKeyword) { + if (s.contains("can't have or gain")) { + cantGain.add(s.split("can't have or gain ")[1]); + } + } + return cantGain; + } + + /** + *

+ * setStaticAbilityStrings. + *

+ * + * @param a + * a {@link java.util.ArrayList} object. + */ + public final void setStaticAbilityStrings(final List a) { + this.getCharacteristics().setStaticAbilityStrings(new ArrayList(a)); + } + + /** + * Gets the static ability strings. + * + * @return the static ability strings + */ + public final List getStaticAbilityStrings() { + return this.getCharacteristics().getStaticAbilityStrings(); + } + + /** + *

+ * addStaticAbilityStrings. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void addStaticAbilityString(final String s) { + if (StringUtils.isNotBlank(s)) { + this.getCharacteristics().getStaticAbilityStrings().add(s); + } + } + + /** + * Sets the static abilities. + * + * @param a + * the new static abilities + */ + public final void setStaticAbilities(final ArrayList a) { + this.getCharacteristics().setStaticAbilities(new ArrayList(a)); + } + + /** + * Gets the static abilities. + * + * @return the static abilities + */ + public final ArrayList getStaticAbilities() { + return new ArrayList(this.getCharacteristics().getStaticAbilities()); + } + + /** + * Adds the static ability. + * + * @param s + * the s + */ + public final StaticAbility addStaticAbility(final String s) { + if (s.trim().length() != 0) { + final StaticAbility stAb = new StaticAbility(s, this); + this.getCharacteristics().getStaticAbilities().add(stAb); + return stAb; + } + return null; + } + + public final boolean isPermanent() { + return !(this.isInstant() || this.isSorcery() || this.isImmutable()); + } + + public final boolean isSpell() { + return (this.isInstant() || this.isSorcery() || (this.isAura() && !this.isInZone((ZoneType.Battlefield)))); + } + + public final boolean isEmblem() { return this.typeContains("Emblem"); } + + public final boolean isLand() { return this.typeContains("Land"); } + public final boolean isBasicLand() { return this.typeContains("Basic"); } + public final boolean isSnow() { return this.typeContains("Snow"); } + + public final boolean isTribal() { return this.typeContains("Tribal"); } + public final boolean isSorcery() { return this.typeContains("Sorcery"); } + public final boolean isInstant() { return this.typeContains("Instant"); } + + public final boolean isCreature() { return this.typeContains("Creature"); } + public final boolean isArtifact() { return this.typeContains("Artifact"); } + public final boolean isEquipment() { return this.typeContains("Equipment"); } + public final boolean isFortification() { return this.typeContains("Fortification"); } + public final boolean isScheme() { return this.typeContains("Scheme"); } + + public final boolean isPlaneswalker() { return this.typeContains("Planeswalker"); } + + public final boolean isEnchantment() { return this.typeContains("Enchantment"); } + public final boolean isAura() { return this.typeContains("Aura"); } + + private boolean typeContains(final String s) { + final Iterator it = this.getType().iterator(); + while (it.hasNext()) { + if (it.next().startsWith(s)) { + return true; + } + } + + return false; + } + + /** + *

+ * Getter for the field uniqueNumber. + *

+ * + * @return a int. + */ + public final int getUniqueNumber() { + return this.uniqueNumber; + } + + /** {@inheritDoc} */ + @Override + public final int compareTo(final Card that) { + /* + * Return a negative integer of this < that, a positive integer if this + * > that, and zero otherwise. + */ + + if (that == null) { + /* + * "Here we can arbitrarily decide that all non-null Cards are + * `greater than' null Cards. It doesn't really matter what we + * return in this case, as long as it is consistent. I rather think + * of null as being lowly." --Braids + */ + return +1; + } else if (this.getUniqueNumber() > that.getUniqueNumber()) { + return +1; + } else if (this.getUniqueNumber() < that.getUniqueNumber()) { + return -1; + } else { + return 0; + } + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(final Object o) { + if (o instanceof Card) { + final Card c = (Card) o; + final int a = this.getUniqueNumber(); + final int b = c.getUniqueNumber(); + return (a == b); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public final int hashCode() { + return this.getUniqueNumber(); + } + + /** {@inheritDoc} */ + @Override + public final String toString() { + return this.getName() + " (" + this.getUniqueNumber() + ")"; + } + + /** + *

+ * isUnearthed. + *

+ * + * @return a boolean. + */ + public final boolean isUnearthed() { + return this.unearthed; + } + + /** + *

+ * Setter for the field unearthed. + *

+ * + * @param b + * a boolean. + */ + public final void setUnearthed(final boolean b) { + this.unearthed = b; + } + + /** + *

+ * Getter for the field miracleCost. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final Cost getMiracleCost() { + return this.miracleCost; + } + + /** + *

+ * Setter for the field miracleCost. + *

+ * + * @param cost + * a {@link java.lang.String} object. + */ + public final void setMiracleCost(final Cost cost) { + this.miracleCost = cost; + } + + /** + *

+ * hasSuspend. + *

+ * + * @return a boolean. + */ + public final boolean hasSuspend() { + return this.suspend; + } + + /** + *

+ * Setter for the field suspend. + *

+ * + * @param b + * a boolean. + */ + public final void setSuspend(final boolean b) { + this.suspend = b; + } + + /** + *

+ * wasSuspendCast. + *

+ * + * @return a boolean. + */ + public final boolean wasSuspendCast() { + return this.suspendCast; + } + + /** + *

+ * Setter for the field suspendCast. + *

+ * + * @param b + * a boolean. + */ + public final void setSuspendCast(final boolean b) { + this.suspendCast = b; + } + + /** + * Checks if is phased out. + * + * @return true, if is phased out + */ + public final boolean isPhasedOut() { + return this.phasedOut; + } + + /** + * Sets the phased out. + * + * @param phasedOut + * the new phased out + */ + public final void setPhasedOut(final boolean phasedOut) { + this.phasedOut = phasedOut; + } + + /** + * Phase. + */ + public final void phase() { + this.phase(true); + } + + /** + * Phase. + * + * @param direct + * the direct + */ + public final void phase(final boolean direct) { + final boolean phasingIn = this.isPhasedOut(); + + if (!this.switchPhaseState()) { + // Switch Phase State returns False if the Permanent can't Phase Out + return; + } + + if (!phasingIn) { + this.setDirectlyPhasedOut(direct); + } + + for (final Card eq : this.getEquippedBy()) { + if (eq.isPhasedOut() == phasingIn) { + eq.phase(false); + } + } + + for (final Card f : this.getFortifiedBy()) { + if (f.isPhasedOut() == phasingIn) { + f.phase(false); + } + } + + for (final Card aura : this.getEnchantedBy()) { + if (aura.isPhasedOut() == phasingIn) { + aura.phase(false); + } + } + + this.getGame().fireEvent(new GameEventCardPhased(this, this.isPhasedOut())); + } + + private boolean switchPhaseState() { + if (!this.phasedOut && this.hasKeyword("CARDNAME can't phase out.")) { + return false; + } + + this.phasedOut = !this.phasedOut; + final Combat combat = this.getGame().getPhaseHandler().getCombat(); + if (combat != null && this.phasedOut) { + combat.removeFromCombat(this); + } + if (this.phasedOut && this.isToken()) { + // 702.23k Phased-out tokens cease to exist as a state-based action. + // See rule 704.5d. + // 702.23d The phasing event doesn't actually cause a permanent to + // change zones or control, + // even though it's treated as though it's not on the battlefield + // and not under its controller's control while it's phased out. + // Zone-change triggers don't trigger when a permanent phases in or + // out. + + // Suppressed Exiling is as close as we can get to + // "ceasing to exist" + getGame().getTriggerHandler().suppressMode(TriggerType.ChangesZone); + getGame().getAction().exile(this); + getGame().getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + } + return true; + } + + /** + * Checks if is directly phased out. + * + * @return true, if is directly phased out + */ + public final boolean isDirectlyPhasedOut() { + return this.directlyPhasedOut; + } + + /** + * Sets the directly phased out. + * + * @param direct + * the new directly phased out + */ + public final void setDirectlyPhasedOut(final boolean direct) { + this.directlyPhasedOut = direct; + } + + /** + *

+ * isReflectedLand. + *

+ * + * @return a boolean. + */ + public final boolean isReflectedLand() { + for (final SpellAbility a : this.getCharacteristics().getManaAbility()) { + if (a.getApi() == ApiType.ManaReflected) { + return true; + } + } + + return false; + } + + /** + *

+ * hasKeyword. + *

+ * + * @param keyword + * a {@link java.lang.String} object. + * @return a boolean. + */ + @Override + public final boolean hasKeyword(final String keyword) { + String kw = keyword; + if (kw.startsWith("HIDDEN")) { + kw = kw.substring(7); + } + return this.getKeyword().contains(kw); + } + + /** + *

+ * hasStartOfKeyword. + *

+ * + * @param keyword + * a {@link java.lang.String} object. + * @return a boolean. + */ + public final boolean hasStartOfKeyword(final String keyword) { + final List a = this.getKeyword(); + for (int i = 0; i < a.size(); i++) { + if (a.get(i).toString().startsWith(keyword)) { + return true; + } + } + return false; + } + + /** + *

+ * hasStartOfKeyword. + *

+ * + * @param keyword + * a {@link java.lang.String} object. + * @return a boolean. + */ + public final boolean hasStartOfUnHiddenKeyword(final String keyword) { + final List a = this.getUnhiddenKeyword(); + for (int i = 0; i < a.size(); i++) { + if (a.get(i).toString().startsWith(keyword)) { + return true; + } + } + return false; + } + + /** + *

+ * getKeywordPosition. + *

+ * + * @param k + * a {@link java.lang.String} object. + * @return a int. + */ + public final int getKeywordPosition(final String k) { + final List a = this.getKeyword(); + for (int i = 0; i < a.size(); i++) { + if (a.get(i).toString().startsWith(k)) { + return i; + } + } + return -1; + } + + /** + *

+ * hasAnyKeyword. + *

+ * + * @param keywords + * an array of {@link java.lang.String} objects. + * @return a boolean. + */ + public final boolean hasAnyKeyword(final Iterable keywords) { + for (final String keyword : keywords) { + if (this.hasKeyword(keyword)) { + return true; + } + } + + return false; + } + + // This counts the number of instances of a keyword a card has + /** + *

+ * getAmountOfKeyword. + *

+ * + * @param k + * a {@link java.lang.String} object. + * @return a int. + */ + public final int getAmountOfKeyword(final String k) { + int count = 0; + for (String kw : this.getKeyword()) { + if (kw.equals(k)) { + count++; + } + } + + return count; + } + + // This is for keywords with a number like Bushido, Annihilator and Rampage. + // It returns the total. + /** + *

+ * getKeywordMagnitude. + *

+ * + * @param k + * a {@link java.lang.String} object. + * @return a int. + */ + public final int getKeywordMagnitude(final String k) { + int count = 0; + for (final String kw : this.getKeyword()) { + if (kw.startsWith(k)) { + final String[] parse = kw.split(" "); + final String s = parse[1]; + count += Integer.parseInt(s); + } + } + return count; + } + + private String toMixedCase(final String s) { + if (s.equals("")) { + return s; + } + final StringBuilder sb = new StringBuilder(); + // to handle hyphenated Types + final String[] types = s.split("-"); + for (int i = 0; i < types.length; i++) { + if (i != 0) { + sb.append("-"); + } + sb.append(types[i].substring(0, 1).toUpperCase()); + sb.append(types[i].substring(1).toLowerCase()); + } + + return sb.toString(); + } + + // usable to check for changelings + /** + *

+ * isType. + *

+ * + * @param type + * a {@link java.lang.String} object. + * @return a boolean. + */ + public final boolean isType(String type) { + if (type == null) { + return false; + } + + type = this.toMixedCase(type); + + if (this.typeContains(type) + || ((this.isCreature() || this.isTribal()) && forge.card.CardType.isACreatureType(type) && this + .typeContains("AllCreatureTypes"))) { + return true; + } + return false; + } // isType + + // Takes one argument like Permanent.Blue+withFlying + /** + *

+ * isValid. + *

+ * + * @param restriction + * a {@link java.lang.String} object. + * @param sourceController + * a {@link forge.game.player.Player} object. + * @param source + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + @Override + public final boolean isValid(final String restriction, final Player sourceController, final Card source) { + + if (this.isImmutable() + && !source.getRemembered().contains(this)) { // special case exclusion + return false; + } + + // Inclusive restrictions are Card types + final String[] incR = restriction.split("\\.", 2); + + boolean testFailed = false; + if (incR[0].startsWith("!")) { + testFailed = true; // a bit counter logical)) + incR[0] = incR[0].substring(1); // consume negation sign + } + + if (incR[0].equals("Spell") && !this.isSpell()) { + return testFailed; + } + if (incR[0].equals("Permanent") && (this.isInstant() || this.isSorcery())) { + return testFailed; + } + if (!incR[0].equals("card") && !incR[0].equals("Card") && !incR[0].equals("Spell") + && !incR[0].equals("Permanent") && !(this.isType(incR[0]))) { + return testFailed; // Check for wrong type + } + + if (incR.length > 1) { + final String excR = incR[1]; + final String[] exR = excR.split("\\+"); // Exclusive Restrictions are ... + for (int j = 0; j < exR.length; j++) { + if (!this.hasProperty(exR[j], sourceController, source)) { + return testFailed; + } + } + } + return !testFailed; + } // isValid(String Restriction) + + // Takes arguments like Blue or withFlying + /** + *

+ * hasProperty. + *

+ * + * @param property + * a {@link java.lang.String} object. + * @param sourceController + * a {@link forge.game.player.Player} object. + * @param source + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + @Override + public boolean hasProperty(final String property, final Player sourceController, final Card source) { + final Game game = getGame(); + final Combat combat = game.getCombat(); + // by name can also have color names, so needs to happen before colors. + if (property.startsWith("named")) { + if (!this.getName().equals(property.substring(5))) { + return false; + } + } else if (property.startsWith("notnamed")) { + if (this.getName().equals(property.substring(8))) { + return false; + } + } else if (property.startsWith("sameName")) { + if (!this.getName().equals(source.getName())) { + return false; + } + } else if (property.equals("NamedCard")) { + if (!this.getName().equals(source.getNamedCard())) { + return false; + } + } else if (property.equals("NamedByRememberedPlayer")) { + if (source.getRemembered().isEmpty()) { + final Card newCard = game.getCardState(source); + for (final Object o : newCard.getRemembered()) { + if (o instanceof Player) { + if (!this.getName().equals(((Player) o).getNamedCard())) { + return false; + } + } + } + } + for (final Object o : source.getRemembered()) { + if (o instanceof Player) { + if (!this.getName().equals(((Player) o).getNamedCard())) { + return false; + } + } + } + } else if (property.equals("ChosenCard")) { + if (!source.getChosenCard().contains(this)) { + return false; + } + } else if (property.equals("nonChosenCard")) { + if (source.getChosenCard().contains(this)) { + return false; + } + } + // ... Card colors + else if (property.contains("White") || property.contains("Blue") || property.contains("Black") + || property.contains("Red") || property.contains("Green")) { + boolean mustHave = !property.startsWith("non"); + int desiredColor = MagicColor.fromName(mustHave ? property : property.substring(3)); + boolean hasColor = CardUtil.getColors(this).hasAnyColor(desiredColor); + if (mustHave != hasColor) + return false; + + } else if (property.contains("Colorless")) { // ... Card is colorless + if (property.startsWith("non") == CardUtil.getColors(this).isColorless()) return false; + + } else if (property.contains("MultiColor")) { // ... Card is multicolored + if (property.startsWith("non") == CardUtil.getColors(this).isMulticolor()) return false; + + } else if (property.contains("MonoColor")) { // ... Card is monocolored + if (property.startsWith("non") == CardUtil.getColors(this).isMonoColor()) return false; + + } else if (property.equals("ChosenColor")) { + if (source.getChosenColor().isEmpty() || !CardUtil.getColors(this).hasAnyColor(MagicColor.fromName(source.getChosenColor().get(0)))) + return false; + + } else if (property.equals("AllChosenColors")) { + if (source.getChosenColor().isEmpty() || !CardUtil.getColors(this).hasAllColors(ColorSet.fromNames(source.getChosenColor()).getColor())) + return false; + + } else if (property.equals("AnyChosenColor")) { + if (source.getChosenColor().isEmpty() || !CardUtil.getColors(this).hasAnyColor(ColorSet.fromNames(source.getChosenColor()).getColor())) + return false; + + } else if (property.equals("DoubleFaced")) { + if (!this.isDoubleFaced()) { + return false; + } + } else if (property.equals("Flip")) { + if (!this.isFlipCard()) { + return false; + } + } else if (property.startsWith("YouCtrl")) { + if (!this.getController().equals(sourceController)) { + return false; + } + } else if (property.startsWith("YouDontCtrl")) { + if (this.getController().equals(sourceController)) { + return false; + } + } else if (property.startsWith("OppCtrl")) { + if (!this.getController().getOpponents().contains(sourceController)) { + return false; + } + } else if (property.startsWith("ChosenCtrl")) { + if (!this.getController().equals(source.getChosenPlayer())) { + return false; + } + } else if (property.startsWith("DefenderCtrl")) { + if (!game.getPhaseHandler().inCombat()) { + return false; + } + if (property.endsWith("ForRemembered")) { + if (source.getRemembered().isEmpty()) { + return false; + } + if (getGame().getCombat().getDefendingPlayerRelatedTo((Card) source.getRemembered().get(0)) + != this.getController()) { + return false; + } + } else { + if (getGame().getCombat().getDefendingPlayerRelatedTo(source) != this.getController()) { + return false; + } + } + } else if (property.startsWith("DefendingPlayerCtrl")) { + if (!game.getPhaseHandler().inCombat()) { + return false; + } + if (!getGame().getCombat().isPlayerAttacked(this.getController())) { + return false; + } + } else if (property.startsWith("EnchantedPlayerCtrl")) { + final Object o = source.getEnchanting(); + if (o instanceof Player) { + if (!this.getController().equals(o)) { + return false; + } + } else { // source not enchanting a player + return false; + } + } else if (property.startsWith("EnchantedControllerCtrl")) { + final Object o = source.getEnchanting(); + if (o instanceof Card) { + if (!this.getController().equals(((Card) o).getController())) { + return false; + } + } else { // source not enchanting a card + return false; + } + } else if (property.startsWith("RememberedPlayer")) { + Player p = property.endsWith("Ctrl") ? this.getController() : this.getOwner(); + if (source.getRemembered().isEmpty()) { + final Card newCard = game.getCardState(source); + for (final Object o : newCard.getRemembered()) { + if (o instanceof Player) { + if (!p.equals(o)) { + return false; + } + } + } + } + + for (final Object o : source.getRemembered()) { + if (o instanceof Player) { + if (!p.equals(o)) { + return false; + } + } + } + } else if (property.startsWith("nonRememberedPlayerCtrl")) { + if (source.getRemembered().isEmpty()) { + final Card newCard = game.getCardState(source); + if (newCard.getRemembered().contains(this.getController())) { + return false; + } + } + + if (source.getRemembered().contains(this.getController())) { + return false; + } + } else if (property.equals("TargetedPlayerCtrl")) { + for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { + final SpellAbility saTargeting = sa.getSATargetingPlayer(); + if (saTargeting != null) { + for (final Player p : saTargeting.getTargets().getTargetPlayers()) { + if (!this.getController().equals(p)) { + return false; + } + } + } + } + } else if (property.equals("TargetedControllerCtrl")) { + for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { + final List list = AbilityUtils.getDefinedCards(source, "Targeted", sa); + final List sas = AbilityUtils.getDefinedSpellAbilities(source, "Targeted", sa); + for (final Card c : list) { + final Player p = c.getController(); + if (!this.getController().equals(p)) { + return false; + } + } + for (final SpellAbility s : sas) { + final Player p = s.getSourceCard().getController(); + if (!this.getController().equals(p)) { + return false; + } + } + } + } else if (property.startsWith("ActivePlayerCtrl")) { + if (!game.getPhaseHandler().isPlayerTurn(this.getController())) { + return false; + } + } else if (property.startsWith("NonActivePlayerCtrl")) { + if (game.getPhaseHandler().isPlayerTurn(this.getController())) { + return false; + } + } else if (property.startsWith("YouOwn")) { + if (!this.getOwner().equals(sourceController)) { + return false; + } + } else if (property.startsWith("YouDontOwn")) { + if (this.getOwner().equals(sourceController)) { + return false; + } + } else if (property.startsWith("OppOwn")) { + if (!this.getOwner().getOpponents().contains(sourceController)) { + return false; + } + } else if (property.equals("TargetedPlayerOwn")) { + for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { + final SpellAbility saTargeting = sa.getSATargetingPlayer(); + if (saTargeting != null) { + for (final Player p : saTargeting.getTargets().getTargetPlayers()) { + if (!this.getOwner().equals(p)) { + return false; + } + } + } + } + } else if (property.startsWith("OwnedBy")) { + final String valid = property.substring(8); + if (!this.getOwner().isValid(valid, sourceController, source)) { + return false; + } + } else if (property.startsWith("OwnerDoesntControl")) { + if (this.getOwner().equals(this.getController())) { + return false; + } + } else if (property.startsWith("ControllerControls")) { + final String type = property.substring(18); + if (type.startsWith("AtLeastAsMany")) { + String realType = type.split("AtLeastAsMany")[1]; + List list = CardLists.getType(this.getController().getCardsIn(ZoneType.Battlefield), realType); + List yours = CardLists.getType(sourceController.getCardsIn(ZoneType.Battlefield), realType); + if (list.size() < yours.size()) { + return false; + } + } else { + final List list = this.getController().getCardsIn(ZoneType.Battlefield); + if (CardLists.getType(list, type).isEmpty()) { + return false; + } + } + } else if (property.startsWith("Other")) { + if (this.equals(source)) { + return false; + } + } else if (property.startsWith("Self")) { + if (!this.equals(source)) { + return false; + } + } else if (property.startsWith("AttachedBy")) { + if (!this.equippedBy.contains(source) && !this.getEnchantedBy().contains(source) && !this.getFortifiedBy().contains(source)) { + return false; + } + } else if (property.equals("Attached")) { + if (!this.equipping.contains(source) && !source.equals(this.enchanting) && !this.fortifying.contains(source)) { + return false; + } + } else if (property.startsWith("AttachedTo")) { + final String restriction = property.split("AttachedTo ")[1]; + if (restriction.equals("Targeted")) { + if (!source.getCharacteristics().getTriggers().isEmpty()) { + for (final Trigger t : source.getCharacteristics().getTriggers()) { + final SpellAbility sa = t.getTriggeredSA(); + final List list = AbilityUtils.getDefinedCards(source, "Targeted", sa); + for (final Card c : list) { + if (!this.equipping.contains(c) && !c.equals(this.enchanting)) { + return false; + } + } + } + } else { + for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { + final List list = AbilityUtils.getDefinedCards(source, "Targeted", sa); + for (final Card c : list) { + if (!this.equipping.contains(c) && !c.equals(this.enchanting)) { + return false; + } + } + } + } + } else { + if (((this.enchanting == null) || !this.enchanting.isValid(restriction, sourceController, source)) + && (this.equipping.isEmpty() || !this.equipping.get(0).isValid(restriction, sourceController, source)) + && (this.fortifying.isEmpty() || !this.fortifying.get(0).isValid(restriction, sourceController, source))) { + return false; + } + } + } else if (property.equals("NameNotEnchantingEnchantedPlayer")) { + Player enchantedPlayer = source.getEnchantingPlayer(); + if (enchantedPlayer == null) { + return false; + } + + List enchanting = enchantedPlayer.getEnchantedBy(); + for (Card c : enchanting) { + if (this.getName().equals(c.getName())) { + return false; + } + } + } else if (property.equals("NotAttachedTo")) { + if (this.equipping.contains(source) || source.equals(this.enchanting) || this.fortifying.contains(source)) { + return false; + } + } else if (property.startsWith("EnchantedBy")) { + if (property.equals("EnchantedBy")) { + if (!this.getEnchantedBy().contains(source) && !this.equals(source.getEnchanting())) { + return false; + } + } else { + final String restriction = property.split("EnchantedBy ")[1]; + if (restriction.equals("Imprinted")) { + for (final Card card : source.getImprinted()) { + if (!this.getEnchantedBy().contains(card) && !this.equals(card.getEnchanting())) { + return false; + } + } + } else if (restriction.equals("Targeted")) { + for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { + final SpellAbility saTargeting = sa.getSATargetingCard(); + if (saTargeting != null) { + for (final Card c : saTargeting.getTargets().getTargetCards()) { + if (!this.getEnchantedBy().contains(c) && !this.equals(c.getEnchanting())) { + return false; + } + } + } + } + } + } + } else if (property.startsWith("NotEnchantedBy")) { + if (property.substring(14).equals("Targeted")) { + for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { + final SpellAbility saTargeting = sa.getSATargetingCard(); + if (saTargeting != null) { + for (final Card c : saTargeting.getTargets().getTargetCards()) { + if (this.getEnchantedBy().contains(c)) { + return false; + } + } + } + } + } else { + if (this.getEnchantedBy().contains(source)) { + return false; + } + } + } else if (property.startsWith("Enchanted")) { + if (!source.equals(this.enchanting)) { + return false; + } + } else if (property.startsWith("CanEnchant")) { + final String restriction = property.substring(10); + if (restriction.equals("Remembered")) { + for (final Object rem : source.getRemembered()) { + if (!(rem instanceof Card) || !((Card) rem).canBeEnchantedBy(this)) + return false; + } + } else if (restriction.equals("Source")) { + if (!source.canBeEnchantedBy(this)) return false; + } + } else if (property.startsWith("CanBeEnchantedBy")) { + if (property.substring(16).equals("Targeted")) { + for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { + final SpellAbility saTargeting = sa.getSATargetingCard(); + if (saTargeting != null) { + for (final Card c : saTargeting.getTargets().getTargetCards()) { + if (!this.canBeEnchantedBy(c)) { + return false; + } + } + } + } + } else if (property.substring(16).equals("AllRemembered")) { + for (final Object rem : source.getRemembered()) { + if (rem instanceof Card) { + final Card card = (Card) rem; + if (!this.canBeEnchantedBy(card)) { + return false; + } + } + } + } else { + if (!this.canBeEnchantedBy(source)) { + return false; + } + } + } else if (property.startsWith("EquippedBy")) { + if (property.substring(10).equals("Targeted")) { + for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { + final SpellAbility saTargeting = sa.getSATargetingCard(); + if (saTargeting != null) { + for (final Card c : saTargeting.getTargets().getTargetCards()) { + if (!this.equippedBy.contains(c)) { + return false; + } + } + } + } + } else if (property.substring(10).equals("Enchanted")) { + if (source.getEnchantingCard() == null || + !this.equippedBy.contains(source.getEnchantingCard())) { + return false; + } + } else { + if (!this.equippedBy.contains(source)) { + return false; + } + } + } else if (property.startsWith("FortifiedBy")) { + if (!this.fortifiedBy.contains(source)) { + return false; + } + } else if (property.startsWith("CanBeEquippedBy")) { + if (!this.canBeEquippedBy(source)) { + return false; + } + } else if (property.startsWith("Equipped")) { + if (!this.equipping.contains(source)) { + return false; + } + } else if (property.startsWith("Fortified")) { + if (!this.fortifying.contains(source)) { + return false; + } + } else if (property.startsWith("HauntedBy")) { + if (!this.hauntedBy.contains(source)) { + return false; + } + } else if (property.startsWith("notTributed")) { + if (this.tributed) { + return false; + } + } else if (property.contains("Paired")) { + if (property.contains("With")) { // PairedWith + if (!this.isPaired() || this.pairedWith != source) { + return false; + } + } else if (property.startsWith("Not")) { // NotPaired + if (this.isPaired()) { + return false; + } + } else { // Paired + if (!this.isPaired()) { + return false; + } + } + } else if (property.startsWith("Above")) { // "Are Above" Source + final List list = this.getOwner().getCardsIn(ZoneType.Graveyard); + if (list.indexOf(source) >= list.indexOf(this)) { + return false; + } + } else if (property.startsWith("DirectlyAbove")) { // "Are Directly Above" Source + final List list = this.getOwner().getCardsIn(ZoneType.Graveyard); + if (list.indexOf(this) - list.indexOf(source) != 1) { + return false; + } + } else if (property.startsWith("TopGraveyardCreature")) { + List list = CardLists.filter(this.getOwner().getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES); + Collections.reverse(list); + if (list.isEmpty() || !this.equals(list.get(0))) { + return false; + } + } else if (property.startsWith("TopGraveyard")) { + final List list = new ArrayList(this.getOwner().getCardsIn(ZoneType.Graveyard)); + Collections.reverse(list); + if (property.substring(12).matches("[0-9][0-9]?")) { + int n = Integer.parseInt(property.substring(12)); + int num = Math.min(n, list.size()); + final List newlist = new ArrayList(); + for (int i = 0; i < num; i++) { + newlist.add(list.get(i)); + } + if (list.isEmpty() || !newlist.contains(this)) { + return false; + } + } else { + if (list.isEmpty() || !this.equals(list.get(0))) { + return false; + } + } + } else if (property.startsWith("BottomGraveyard")) { + final List list = this.getOwner().getCardsIn(ZoneType.Graveyard); + if (list.isEmpty() || !this.equals(list.get(0))) { + return false; + } + } else if (property.startsWith("TopLibrary")) { + final List list = this.getOwner().getCardsIn(ZoneType.Library); + if (list.isEmpty() || !this.equals(list.get(0))) { + return false; + } + } else if (property.startsWith("Cloned")) { + if ((this.cloneOrigin == null) || !this.cloneOrigin.equals(source)) { + return false; + } + } else if (property.startsWith("DamagedBy")) { + if (!this.receivedDamageFromThisTurn.containsKey(source)) { + return false; + } + } else if (property.startsWith("Damaged")) { + if (!this.dealtDamageToThisTurn.containsKey(source)) { + return false; + } + } else if (property.startsWith("IsTargetingSource")) { + for (final SpellAbility sa : this.getCharacteristics().getSpellAbility()) { + final SpellAbility saTargeting = sa.getSATargetingCard(); + if (saTargeting != null) { + for (final Card c : saTargeting.getTargets().getTargetCards()) { + if (c.equals(source)) { + return true; + } + } + } + } + return false; + } else if (property.startsWith("SharesColorWith")) { + if (property.equals("SharesColorWith")) { + if (!this.sharesColorWith(source)) { + return false; + } + } else { + final String restriction = property.split("SharesColorWith ")[1]; + if (restriction.equals("TopCardOfLibrary")) { + final List list = sourceController.getCardsIn(ZoneType.Library); + if (list.isEmpty() || !this.sharesColorWith(list.get(0))) { + return false; + } + } else if (restriction.equals("Remembered")) { + for (final Object obj : source.getRemembered()) { + if (!(obj instanceof Card) || !this.sharesColorWith((Card) obj)) { + return false; + } + } + } else if (restriction.equals("Imprinted")) { + for (final Card card : source.getImprinted()) { + if (!this.sharesColorWith(card)) { + return false; + } + } + } else if (restriction.equals("Equipped")) { + if (!source.isEquipment() || !source.isEquipping() + || !this.sharesColorWith(source.getEquippingCard())) { + return false; + } + } else if (restriction.equals("MostProminentColor")) { + byte mask = CardFactoryUtil.getMostProminentColors(game.getCardsIn(ZoneType.Battlefield)); + if (!CardUtil.getColors(this).hasAnyColor(mask)) + return false; + } else if (restriction.equals("LastCastThisTurn")) { + final List c = game.getStack().getCardsCastThisTurn(); + if (c.isEmpty() || !this.sharesColorWith(c.get(c.size() - 1))) { + return false; + } + } else if (restriction.equals("ActivationColor")) { + byte manaSpent = source.getColorsPaid(); + if (!CardUtil.getColors(this).hasAnyColor(manaSpent)) { + return false; + } + } else { + for (final Card card : sourceController.getCardsIn(ZoneType.Battlefield)) { + if (card.isValid(restriction, sourceController, source) && this.sharesColorWith(card)) { + return true; + } + } + return false; + } + } + } else if (property.startsWith("MostProminentColor")) { + // MostProminentColor + // e.g. MostProminentColor black + String[] props = property.split(" "); + if (props.length == 1) { + System.out.println("WARNING! Using MostProminentColor property without a color."); + return false; + } + String color = props[1]; + + byte mostProm = CardFactoryUtil.getMostProminentColors(game.getCardsIn(ZoneType.Battlefield)); + return ColorSet.fromMask(mostProm).hasAnyColor(MagicColor.fromName(color)); + } else if (property.startsWith("notSharesColorWith")) { + if (property.equals("notSharesColorWith")) { + if (this.sharesColorWith(source)) { + return false; + } + } else { + final String restriction = property.split("notSharesColorWith ")[1]; + for (final Card card : sourceController.getCardsIn(ZoneType.Battlefield)) { + if (card.isValid(restriction, sourceController, source) && this.sharesColorWith(card)) { + return false; + } + } + } + } else if (property.startsWith("sharesCreatureTypeWith")) { + if (property.equals("sharesCreatureTypeWith")) { + if (!this.sharesCreatureTypeWith(source)) { + return false; + } + } else { + final String restriction = property.split("sharesCreatureTypeWith ")[1]; + if (restriction.equals("TopCardOfLibrary")) { + final List list = sourceController.getCardsIn(ZoneType.Library); + if (list.isEmpty() || !this.sharesCreatureTypeWith(list.get(0))) { + return false; + } + } if (restriction.equals("Enchanted")) { + for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { + final SpellAbility root = sa.getRootAbility(); + Card c = source.getEnchantingCard(); + if ((c == null) && (root != null) + && (root.getPaidList("Sacrificed") != null) + && !root.getPaidList("Sacrificed").isEmpty()) { + c = root.getPaidList("Sacrificed").get(0).getEnchantingCard(); + if (!this.sharesCreatureTypeWith(c)) { + return false; + } + } + } + } if (restriction.equals("Equipped")) { + if (source.isEquipping() && this.sharesCreatureTypeWith(source.getEquippingCard())) { + return true; + } + return false; + } if (restriction.equals("Remembered")) { + for (final Object rem : source.getRemembered()) { + if (rem instanceof Card) { + final Card card = (Card) rem; + if (this.sharesCreatureTypeWith(card)) { + return true; + } + } + } + return false; + } if (restriction.equals("AllRemembered")) { + for (final Object rem : source.getRemembered()) { + if (rem instanceof Card) { + final Card card = (Card) rem; + if (!this.sharesCreatureTypeWith(card)) { + return false; + } + } + } + } else { + boolean shares = false; + for (final Card card : sourceController.getCardsIn(ZoneType.Battlefield)) { + if (card.isValid(restriction, sourceController, source) && this.sharesCreatureTypeWith(card)) { + shares = true; + } + } + if (!shares) { + return false; + } + } + } + } else if (property.startsWith("sharesCardTypeWith")) { + if (property.equals("sharesCardTypeWith")) { + if (!this.sharesCardTypeWith(source)) { + return false; + } + } else { + final String restriction = property.split("sharesCardTypeWith ")[1]; + if (restriction.equals("Imprinted")) { + if (source.getImprinted().isEmpty() || !this.sharesCardTypeWith(source.getImprinted().get(0))) { + return false; + } + } else if (restriction.equals("Remembered")) { + for (final Object rem : source.getRemembered()) { + if (rem instanceof Card) { + final Card card = (Card) rem; + if (this.sharesCardTypeWith(card)) { + return true; + } + } + } + return false; + } else if (restriction.equals("EachTopLibrary")) { + final List list = new ArrayList(); + for (Player p : game.getPlayers()) { + final Card top = p.getCardsIn(ZoneType.Library).get(0); + list.add(top); + } + for (Card c : list) { + if (this.sharesCardTypeWith(c)) { + return true; + } + } + return false; + } + } + } else if (property.startsWith("sharesNameWith")) { + if (property.equals("sharesNameWith")) { + if (!this.getName().equals(source.getName())) { + return false; + } + } else { + final String restriction = property.split("sharesNameWith ")[1]; + if (restriction.equals("YourGraveyard")) { + for (final Card card : sourceController.getCardsIn(ZoneType.Graveyard)) { + if (this.getName().equals(card.getName())) { + return true; + } + } + return false; + } else if (restriction.equals(ZoneType.Graveyard.toString())) { + for (final Card card : game.getCardsIn(ZoneType.Graveyard)) { + if (this.getName().equals(card.getName())) { + return true; + } + } + return false; + } else if (restriction.equals(ZoneType.Battlefield.toString())) { + for (final Card card : game.getCardsIn(ZoneType.Battlefield)) { + if (this.getName().equals(card.getName())) { + return true; + } + } + return false; + } else if (restriction.equals("ThisTurnCast")) { + for (final Card card : CardUtil.getThisTurnCast("Card", source)) { + if (this.getName().equals(card.getName())) { + return true; + } + } + return false; + } else if (restriction.equals("Remembered")) { + for (final Object rem : source.getRemembered()) { + if (rem instanceof Card) { + final Card card = (Card) rem; + if (this.getName().equals(card.getName())) { + return true; + } + } + } + return false; + } else if (restriction.equals("Imprinted")) { + for (final Card card : source.getImprinted()) { + if (this.getName().equals(card.getName())) { + return true; + } + } + return false; + } else if (restriction.equals("MovedToGrave")) { + for (final SpellAbility sa : source.getCharacteristics().getSpellAbility()) { + final SpellAbility root = sa.getRootAbility(); + if (root != null && (root.getPaidList("MovedToGrave") != null) + && !root.getPaidList("MovedToGrave").isEmpty()) { + List list = root.getPaidList("MovedToGrave"); + for (Card card : list) { + if (this.getName().equals(card.getName())) { + return true; + } + } + } + } + return false; + } else if (restriction.equals("NonToken")) { + final List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), + Presets.NON_TOKEN); + for (final Card card : list) { + if (this.getName().equals(card.getName())) { + return true; + } + } + return false; + } + } + } else if (property.startsWith("doesNotShareNameWith")) { + if (property.equals("doesNotShareNameWith")) { + if (this.getName().equals(source.getName())) { + return false; + } + } else { + final String restriction = property.split("doesNotShareNameWith ")[1]; + if (restriction.equals("Remembered")) { + for (final Object rem : source.getRemembered()) { + if (rem instanceof Card) { + final Card card = (Card) rem; + if (this.getName().equals(card.getName())) { + return false; + } + } + } + } + } + } else if (property.startsWith("sharesControllerWith")) { + if (property.equals("sharesControllerWith")) { + if (!this.sharesControllerWith(source)) { + return false; + } + } else { + final String restriction = property.split("sharesControllerWith ")[1]; + if (restriction.equals("Remembered")) { + for (final Object rem : source.getRemembered()) { + if (rem instanceof Card) { + final Card card = (Card) rem; + if (!this.sharesControllerWith(card)) { + return false; + } + } + } + } else if (restriction.equals("Imprinted")) { + for (final Card card : source.getImprinted()) { + if (!this.sharesControllerWith(card)) { + return false; + } + } + } + } + } else if (property.startsWith("sharesOwnerWith")) { + if (property.equals("sharesOwnerWith")) { + if (!this.getOwner().equals(source.getOwner())) { + return false; + } + } else { + final String restriction = property.split("sharesOwnerWith ")[1]; + if (restriction.equals("Remembered")) { + for (final Object rem : source.getRemembered()) { + if (rem instanceof Card) { + final Card card = (Card) rem; + if (!this.getOwner().equals(card.getOwner())) { + return false; + } + } + } + } + } + } else if (property.startsWith("SecondSpellCastThisTurn")) { + final List list = CardUtil.getThisTurnCast("Card", source); + if (list.size() < 2) { + return false; + } + else if (list.get(1) != this) { + return false; + } + } else if (property.equals("ThisTurnCast")) { + for (final Card card : CardUtil.getThisTurnCast("Card", source)) { + if (this.equals(card)) { + return true; + } + } + return false; + } else if (property.startsWith("ThisTurnEntered")) { + final String restrictions = property.split("ThisTurnEntered_")[1]; + final String[] res = restrictions.split("_"); + final ZoneType destination = ZoneType.smartValueOf(res[0]); + ZoneType origin = null; + if (res[1].equals("from")) { + origin = ZoneType.smartValueOf(res[2]); + } + List list = CardUtil.getThisTurnEntered(destination, + origin, "Card", source); + if (!list.contains(this)) { + return false; + } + } else if (property.startsWith("sharesTypeWith")) { + if (property.equals("sharesTypeWith")) { + if (!this.sharesTypeWith(source)) { + return false; + } + } else { + final String restriction = property.split("sharesTypeWith ")[1]; + if (restriction.equals("FirstImprinted")) { + if (source.getImprinted().isEmpty() || + !this.sharesTypeWith(source.getImprinted().get(0))) { + return false; + } + } + } + } else if (property.startsWith("withFlashback")) { + boolean fb = false; + if (this.hasStartOfUnHiddenKeyword("Flashback")) { + fb = true; + } + for (final SpellAbility sa : this.getSpellAbilities()) { + if (sa.isFlashBackAbility()) { + fb = true; + } + } + if (!fb) { + return false; + } + } else if (property.startsWith("with")) { + // ... Card keywords + if (property.startsWith("without") && this.hasStartOfUnHiddenKeyword(property.substring(7))) { + return false; + } + if (!property.startsWith("without") && !this.hasStartOfUnHiddenKeyword(property.substring(4))) { + return false; + } + } else if (property.startsWith("tapped")) { + if (!this.isTapped()) { + return false; + } + } else if (property.startsWith("untapped")) { + if (!this.isUntapped()) { + return false; + } + } else if (property.startsWith("faceDown")) { + if (!this.isFaceDown()) { + return false; + } + } else if (property.startsWith("faceUp")) { + if (this.isFaceDown()) { + return false; + } + } else if (property.startsWith("hasLevelUp")) { + for (final SpellAbility sa : this.getSpellAbilities()) { + if (sa.getApi() == ApiType.PutCounter && sa.hasParam("LevelUp")) { + return true; + } + } + return false; + } else if (property.startsWith("DrawnThisTurn")) { + if (!this.getDrawnThisTurn()) { + return false; + } + } else if (property.startsWith("enteredBattlefieldThisTurn")) { + if (!(this.getTurnInZone() == game.getPhaseHandler().getTurn())) { + return false; + } + } else if (property.startsWith("notEnteredBattlefieldThisTurn")) { + if (this.getTurnInZone() == game.getPhaseHandler().getTurn()) { + return false; + } + } else if (property.startsWith("firstTurnControlled")) { + if (!this.isFirstTurnControlled()) { + return false; + } + } else if (property.startsWith("notFirstTurnControlled")) { + if (this.isFirstTurnControlled()) { + return false; + } + } else if (property.startsWith("startedTheTurnUntapped")) { + if (!this.hasStartedTheTurnUntapped()) { + return false; + } + } else if (property.equals("attackedOrBlockedSinceYourLastUpkeep")) { + if (!this.getDamageHistory().hasAttackedSinceLastUpkeepOf(sourceController) + && !this.getDamageHistory().hasBlockedSinceLastUpkeepOf(sourceController)) { + return false; + } + } else if (property.equals("blockedOrBeenBlockedSinceYourLastUpkeep")) { + if (!this.getDamageHistory().hasBeenBlockedSinceLastUpkeepOf(sourceController) + && !this.getDamageHistory().hasBlockedSinceLastUpkeepOf(sourceController)) { + return false; + } + } else if (property.startsWith("dealtDamageToYouThisTurn")) { + if (!this.getDamageHistory().getThisTurnDamaged().contains(sourceController)) { + return false; + } + } else if (property.startsWith("dealtDamageToOppThisTurn")) { + if (!this.getDamageHistory().getThisTurnDamaged().contains(sourceController.getOpponent())) { + return false; + } + } else if (property.startsWith("controllerWasDealtCombatDamageByThisTurn")) { + if (!source.getDamageHistory().getThisTurnCombatDamaged().contains(this.getController())) { + return false; + } + } else if (property.startsWith("controllerWasDealtDamageByThisTurn")) { + if (!source.getDamageHistory().getThisTurnDamaged().contains(this.getController())) { + return false; + } + } else if (property.startsWith("wasDealtDamageThisTurn")) { + if ((this.getReceivedDamageFromThisTurn().keySet()).isEmpty()) { + return false; + } + } else if (property.equals("wasDealtDamageByHostThisTurn")) { + if (!this.getReceivedDamageFromThisTurn().keySet().contains(source)) { + return false; + } + } else if (property.equals("wasDealtDamageByEquipeeThisTurn")) { + Card equipee = source.getEquippingCard(); + if (equipee == null || this.getReceivedDamageFromThisTurn().keySet().isEmpty() + || !this.getReceivedDamageFromThisTurn().keySet().contains(equipee)) { + return false; + } + } else if (property.equals("wasDealtDamageByEnchantedThisTurn")) { + Card enchanted = source.getEnchantingCard(); + if (enchanted == null || this.getReceivedDamageFromThisTurn().keySet().isEmpty() + || !this.getReceivedDamageFromThisTurn().keySet().contains(enchanted)) { + return false; + } + } else if (property.startsWith("dealtDamageThisTurn")) { + if (this.getTotalDamageDoneBy() == 0) { + return false; + } + } else if (property.startsWith("attackedThisTurn")) { + if (!this.getDamageHistory().getCreatureAttackedThisTurn()) { + return false; + } + } else if (property.startsWith("attackedLastTurn")) { + return this.getDamageHistory().getCreatureAttackedLastTurnOf(this.getController()); + } else if (property.startsWith("blockedThisTurn")) { + if (!this.getDamageHistory().getCreatureBlockedThisTurn()) { + return false; + } + } else if (property.startsWith("gotBlockedThisTurn")) { + if (!this.getDamageHistory().getCreatureGotBlockedThisTurn()) { + return false; + } + } else if (property.startsWith("notAttackedThisTurn")) { + if (this.getDamageHistory().getCreatureAttackedThisTurn()) { + return false; + } + } else if (property.startsWith("notAttackedLastTurn")) { + return !this.getDamageHistory().getCreatureAttackedLastTurnOf(this.getController()); + } else if (property.startsWith("notBlockedThisTurn")) { + if (this.getDamageHistory().getCreatureBlockedThisTurn()) { + return false; + } + } else if (property.startsWith("greatestPower")) { + final List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); + for (final Card crd : list) { + if (crd.getNetAttack() > this.getNetAttack()) { + return false; + } + } + } else if (property.startsWith("yardGreatestPower")) { + final List list = CardLists.filter(sourceController.getCardsIn(ZoneType.Graveyard), Presets.CREATURES); + for (final Card crd : list) { + if (crd.getNetAttack() > this.getNetAttack()) { + return false; + } + } + } else if (property.startsWith("leastPower")) { + final List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); + for (final Card crd : list) { + if (crd.getNetAttack() < this.getNetAttack()) { + return false; + } + } + } else if (property.startsWith("leastToughness")) { + final List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); + for (final Card crd : list) { + if (crd.getNetDefense() < this.getNetDefense()) { + return false; + } + } + } else if (property.startsWith("greatestCMC")) { + List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); + if (property.contains("ControlledBy")) { + List p = AbilityUtils.getDefinedPlayers(source, property.split("ControlledBy")[1], null); + list = CardLists.filterControlledBy(list, p); + if (!list.contains(this)) { + return false; + } + } + for (final Card crd : list) { + if (crd.isSplitCard()) { + if (crd.getCMC(Card.SplitCMCMode.LeftSplitCMC) > this.getCMC() || crd.getCMC(Card.SplitCMCMode.RightSplitCMC) > this.getCMC()) { + return false; + } + } else { + if (crd.getCMC() > this.getCMC()) { + return false; + } + } + } + } else if (property.startsWith("greatestRememberedCMC")) { + List list = new ArrayList(); + for (final Object o : source.getRemembered()) { + if (o instanceof Card) { + list.add(game.getCardState((Card) o)); + } + } + if (!list.contains(this)) { + return false; + } + list = CardLists.getCardsWithHighestCMC(list); + if (!list.contains(this)) { + return false; + } + } else if (property.startsWith("lowestRememberedCMC")) { + List list = new ArrayList(); + for (final Object o : source.getRemembered()) { + if (o instanceof Card) { + list.add(game.getCardState((Card) o)); + } + } + if (!list.contains(this)) { + return false; + } + list = CardLists.getCardsWithLowestCMC(list); + if (!list.contains(this)) { + return false; + } + } else if (property.startsWith("lowestCMC")) { + final List list = game.getCardsIn(ZoneType.Battlefield); + for (final Card crd : list) { + if (!crd.isLand() && !crd.isImmutable()) { + if (crd.isSplitCard()) { + if (crd.getCMC(Card.SplitCMCMode.LeftSplitCMC) < this.getCMC() || crd.getCMC(Card.SplitCMCMode.RightSplitCMC) < this.getCMC()) { + return false; + } + } else { + if (crd.getCMC() < this.getCMC()) { + return false; + } + } + } + } + } else if (property.startsWith("enchanted")) { + if (!this.isEnchanted()) { + return false; + } + } else if (property.startsWith("unenchanted")) { + if (this.isEnchanted()) { + return false; + } + } else if (property.startsWith("enchanting")) { + if (!this.isEnchanting()) { + return false; + } + } else if (property.startsWith("equipped")) { + if (!this.isEquipped()) { + return false; + } + } else if (property.startsWith("unequipped")) { + if (this.isEquipped()) { + return false; + } + } else if (property.startsWith("equipping")) { + if (!this.isEquipping()) { + return false; + } + } else if (property.startsWith("token")) { + if (!this.isToken()) { + return false; + } + } else if (property.startsWith("nonToken")) { + if (this.isToken()) { + return false; + } + } else if (property.startsWith("hasXCost")) { + SpellAbility sa1 = this.getFirstSpellAbility(); + if (sa1 != null && !sa1.isXCost()) { + return false; + } + } else if (property.startsWith("suspended")) { + if (!this.hasSuspend() || !game.isCardExiled(this) + || !(this.getCounters(CounterType.getType("TIME")) >= 1)) { + return false; + } + + } else if (property.startsWith("power") || property.startsWith("toughness") + || property.startsWith("cmc") || property.startsWith("totalPT")) { + int x = 0; + int y = 0; + int y2 = -1; // alternative value for the second split face of a split card + String rhs = ""; + + if (property.startsWith("power")) { + rhs = property.substring(7); + y = this.getNetAttack(); + } else if (property.startsWith("toughness")) { + rhs = property.substring(11); + y = this.getNetDefense(); + } else if (property.startsWith("cmc")) { + rhs = property.substring(5); + if (isSplitCard() && getCurState() == CardCharacteristicName.Original) { + y = getState(CardCharacteristicName.LeftSplit).getManaCost().getCMC(); + y2 = getState(CardCharacteristicName.RightSplit).getManaCost().getCMC(); + } else { + y = getCMC(); + } + } else if (property.startsWith("totalPT")) { + rhs = property.substring(10); + y = this.getNetAttack() + this.getNetDefense(); + } + try { + x = Integer.parseInt(rhs); + } catch (final NumberFormatException e) { + x = CardFactoryUtil.xCount(source, source.getSVar(rhs)); + } + + if (y2 == -1) { + if (!Expressions.compare(y, property, x)) { + return false; + } + } else { + if (!Expressions.compare(y, property, x) && !Expressions.compare(y2, property, x)) { + return false; + } + } + } + + // syntax example: countersGE9 P1P1 or countersLT12TIME (greater number + // than 99 not supported) + /* + * slapshot5 - fair warning, you cannot use numbers with 2 digits + * (greater number than 9 not supported you can use X and the + * SVar:X:Number$12 to get two digits. This will need a better fix, and + * I have the beginnings of a regex below + */ + else if (property.startsWith("counters")) { + /* + * Pattern p = Pattern.compile("[a-z]*[A-Z][A-Z][X0-9]+.*$"); + * String[] parse = ??? + * System.out.println("Parsing completed of: "+Property); for (int i + * = 0; i < parse.length; i++) { + * System.out.println("parse["+i+"]: "+parse[i]); } + */ + + // TODO get a working regex out of this pattern so the amount of + // digits doesn't matter + int number = 0; + final String[] splitProperty = property.split("_"); + final String strNum = splitProperty[1].substring(2); + final String comparator = splitProperty[1].substring(0, 2); + String counterType = ""; + try { + number = Integer.parseInt(strNum); + } catch (final NumberFormatException e) { + number = CardFactoryUtil.xCount(source, source.getSVar(strNum)); + } + counterType = splitProperty[2]; + + final int actualnumber = this.getCounters(CounterType.getType(counterType)); + + if (!Expressions.compare(actualnumber, comparator, number)) { + return false; + } + } + // These predicated refer to ongoing combat. If no combat happens, they'll return false (meaning not attacking/blocking ATM) + else if (property.startsWith("attacking")) { + if (null == combat) return false; + if (property.equals("attacking")) return combat.isAttacking(this); + if (property.equals("attackingYou")) return combat.isAttacking(this, sourceController); + } else if (property.startsWith("notattacking")) { + return null == combat || !combat.isAttacking(this); + } else if (property.equals("attackedBySourceThisCombat")) { + if (null == combat) return false; + final GameEntity defender = combat.getDefenderByAttacker(source); + if (defender instanceof Card) { + if (!this.equals((Card) defender)) { + return false; + } + } + } else if (property.startsWith("blocking")) { + if (null == combat) return false; + String what = property.substring("blocking".length()); + + if (StringUtils.isEmpty(what)) return combat.isBlocking(this); + if (what.startsWith("Source")) return combat.isBlocking(this, source) ; + if (what.startsWith("CreatureYouCtrl")) { + for (final Card c : CardLists.filter(sourceController.getCardsIn(ZoneType.Battlefield), Presets.CREATURES)) + if (combat.isBlocking(this, c)) + return true; + return false; + } + if (what.startsWith("Remembered")) { + for (final Object o : source.getRemembered()) { + if (o instanceof Card && combat.isBlocking(this, (Card) o)) { + return true; + } + } + return false; + } + } else if (property.startsWith("sharesBlockingAssignmentWith")) { + if (null == combat) { return false; } + if (null == combat.getAttackersBlockedBy(source) || null == combat.getAttackersBlockedBy(this)) { return false; } + + List sourceBlocking = new ArrayList(combat.getAttackersBlockedBy(source)); + List thisBlocking = new ArrayList(combat.getAttackersBlockedBy(this)); + if (Collections.disjoint(sourceBlocking, thisBlocking)) { + return false; + } + } else if (property.startsWith("notblocking")) { + return null == combat || !combat.isBlocking(this); + } + // Nex predicates refer to past combat and don't need a reference to actual combat + else if (property.equals("blocked")) { + return null != combat && combat.isBlocked(this); + } else if (property.startsWith("blockedBySource")) { + return null != combat && combat.isBlocking(source, this); + } else if (property.startsWith("blockedThisTurn")) { + return this.getBlockedThisTurn() != null; + } else if (property.startsWith("blockedByThisTurn")) { + return this.getBlockedByThisTurn() != null; + } else if (property.startsWith("blockedBySourceThisTurn")) { + return source.getBlockedByThisTurn() != null && source.getBlockedByThisTurn().contains(this); + } else if (property.startsWith("blockedSource")) { + return null != combat && combat.isBlocking(this, source); + } else if (property.startsWith("isBlockedByRemembered")) { + if (null == combat) return false; + for (final Object o : source.getRemembered()) { + if (o instanceof Card && combat.isBlocking((Card) o, this)) { + return true; + } + } + return false; + } else if (property.startsWith("blockedRemembered")) { + Card rememberedcard; + for (final Object o : source.getRemembered()) { + if (o instanceof Card) { + rememberedcard = (Card) o; + if (this.getBlockedThisTurn() == null || !this.getBlockedThisTurn().contains(rememberedcard)) { + return false; + } + } + } + } else if (property.startsWith("blockedByRemembered")) { + Card rememberedcard; + for (final Object o : source.getRemembered()) { + if (o instanceof Card) { + rememberedcard = (Card) o; + if (this.getBlockedByThisTurn() == null || !this.getBlockedByThisTurn().contains(rememberedcard)) { + return false; + } + } + } + } else if (property.startsWith("unblocked")) { + if (game.getCombat() == null || !game.getCombat().isUnblocked(this)) { + return false; + } + } else if (property.equals("attackersBandedWith")) { + if (this.equals(source)) { + // You don't band with yourself + return false; + } + AttackingBand band = combat == null ? null : combat.getBandOfAttacker(source); + if (band == null || !band.getAttackers().contains(this)) { + return false; + } + } else if (property.startsWith("kicked")) { + if (property.equals("kicked")) { + if (this.getKickerMagnitude() == 0) { + return false; + } + } else { + String s = property.split("kicked ")[1]; + if ("1".equals(s) && !this.isOptionalCostPaid(OptionalCost.Kicker1)) return false; + if ("2".equals(s) && !this.isOptionalCostPaid(OptionalCost.Kicker2)) return false; + } + } else if (property.startsWith("notkicked")) { + if (this.getKickerMagnitude() > 0) { + return false; + } + } else if (property.startsWith("evoked")) { + if (!this.isEvoked()) { + return false; + } + } else if (property.equals("HasDevoured")) { + if (this.devouredCards.size() == 0) { + return false; + } + } else if (property.equals("HasNotDevoured")) { + if (this.devouredCards.size() != 0) { + return false; + } + } else if (property.equals("IsMonstrous")) { + if (!this.isMonstrous()) { + return false; + } + } else if (property.equals("IsNotMonstrous")) { + if (this.isMonstrous()) { + return false; + } + } else if (property.startsWith("non")) { + // ... Other Card types + if (this.isType(property.substring(3))) { + return false; + } + } else if (property.equals("CostsPhyrexianMana")) { + if (!this.getCharacteristics().getManaCost().hasPhyrexian()) { + return false; + } + } else if (property.equals("IsRemembered")) { + if (!source.getRemembered().contains(this)) { + return false; + } + } else if (property.equals("IsNotRemembered")) { + if (source.getRemembered().contains(this)) { + return false; + } + } else if (property.equals("IsImprinted")) { + if (!source.getImprinted().contains(this)) { + return false; + } + } else if (property.equals("IsNotImprinted")) { + if (source.getImprinted().contains(this)) { + return false; + } + } else if (property.equals("hasActivatedAbilityWithTapCost")) { + for (final SpellAbility sa : this.getSpellAbilities()) { + if (sa.isAbility() && (sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) { + return true; + } + } + return false; + } else if (property.equals("hasActivatedAbility")) { + for (final SpellAbility sa : this.getSpellAbilities()) { + if (sa.isAbility()) { + return true; + } + } + return false; + } else if (property.equals("hasManaAbility")) { + for (final SpellAbility sa : this.getSpellAbilities()) { + if (sa.isManaAbility()) { + return true; + } + } + return false; + } else if (property.equals("hasNonManaActivatedAbility")) { + for (final SpellAbility sa : this.getSpellAbilities()) { + if (sa.isAbility() && !sa.isManaAbility()) { + return true; + } + } + return false; + } else if (property.equals("NoAbilities")) { + if (!((this.getAbilityText().trim().equals("") || this.isFaceDown()) && (this.getUnhiddenKeyword().size() == 0))) { + return false; + } + } else if (property.equals("HasCounters")) { + if (!this.hasCounters()) { + return false; + } + } else if (property.startsWith("wasCastFrom")) { + final String strZone = property.substring(11); + final ZoneType realZone = ZoneType.smartValueOf(strZone); + if (realZone != this.getCastFrom()) { + return false; + } + } else if (property.startsWith("wasNotCastFrom")) { + final String strZone = property.substring(14); + final ZoneType realZone = ZoneType.smartValueOf(strZone); + if (realZone == this.getCastFrom()) { + return false; + } + } else if (property.startsWith("set")) { + final String setCode = property.substring(3, 6); + if (!this.getCurSetCode().equals(setCode)) { + return false; + } + } else if (property.startsWith("inZone")) { + final String strZone = property.substring(6); + final ZoneType realZone = ZoneType.smartValueOf(strZone); + if (!this.isInZone(realZone)) { + return false; + } + } else if (property.equals("ChosenType")) { + if (!this.isType(source.getChosenType())) { + return false; + } + } else if (property.equals("IsNotChosenType")) { + if (this.isType(source.getChosenType())) { + return false; + } + } else if (property.equals("IsCommander")) { + if (!this.isCommander) { + return false; + } + } else { + if (!this.isType(property)) { + return false; + } + } + return true; + } // hasProperty + + /** + *

+ * setImmutable. + *

+ * + * @param isImmutable + * a boolean. + */ + public final void setImmutable(final boolean isImmutable) { + this.isImmutable = isImmutable; + } + + /** + *

+ * isImmutable. + *

+ * + * @return a boolean. + */ + public final boolean isImmutable() { + return this.isImmutable; + } + + /* + * there are easy checkers for Color. The CardUtil functions should be made + * part of the Card class, so calling out is not necessary + */ + + public final boolean isOfColor(final String col) { return CardUtil.getColors(this).hasAnyColor(MagicColor.fromName(col)); } + public final boolean isBlack() { return CardUtil.getColors(this).hasBlack(); } + public final boolean isBlue() { return CardUtil.getColors(this).hasBlue(); } + public final boolean isRed() { return CardUtil.getColors(this).hasRed(); } + public final boolean isGreen() { return CardUtil.getColors(this).hasGreen(); } + public final boolean isWhite() { return CardUtil.getColors(this).hasWhite(); } + public final boolean isColorless() { return CardUtil.getColors(this).isColorless(); } + + /** + *

+ * sharesColorWith. + *

+ * + * @param c1 + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean sharesColorWith(final Card c1) { + boolean shares = false; + shares |= (this.isBlack() && c1.isBlack()); + shares |= (this.isBlue() && c1.isBlue()); + shares |= (this.isGreen() && c1.isGreen()); + shares |= (this.isRed() && c1.isRed()); + shares |= (this.isWhite() && c1.isWhite()); + return shares; + } + + /** + *

+ * sharesCreatureTypeWith. + *

+ * + * @param c1 + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean sharesCreatureTypeWith(final Card c1) { + + if (c1 == null) { + return false; + } + + for (final String type : this.getType()) { + if (type.equals("AllCreatureTypes") && c1.hasACreatureType()) { + return true; + } + if (forge.card.CardType.isACreatureType(type) && c1.isType(type)) { + return true; + } + } + return false; + } + + /** + *

+ * sharesTypeWith. + *

+ * + * @param c1 + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean sharesCardTypeWith(final Card c1) { + + for (final String type : this.getType()) { + if (forge.card.CardType.isACardType(type) && c1.isType(type)) { + return true; + } + } + return false; + } + + /** + *

+ * sharesTypeWith. + *

+ * + * @param c1 + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean sharesTypeWith(final Card c1) { + + for (final String type : this.getType()) { + if (c1.isType(type)) { + return true; + } + } + return false; + } + + /** + *

+ * sharesControllerWith. + *

+ * + * @param c1 + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean sharesControllerWith(final Card c1) { + + if (c1 == null) { + return false; + } + + return this.getController().equals(c1.getController()); + } + + /** + *

+ * hasACreatureType. + *

+ * + * @return a boolean. + */ + public final boolean hasACreatureType() { + for (final String type : this.getType()) { + if (forge.card.CardType.isACreatureType(type) || type.equals("AllCreatureTypes")) { + return true; + } + } + return false; + } + + /** + *

+ * isUsedToPay. + *

+ * + * @return a boolean. + */ + public final boolean isUsedToPay() { + return this.usedToPayCost; + } + + /** + *

+ * setUsedToPay. + *

+ * + * @param b + * a boolean. + */ + public final void setUsedToPay(final boolean b) { + this.usedToPayCost = b; + } + + // ///////////////////////// + // + // Damage code + // + // //////////////////////// + + /** + *

+ * addReceivedDamageFromThisTurn. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param damage + * a int. + */ + public final void addReceivedDamageFromThisTurn(final Card c, final int damage) { + this.receivedDamageFromThisTurn.put(c, damage); + } + + /** + *

+ * Setter for the field receivedDamageFromThisTurn. + *

+ * + * @param receivedDamageList + * a Map object. + */ + public final void setReceivedDamageFromThisTurn(final Map receivedDamageList) { + this.receivedDamageFromThisTurn = receivedDamageList; + } + + /** + *

+ * Getter for the field receivedDamageFromThisTurn. + *

+ * + * @return a Map object. + */ + public final Map getReceivedDamageFromThisTurn() { + return this.receivedDamageFromThisTurn; + } + + /** + *

+ * resetReceivedDamageFromThisTurn. + *

+ */ + public final void resetReceivedDamageFromThisTurn() { + this.receivedDamageFromThisTurn.clear(); + } + + public final int getTotalDamageRecievedThisTurn() { + int total = 0; + for (int damage : this.receivedDamageFromThisTurn.values()) { + total += damage; + } + return total; + } + + /** + *

+ * addDealtDamageToThisTurn. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param damage + * a int. + */ + public final void addDealtDamageToThisTurn(final Card c, final int damage) { + this.dealtDamageToThisTurn.put(c, damage); + } + + /** + *

+ * Setter for the field dealtDamageToThisTurn. + *

+ * + * @param dealtDamageList + * a {@link java.util.Map} object. + */ + public final void setDealtDamageToThisTurn(final Map dealtDamageList) { + this.dealtDamageToThisTurn = dealtDamageList; + } + + /** + *

+ * Getter for the field dealtDamageToThisTurn. + *

+ * + * @return a {@link java.util.Map} object. + */ + public final Map getDealtDamageToThisTurn() { + return this.dealtDamageToThisTurn; + } + + /** + *

+ * resetDealtDamageToThisTurn. + *

+ */ + public final void resetDealtDamageToThisTurn() { + this.dealtDamageToThisTurn.clear(); + } + + /** + *

+ * addDealtDamageToPlayerThisTurn. + *

+ * + * @param player + * player as name String. + * @param damage + * a int. + */ + public final void addDealtDamageToPlayerThisTurn(final String player, final int damage) { + this.dealtDamageToPlayerThisTurn.put(player, damage); + } + + /** + *

+ * Setter for the field dealtDamageToPlayerThisTurn. + *

+ * + * @param dealtDamageList + * a {@link java.util.Map} object. + */ + public final void setDealtDamageToPlayerThisTurn(final Map dealtDamageList) { + this.dealtDamageToPlayerThisTurn = dealtDamageList; + } + + /** + *

+ * Getter for the field dealtDamageToPlayerThisTurn. + *

+ * + * @return a {@link java.util.Map} object. + */ + public final Map getDealtDamageToPlayerThisTurn() { + return this.dealtDamageToPlayerThisTurn; + } + + /** + *

+ * resetDealtDamageToPlayerThisTurn. + *

+ */ + public final void resetDealtDamageToPlayerThisTurn() { + this.dealtDamageToPlayerThisTurn.clear(); + } + + // this is the minimal damage a trampling creature has to assign to a blocker + /** + *

+ * getLethalDamage. + *

+ * + * @return a int. + */ + public final int getLethalDamage() { + final int lethalDamage = this.getNetDefense() - this.getDamage() - this.getTotalAssignedDamage(); + + return lethalDamage; + } + + /** + *

+ * Setter for the field damage. + *

+ * + * @param n + * a int. + */ + public final void setDamage(final int n) { + int oldDamae = damage; + this.damage = n; + if (n != oldDamae) + getGame().fireEvent(new GameEventCardStatsChanged(this)); + } + + /** + *

+ * Getter for the field damage. + *

+ * + * @return a int. + */ + public final int getDamage() { + return this.damage; + } + + /** + *

+ * addAssignedDamage. + *

+ * + * @param damage + * a int. + * @param sourceCard + * a {@link forge.game.card.Card} object. + */ + public final void addAssignedDamage(int damage, final Card sourceCard) { + if (damage < 0) { + damage = 0; + } + + final int assignedDamage = damage; + + Log.debug(this + " - was assigned " + assignedDamage + " damage, by " + sourceCard); + if (!this.assignedDamageMap.containsKey(sourceCard)) { + this.assignedDamageMap.put(sourceCard, assignedDamage); + } else { + this.assignedDamageMap.put(sourceCard, this.assignedDamageMap.get(sourceCard) + assignedDamage); + } + } + + /** + *

+ * clearAssignedDamage. + *

+ */ + public final void clearAssignedDamage() { + this.assignedDamageMap.clear(); + } + + /** + *

+ * getTotalAssignedDamage. + *

+ * + * @return a int. + */ + public final int getTotalAssignedDamage() { + int total = 0; + + final Collection c = this.assignedDamageMap.values(); + + final Iterator itr = c.iterator(); + while (itr.hasNext()) { + total += itr.next(); + } + + return total; + } + + /** + *

+ * Getter for the field assignedDamageMap. + *

+ * + * @return a {@link java.util.Map} object. + */ + public final Map getAssignedDamageMap() { + return this.assignedDamageMap; + } + + /** + *

+ * addCombatDamage. + *

+ * + * @param map + * a {@link java.util.Map} object. + */ + public final void addCombatDamage(final Map map) { + final List list = new ArrayList(); + + for (final Entry entry : map.entrySet()) { + final Card source = entry.getKey(); + list.add(source); + int damageToAdd = entry.getValue(); + + damageToAdd = this.replaceDamage(damageToAdd, source, true); + damageToAdd = this.preventDamage(damageToAdd, source, true); + + map.put(source, damageToAdd); + } + + if (this.isInPlay()) { + this.addDamage(map); + } + } + + // This is used by the AI to forecast an effect (so it must not change the game state) + /** + *

+ * staticDamagePrevention. + *

+ * + * @param damage + * a int. + * @param possiblePrevention + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @param isCombat + * a boolean. + * @return a int. + */ + public final int staticDamagePrevention(final int damage, final int possiblePrevention, final Card source, + final boolean isCombat) { + + if (getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) { + return damage; + } + + for (final Card ca : getGame().getCardsIn(ZoneType.Battlefield)) { + for (final ReplacementEffect re : ca.getReplacementEffects()) { + Map params = re.getMapParams(); + if (!"DamageDone".equals(params.get("Event")) || !params.containsKey("PreventionEffect")) { + continue; + } + if (params.containsKey("ValidSource") + && !source.isValid(params.get("ValidSource"), ca.getController(), ca)) { + continue; + } + if (params.containsKey("ValidTarget") + && !this.isValid(params.get("ValidTarget"), ca.getController(), ca)) { + continue; + } + if (params.containsKey("IsCombat")) { + if (params.get("IsCombat").equals("True")) { + if (!isCombat) { + continue; + } + } else { + if (isCombat) { + continue; + } + } + + } + return 0; + } + } + + int restDamage = damage - possiblePrevention; + + restDamage = this.staticDamagePrevention(restDamage, source, isCombat, true); + + return restDamage; + } + + // This should be also usable by the AI to forecast an effect (so it must not change the game state) + /** + *

+ * staticDamagePrevention. + *

+ * + * @param damageIn + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @param isCombat + * a boolean. + * @return a int. + */ + @Override + public final int staticDamagePrevention(final int damageIn, final Card source, final boolean isCombat, final boolean isTest) { + + if (getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) { + return damageIn; + } + + if (isCombat && getGame().getPhaseHandler().isPreventCombatDamageThisTurn()) { + return 0; + } + + int restDamage = damageIn; + + if (this.hasProtectionFrom(source)) { + return 0; + } + + for (String kw : source.getKeyword()) { + if (isCombat) { + if (kw.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")) { + return 0; + } + if (kw.equals("Prevent all combat damage that would be dealt by CARDNAME.")) { + return 0; + } + } + if (kw.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) { + return 0; + } + if (kw.equals("Prevent all damage that would be dealt by CARDNAME.")) { + return 0; + } + } + for (String kw : this.getKeyword()) { + if (isCombat) { + if (kw.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")) { + return 0; + } + if (kw.equals("Prevent all combat damage that would be dealt to CARDNAME.")) { + return 0; + } + } + if (kw.equals("Prevent all damage that would be dealt to CARDNAME.")) { + return 0; + } + if (kw.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) { + return 0; + } + if (kw.startsWith("Absorb")) { + final int absorbed = this.getKeywordMagnitude("Absorb"); + if (restDamage > absorbed) { + restDamage = restDamage - absorbed; + } else { + return 0; + } + } + if (kw.startsWith("PreventAllDamageBy")) { + String valid = this.getKeyword().get(this.getKeywordPosition("PreventAllDamageBy")); + valid = valid.split(" ", 2)[1]; + if (source.isValid(valid, this.getController(), this)) { + return 0; + } + } + } + + // Prevent Damage static abilities + for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { + final ArrayList staticAbilities = ca.getStaticAbilities(); + for (final StaticAbility stAb : staticAbilities) { + restDamage = stAb.applyAbility("PreventDamage", source, this, restDamage, isCombat, isTest); + } + } + + // specific Cards + if (this.isCreature()) { // and not a planeswalker + + if (source.isCreature() && getGame().isCardInPlay("Well-Laid Plans") + && source.sharesColorWith(this)) { + return 0; + } + } // Creature end + + + return restDamage > 0 ? restDamage : 0; + } + + /** + *

+ * preventDamage. + *

+ * + * @param damage + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @param isCombat + * a boolean. + * @return a int. + */ + @Override + public final int preventDamage(final int damage, final Card source, final boolean isCombat) { + + if (getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) + || source.hasKeyword("Damage that would be dealt by CARDNAME can't be prevented.")) { + return damage; + } + + int restDamage = damage; + + boolean DEBUGShieldsWithEffects = false; + while (!this.getPreventNextDamageWithEffect().isEmpty() && restDamage != 0) { + TreeMap> shieldMap = this.getPreventNextDamageWithEffect(); + List preventionEffectSources = new ArrayList(shieldMap.keySet()); + Card shieldSource = preventionEffectSources.get(0); + if (preventionEffectSources.size() > 1) { + Map choiceMap = new TreeMap(); + List choices = new ArrayList(); + for (final Card key : preventionEffectSources) { + String effDesc = shieldMap.get(key).get("EffectString"); + int descIndex = effDesc.indexOf("SpellDescription"); + effDesc = effDesc.substring(descIndex + 18); + String shieldDescription = key.toString() + " - " + shieldMap.get(key).get("ShieldAmount") + + " shields - " + effDesc; + choices.add(shieldDescription); + choiceMap.put(shieldDescription, key); + } + shieldSource = this.getController().getController().chooseProtectionShield(this, choices, choiceMap); + } + if (DEBUGShieldsWithEffects) { + System.out.println("Prevention shield source: " + shieldSource); + } + + int shieldAmount = Integer.valueOf(shieldMap.get(shieldSource).get("ShieldAmount")); + int dmgToBePrevented = Math.min(restDamage, shieldAmount); + if (DEBUGShieldsWithEffects) { + System.out.println("Selected source initial shield amount: " + shieldAmount); + System.out.println("Incoming damage: " + restDamage); + System.out.println("Damage to be prevented: " + dmgToBePrevented); + } + + //Set up ability + SpellAbility shieldSA = null; + String effectAbString = shieldMap.get(shieldSource).get("EffectString"); + effectAbString = effectAbString.replace("PreventedDamage", Integer.toString(dmgToBePrevented)); + effectAbString = effectAbString.replace("ShieldEffectTarget", shieldMap.get(shieldSource).get("ShieldEffectTarget")); + if (DEBUGShieldsWithEffects) { + System.out.println("Final shield ability string: " + effectAbString); + } + shieldSA = AbilityFactory.getAbility(effectAbString, shieldSource); + if (shieldSA.usesTargeting()) { + System.err.println(shieldSource + " - Targeting for prevention shield's effect should be done with initial spell"); + } + + boolean apiIsEffect = (shieldSA.getApi() == ApiType.Effect); + List cardsInCommand = null; + if (apiIsEffect) { + cardsInCommand = this.getGame().getCardsIn(ZoneType.Command); + } + + this.getController().getController().playSpellAbilityNoStack(shieldSA, true); + if (apiIsEffect) { + List newCardsInCommand = this.getGame().getCardsIn(ZoneType.Command); + newCardsInCommand.removeAll(cardsInCommand); + if (!newCardsInCommand.isEmpty()) { + newCardsInCommand.get(0).setSVar("PreventedDamage", "Number$" + Integer.toString(dmgToBePrevented)); + } + } + this.subtractPreventNextDamageWithEffect(shieldSource, restDamage); + restDamage = restDamage - dmgToBePrevented; + + if (DEBUGShieldsWithEffects) { + System.out.println("Remaining shields: " + + (shieldMap.containsKey(shieldSource) ? shieldMap.get(shieldSource).get("ShieldAmount") : "all shields used")); + System.out.println("Remaining damage: " + restDamage); + } + } + + + final HashMap repParams = new HashMap(); + repParams.put("Event", "DamageDone"); + repParams.put("Affected", this); + repParams.put("DamageSource", source); + repParams.put("DamageAmount", damage); + repParams.put("IsCombat", isCombat); + repParams.put("Prevention", true); + + if (getGame().getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { + return 0; + } + + restDamage = this.staticDamagePrevention(restDamage, source, isCombat, false); + + if (restDamage == 0) { + return 0; + } + + if (this.hasKeyword("If damage would be dealt to CARDNAME, " + + "prevent that damage. Remove a +1/+1 counter from CARDNAME.")) { + restDamage = 0; + this.subtractCounter(CounterType.P1P1, 1); + } + + if (restDamage >= this.getPreventNextDamage()) { + restDamage = restDamage - this.getPreventNextDamage(); + this.setPreventNextDamage(0); + } else { + this.setPreventNextDamage(this.getPreventNextDamage() - restDamage); + restDamage = 0; + } + + return restDamage; + } + + // This is used by the AI to forecast an effect (so it must not change the game state) + /** + *

+ * staticReplaceDamage. + *

+ * + * @param damage + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @param isCombat + * a boolean. + * @return a int. + */ + @Override + public final int staticReplaceDamage(final int damage, final Card source, final boolean isCombat) { + + int restDamage = damage; + for (Card c : getGame().getCardsIn(ZoneType.Battlefield)) { + if (c.getName().equals("Sulfuric Vapors")) { + if (source.isSpell() && source.isRed()) { + restDamage += 1; + } + } else if (c.getName().equals("Pyromancer's Swath")) { + if (c.getController().equals(source.getController()) && (source.isInstant() || source.isSorcery()) + && this.isCreature()) { + restDamage += 2; + } + } else if (c.getName().equals("Furnace of Rath")) { + if (this.isCreature()) { + restDamage += restDamage; + } + } else if (c.getName().equals("Gratuitous Violence")) { + if (c.getController().equals(source.getController()) && source.isCreature() && this.isCreature()) { + restDamage += restDamage; + } + } else if (c.getName().equals("Fire Servant")) { + if (c.getController().equals(source.getController()) && source.isRed() + && (source.isInstant() || source.isSorcery())) { + restDamage *= 2; + } + } else if (c.getName().equals("Gisela, Blade of Goldnight")) { + if (!c.getController().equals(this.getController())) { + restDamage *= 2; + } + } else if (c.getName().equals("Inquisitor's Flail")) { + if (isCombat && c.getEquippingCard() != null + && (c.getEquippingCard().equals(this) || c.getEquippingCard().equals(source))) { + restDamage *= 2; + } + } else if (c.getName().equals("Ghosts of the Innocent")) { + if (this.isCreature()) { + restDamage = restDamage / 2; + } + } else if (c.getName().equals("Benevolent Unicorn")) { + if (source.isSpell() && this.isCreature()) { + restDamage -= 1; + } + } else if (c.getName().equals("Divine Presence")) { + if (restDamage > 3 && this.isCreature()) { + restDamage = 3; + } + } else if (c.getName().equals("Lashknife Barrier")) { + if (c.getController().equals(this.getController()) && this.isCreature()) { + restDamage -= 1; + } + } + } + + if (this.getName().equals("Phytohydra")) { + return 0; + } + + return restDamage; + } + + /** + *

+ * replaceDamage. + *

+ * + * @param damageIn + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @param isCombat + * a boolean. + * @return a int. + */ + @Override + public final int replaceDamage(final int damageIn, final Card source, final boolean isCombat) { + // Replacement effects + final HashMap repParams = new HashMap(); + repParams.put("Event", "DamageDone"); + repParams.put("Affected", this); + repParams.put("DamageSource", source); + repParams.put("DamageAmount", damageIn); + repParams.put("IsCombat", isCombat); + + if (getGame().getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { + return 0; + } + + return damageIn; + } + + /** + *

+ * addDamage. + *

+ * + * @param sourcesMap + * a {@link java.util.Map} object. + */ + public final void addDamage(final Map sourcesMap) { + for (final Entry entry : sourcesMap.entrySet()) { + // damage prevention is already checked! + this.addDamageAfterPrevention(entry.getValue(), entry.getKey(), true); + } + } + + /** + *

+ * addDamageAfterPrevention. + *

+ * This function handles damage after replacement and prevention effects are + * applied. + * + * @param damageIn + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @param isCombat + * a boolean. + * @return whether or not damage as dealt + */ + @Override + public final boolean addDamageAfterPrevention(final int damageIn, final Card source, final boolean isCombat) { + final int damageToAdd = damageIn; + + if (damageToAdd == 0) { + return false; // Rule 119.8 + } + + this.addReceivedDamageFromThisTurn(source, damageToAdd); + source.addDealtDamageToThisTurn(this, damageToAdd); + + if (source.hasKeyword("Lifelink")) { + source.getController().gainLife(damageToAdd, source); + } + + // Run triggers + final Map runParams = new TreeMap(); + runParams.put("DamageSource", source); + runParams.put("DamageTarget", this); + runParams.put("DamageAmount", damageToAdd); + runParams.put("IsCombatDamage", isCombat); + getGame().getTriggerHandler().runTrigger(TriggerType.DamageDone, runParams, false); + + GameEventCardDamaged.DamageType damageType = DamageType.Normal; + if (this.isPlaneswalker()) { + this.subtractCounter(CounterType.LOYALTY, damageToAdd); + damageType = DamageType.LoyaltyLoss; + } + else { + final Game game = source.getGame(); + + final String s = this + " - destroy"; + + final int amount = this.getAmountOfKeyword("When CARDNAME is dealt damage, destroy it."); + if (amount > 0) { + final Ability abDestroy = new Ability(source, ManaCost.ZERO){ + @Override public void resolve() { game.getAction().destroy(Card.this, this); } + }; + abDestroy.setStackDescription(s + ", it cannot be regenerated."); + + for (int i = 0; i < amount; i++) { + game.getStack().addSimultaneousStackEntry(abDestroy); + } + } + + final int amount2 = this.getAmountOfKeyword("When CARDNAME is dealt damage, destroy it. It can't be regenerated."); + if (amount2 > 0) { + final Ability abDestoryNoRegen = new Ability(source, ManaCost.ZERO){ + @Override public void resolve() { game.getAction().destroyNoRegeneration(Card.this, this); } + }; + abDestoryNoRegen.setStackDescription(s); + + for (int i = 0; i < amount2; i++) { + game.getStack().addSimultaneousStackEntry(abDestoryNoRegen); + } + } + + boolean wither = (getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.alwaysWither) + || source.hasKeyword("Wither") || source.hasKeyword("Infect")); + + if (this.isInPlay()) { + if (wither) { + this.addCounter(CounterType.M1M1, damageToAdd, true); + damageType = DamageType.M1M1Counters; + } else + this.damage += damageToAdd; + } + + if (source.hasKeyword("Deathtouch") && this.isCreature()) { + getGame().getAction().destroy(this, null); + damageType = DamageType.Deathtouch; + } + + // Play the Damage sound + game.fireEvent(new GameEventCardDamaged(this, source, damageToAdd, damageType)); + } + + return true; + } + + /** + *

+ * Setter for the field curSetCode. + *

+ * + * @param setCode + * a {@link java.lang.String} object. + */ + public final void setCurSetCode(final String setCode) { + this.getCharacteristics().setCurSetCode(setCode); + } + + /** + *

+ * Getter for the field curSetCode. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getCurSetCode() { + return this.getCharacteristics().getCurSetCode(); + } + + /** + *

+ * getCurSetRarity. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final CardRarity getRarity() { + return this.getCharacteristics().getRarity(); + } + + public final void setRarity(CardRarity r) { + this.getCharacteristics().setRarity(r); + } + + /** + *

+ * getMostRecentSet. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getMostRecentSet() { + return Singletons.getMagicDb().getCommonCards().getCard(this.getName()).getEdition(); + } + + public final void setImageKey(final String iFN) { + this.getCharacteristics().setImageKey(iFN); + } + + public final String getImageKey() { + return this.getCharacteristics().getImageKey(); + } + + public String getImageKey(CardCharacteristicName state) { + CardCharacteristics c = this.characteristicsMap.get(state); + return (c != null ? c.getImageKey() : ""); + } + + /** + *

+ * Setter for the field evoked. + *

+ * + * @param evokedIn + * a boolean. + */ + public final void setEvoked(final boolean evokedIn) { + this.evoked = evokedIn; + } + + /** + *

+ * isEvoked. + *

+ * + * @return a boolean. + */ + public final boolean isEvoked() { + return this.evoked; + } + + /** + *

+ * Setter for the field tributed. + *

+ * + * @param b + * a boolean. + */ + public final void setTributed(final boolean b) { + this.tributed = b; + } + + /** + *

+ * Setter for the field monstrous. + *

+ * + * @param monstrous + * a boolean. + */ + public final void setMonstrous(final boolean monstrous) { + this.monstrous = monstrous; + } + + /** + *

+ * isMonstrous. + *

+ * + * @return a boolean. + */ + public final boolean isMonstrous() { + return this.monstrous; + } + + /** + *

+ * animateBestow. + *

+ */ + public final void animateBestow() { + this.bestowTimestamp = this.getGame().getNextTimestamp(); + this.addChangedCardTypes(new ArrayList(Arrays.asList("Aura")), + new ArrayList(Arrays.asList("Creature")), false, false, false, true, bestowTimestamp); + this.addChangedCardKeywords(Arrays.asList("Enchant creature"), new ArrayList(), false, bestowTimestamp); + } + + /** + *

+ * unanimateBestow. + *

+ */ + public final void unanimateBestow() { + this.removeChangedCardKeywords(bestowTimestamp); + this.removeChangedCardTypes(bestowTimestamp); + bestowTimestamp = -1; + } + + /** + *

+ * isBestowed. + *

+ * + * @return a boolean. + */ + public final boolean isBestowed() { + return this.bestowTimestamp != -1; + } + + /** + *

+ * Setter for the field monstrosityNum. + *

+ * + * @param num + * an int. + */ + public final void setMonstrosityNum(final int num) { + this.monstrosityNum = num; + } + + /** + *

+ * getMonstrosityNum. + *

+ * + * @return a int. + */ + public final int getMonstrosityNum() { + return this.monstrosityNum; + } + /** + * + * TODO Write javadoc for this method. + * + * @param t + * a long + */ + public final void setTimestamp(final long t) { + this.timestamp = t; + } + + /** + * + * TODO Write javadoc for this method. + * + * @return a long + */ + public final long getTimestamp() { + return this.timestamp; + } + + /** + * + * TODO Write javadoc for this method. + * + * @return an int + */ + public final int getFoil() { + final String foil = this.getCharacteristics().getSVar("Foil"); + if (!foil.isEmpty()) { + return Integer.parseInt(foil); + } + return 0; + } + + /** + * Assign a random foil finish depending on the card edition. + * + * @param remove if true, a random foil is assigned, otherwise it is + * removed. + */ + public final void setRandomFoil() { + CardEdition.FoilType foilType = CardEdition.FoilType.NOT_SUPPORTED; + if (this.getCurSetCode() != null && Singletons.getMagicDb().getEditions().get(this.getCurSetCode()) != null) { + foilType = Singletons.getMagicDb().getEditions().get(this.getCurSetCode()).getFoilType(); + } + if (foilType != CardEdition.FoilType.NOT_SUPPORTED) { + this.setFoil(foilType == CardEdition.FoilType.MODERN ? MyRandom.getRandom().nextInt(9) + 1 : MyRandom.getRandom().nextInt(9) + 11); + } + } + + /** + * + * TODO Write javadoc for this method. + * + * @param f an int + */ + public final void setFoil(final int f) { + this.getCharacteristics().setSVar("Foil", Integer.toString(f)); + } + + /** + * Adds the haunted by. + * + * @param c + * the c + */ + public final void addHauntedBy(final Card c) { + this.hauntedBy.add(c); + if (c != null) { + c.setHaunting(this); + } + } + + /** + * Gets the haunted by. + * + * @return the haunted by + */ + public final List getHauntedBy() { + return this.hauntedBy; + } + + /** + * Removes the haunted by. + * + * @param c + * the c + */ + public final void removeHauntedBy(final Card c) { + this.hauntedBy.remove(c); + } + + /** + * Gets the pairing. + * + * @return the pairedWith + */ + public final Card getPairedWith() { + return this.pairedWith; + } + + /** + * Sets the pairing. + * + * @param c + * the new pairedWith + */ + public final void setPairedWith(final Card c) { + this.pairedWith = c; + } + + /** + *

+ * isPaired. + *

+ * + * @return a boolean. + */ + public final boolean isPaired() { + return this.pairedWith != null; + } + + /** + * Gets the haunting. + * + * @return the haunting + */ + public final Card getHaunting() { + return this.haunting; + } + + /** + * Sets the haunting. + * + * @param c + * the new haunting + */ + public final void setHaunting(final Card c) { + this.haunting = c; + } + + /** + * Gets the damage done this turn. + * + * @return the damage done this turn + */ + public final int getDamageDoneThisTurn() { + int sum = 0; + for (final Card c : this.dealtDamageToThisTurn.keySet()) { + sum += this.dealtDamageToThisTurn.get(c); + } + + return sum; + } + + /** + * Gets the damage done to a player by card this turn. + * + * @param player + * the player name + * @return the damage done to player p this turn + */ + public final int getDamageDoneToPlayerBy(final String player) { + int sum = 0; + for (final String p : this.dealtDamageToPlayerThisTurn.keySet()) { + if (p.equals(player)) { + sum += this.dealtDamageToPlayerThisTurn.get(p); + } + } + + return sum; + } + + /** + * Gets the total damage done by card this turn (after prevention and redirects). + * + * @return the damage done to player p this turn + */ + public final int getTotalDamageDoneBy() { + int sum = 0; + for (final Card c : this.dealtDamageToThisTurn.keySet()) { + sum += this.dealtDamageToThisTurn.get(c); + } + for (final String p : this.dealtDamageToPlayerThisTurn.keySet()) { + sum += this.dealtDamageToPlayerThisTurn.get(p); + } + + return sum; + } + + + /* + * (non-Javadoc) + * + * @see forge.GameEntity#hasProtectionFrom(forge.Card) + */ + @Override + public boolean hasProtectionFrom(final Card source) { + if (source == null) { + return false; + } + + if (this.isImmutable()) { + return true; + } + + if (this.getKeyword() != null) { + final List list = this.getKeyword(); + + String kw = ""; + for (int i = 0; i < list.size(); i++) { + kw = list.get(i); + if (!kw.startsWith("Protection")) { + continue; + } + if (kw.equals("Protection from white")) { + if (source.isWhite() && !source.getName().equals("White Ward") + && !source.getName().contains("Pledge of Loyalty")) { + return true; + } + } else if (kw.equals("Protection from blue")) { + if (source.isBlue() && !source.getName().equals("Blue Ward") + && !source.getName().contains("Pledge of Loyalty")) { + return true; + } + } else if (kw.equals("Protection from black")) { + if (source.isBlack() && !source.getName().equals("Black Ward") + && !source.getName().contains("Pledge of Loyalty")) { + return true; + } + } else if (kw.equals("Protection from red")) { + if (source.isRed() && !source.getName().equals("Red Ward") + && !source.getName().contains("Pledge of Loyalty")) { + return true; + } + } else if (kw.equals("Protection from green")) { + if (source.isGreen() && !source.getName().equals("Green Ward") + && !source.getName().contains("Pledge of Loyalty")) { + return true; + } + } else if (kw.equals("Protection from creatures")) { + if (source.isCreature()) { + return true; + } + } else if (kw.equals("Protection from artifacts")) { + if (source.isArtifact()) { + return true; + } + } else if (kw.equals("Protection from enchantments")) { + if (source.isEnchantment() && !source.getName().contains("Tattoo Ward")) { + return true; + } + } else if (kw.equals("Protection from everything")) { + return true; + } else if (kw.startsWith("Protection:")) { // uses isValid + final String characteristic = kw.split(":")[1]; + final String[] characteristics = characteristic.split(","); + if (source.isValid(characteristics, this.getController(), this) + && !source.getName().contains("Flickering Ward") && !source.getName().contains("Pentarch Ward") + && !source.getName().contains("Cho-Manno's Blessing") && !source.getName().contains("Floating Shield") + && !source.getName().contains("Ward of Lights")) { + return true; + } + } else if (kw.equals("Protection from colored spells")) { + if (source.isSpell() && !source.isColorless()) { + return true; + } + } else if (kw.equals("Protection from Dragons")) { + if (source.isType("Dragon")) { + return true; + } + } else if (kw.equals("Protection from Demons")) { + if (source.isType("Demon")) { + return true; + } + } else if (kw.equals("Protection from Goblins")) { + if (source.isType("Goblin")) { + return true; + } + } else if (kw.equals("Protection from Clerics")) { + if (source.isType("Cleric")) { + return true; + } + } else if (kw.equals("Protection from Gorgons")) { + if (source.isType("Gorgon")) { + return true; + } + } else if (kw.equals("Protection from the chosen player")) { + if (source.getController().equals(this.chosenPlayer)) { + return true; + } + } + } + } + return false; + } + + /** + * + * is In Zone. + * + * @param zone + * Constant.Zone + * @return boolean + */ + public boolean isInZone(final ZoneType zone) { + Zone z = this.getZone(); + return z != null && z.getZoneType() == zone; + } + + public Zone getZone() { + return currentZone; + } + + public void setZone(Zone zone) { + currentZone = zone; + } + + public final boolean canBeDestroyed() { + return isInPlay() && (!hasKeyword("Indestructible") || (isCreature() && getNetDefense() <= 0)); + } + + /** + * Can target. + * + * @param sa + * the sa + * @return a boolean + */ + @Override + public final boolean canBeTargetedBy(final SpellAbility sa) { + + if (sa == null) { + return true; + } + + // CantTarget static abilities + for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { + final ArrayList staticAbilities = ca.getStaticAbilities(); + for (final StaticAbility stAb : staticAbilities) { + if (stAb.applyAbility("CantTarget", this, sa)) { + return false; + } + } + } + + // keywords don't work outside battlefield + if (!this.isInZone(ZoneType.Battlefield)) { + return true; + } + + if (this.hasProtectionFrom(sa.getSourceCard())) { + return false; + } + + if (this.isPhasedOut()) { + return false; + } + + final Card source = sa.getSourceCard(); + + if (this.getKeyword() != null) { + for (String kw : this.getKeyword()) { + if (kw.equals("Shroud")) { + return false; + } + + if (kw.equals("Hexproof")) { + if (sa.getActivatingPlayer().getOpponents().contains(this.getController())) { + if (!sa.getActivatingPlayer().getKeywords().contains("Spells and abilities you control can target hexproof creatures")) { + return false; + } + } + } + + if (kw.equals("CARDNAME can't be the target of Aura spells.")) { + if (source.isAura() && sa.isSpell()) { + return false; + } + } + + if (kw.equals("CARDNAME can't be enchanted.")) { + if (source.isAura()) { + return false; + } + } //Sets source as invalid enchant target for computer player only. + + if (kw.equals("CARDNAME can't be equipped.")) { + if (source.isEquipment()) { + return false; + } + } //Sets source as invalid equip target for computer player only. + + if (kw.equals("CARDNAME can't be the target of red spells or abilities from red sources.")) { + if (source.isRed()) { + return false; + } + } + + if (kw.equals("CARDNAME can't be the target of black spells.")) { + if (source.isBlack() && sa.isSpell()) { + return false; + } + } + + if (kw.equals("CARDNAME can't be the target of blue spells.")) { + if (source.isBlue() && sa.isSpell()) { + return false; + } + } + + if (kw.equals("CARDNAME can't be the target of spells.")) { + if (sa.isSpell()) { + return false; + } + } + } + } + if (sa.isSpell() && source.hasStartOfKeyword("SpellCantTarget")) { + final int keywordPosition = source.getKeywordPosition("SpellCantTarget"); + final String parse = source.getKeyword().get(keywordPosition).toString(); + final String[] k = parse.split(":"); + final String[] restrictions = k[1].split(","); + if (this.isValid(restrictions, source.getController(), source)) { + return false; + } + } + return true; + } + + /** + * canBeEnchantedBy. + * + * @param aura + * a Card + * @return a boolean + */ + public final boolean canBeEnchantedBy(final Card aura) { + SpellAbility sa = aura.getFirstSpellAbility(); + if (aura.isBestowed()) { + for (SpellAbility s : aura.getSpellAbilities()) { + if (s.getApi() == ApiType.Attach && s.hasParam("Bestow")) { + sa = s; + break; + } + } + } + TargetRestrictions tgt = null; + if (sa != null) { + tgt = sa.getTargetRestrictions(); + } + + if (this.hasProtectionFrom(aura) + || (this.hasKeyword("CARDNAME can't be enchanted.") && !aura.getName().equals("Anti-Magic Aura") + && !(aura.getName().equals("Consecrate Land") && aura.isInZone(ZoneType.Battlefield))) + || ((tgt != null) && !this.isValid(tgt.getValidTgts(), aura.getController(), aura))) { + return false; + } + return true; + } + + /** + * canBeEquippedBy. + * + * @param equip + * a Card + * @return a boolean + */ + public final boolean canBeEquippedBy(final Card equip) { + if (equip.hasStartOfKeyword("CantEquip")) { + final int keywordPosition = equip.getKeywordPosition("CantEquip"); + final String parse = equip.getKeyword().get(keywordPosition).toString(); + final String[] k = parse.split(" ", 2); + final String[] restrictions = k[1].split(","); + if (this.isValid(restrictions, equip.getController(), equip)) { + return false; + } + } + if (this.hasProtectionFrom(equip) + || this.hasKeyword("CARDNAME can't be equipped.") + || !this.isValid("Creature", equip.getController(), equip)) { + return false; + } + return true; + } + + /** + * Gets the replacement effects. + * + * @return the replacement effects + */ + public List getReplacementEffects() { + return this.getCharacteristics().getReplacementEffects(); + } + + /** + * Sets the replacement effects. + * + * @param res + * the new replacement effects + */ + public void setReplacementEffects(final List res) { + this.getCharacteristics().getReplacementEffects().clear(); + for (final ReplacementEffect replacementEffect : res) { + if (replacementEffect.isIntrinsic()) { + this.addReplacementEffect(replacementEffect); + } + } + } + + /** + * Adds the replacement effect. + * + * @param replacementEffect + * the rE + */ + public ReplacementEffect addReplacementEffect(final ReplacementEffect replacementEffect) { + final ReplacementEffect replacementEffectCopy = replacementEffect.getCopy(); // doubtful - every caller provides a newly parsed instance, why copy? + replacementEffectCopy.setHostCard(this); + this.getCharacteristics().getReplacementEffects().add(replacementEffectCopy); + return replacementEffectCopy; + } + + /** + * Returns what zone this card was cast from (from what zone it was moved to + * the stack). + * + * @return the castFrom + */ + public ZoneType getCastFrom() { + return this.castFrom; + } + + /** + * @param castFrom0 + * the castFrom to set + */ + public void setCastFrom(final ZoneType castFrom0) { + this.castFrom = castFrom0; + } + + /** + * @return CardDamageHistory + */ + public CardDamageHistory getDamageHistory() { + return damageHistory; + } + + /** + * @return the effectSource + */ + public Card getEffectSource() { + return effectSource; + } + + /** + * @param src the effectSource to set + */ + public void setEffectSource(Card src) { + this.effectSource = src; + } + + /** + * @return the startsGameInPlay + */ + public boolean isStartsGameInPlay() { + return startsGameInPlay; + } + + /** + * @param startsGameInPlay the startsGameInPlay to set + */ + public void setStartsGameInPlay(boolean startsGameInPlay) { + this.startsGameInPlay = startsGameInPlay; + } + + /** @return boolean */ + public boolean isInPlay() { + return this.isInZone(ZoneType.Battlefield); + } + + public void onCleanupPhase(final Player turn) { + setDamage(0); + resetPreventNextDamage(); + resetPreventNextDamageWithEffect(); + resetReceivedDamageFromThisTurn(); + resetDealtDamageToThisTurn(); + resetDealtDamageToPlayerThisTurn(); + getDamageHistory().newTurn(); + setRegeneratedThisTurn(0); + setBecameTargetThisTurn(false); + clearMustBlockCards(); + getDamageHistory().setCreatureAttackedLastTurnOf(turn, getDamageHistory().getCreatureAttackedThisTurn()); + getDamageHistory().setCreatureAttackedThisTurn(false); + getDamageHistory().setCreatureAttacksThisTurn(0); + getDamageHistory().setCreatureBlockedThisTurn(false); + getDamageHistory().setCreatureGotBlockedThisTurn(false); + clearBlockedByThisTurn(); + clearBlockedThisTurn(); + } + + /** + *

+ * hasETBTrigger. + *

+ * + * @param card + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public boolean hasETBTrigger() { + for (final Trigger tr : this.getTriggers()) { + final HashMap params = tr.getMapParams(); + if (tr.getMode() != TriggerType.ChangesZone) { + continue; + } + + if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) { + continue; + } + + if (params.containsKey("ValidCard") && !params.get("ValidCard").contains("Self")) { + continue; + } + return true; + } + return false; + } + + /** + *

+ * hasETBTrigger. + *

+ * + * @return a boolean. + */ + public boolean hasETBReplacement() { + for (final ReplacementEffect re : getReplacementEffects()) { + final Map params = re.getMapParams(); + if (!(re instanceof ReplaceMoved)) { + continue; + } + + if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) { + continue; + } + + if (params.containsKey("ValidCard") && !params.get("ValidCard").contains("Self")) { + continue; + } + return true; + } + return false; + } + + public boolean canBeShownTo(final Player viewer) { + if (!isFaceDown()) { + return true; + } + if (getController() == viewer && isInZone(ZoneType.Battlefield)) { + return true; + } + final Game game = this.getGame(); + if (getController() == viewer && hasKeyword("You may look at this card.")) { + return true; + } + if (getController().isOpponentOf(viewer) && hasKeyword("Your opponent may look at this card.")) { + return true; + } + for (Card host : game.getCardsIn(ZoneType.Battlefield)) { + final ArrayList staticAbilities = host.getStaticAbilities(); + for (final StaticAbility stAb : staticAbilities) { + if (stAb.applyAbility("MayLookAt", this, viewer)) { + return true; + } + } + } + return false; + } + + /** + *

+ * getConvertedManaCost. + *

+ * + * @return a int. + */ + public int getCMC() { + return getCMC(SplitCMCMode.CurrentSideCMC); + } + + public int getCMC(SplitCMCMode mode) { + if (isToken() && !isCopiedToken()) { + return 0; + } + + int xPaid = 0; + + // 2012-07-22 - If a card is on the stack, count the xManaCost in with it's CMC + if (getGame().getCardsIn(ZoneType.Stack).contains(this) && getManaCost() != null) { + xPaid = getXManaCostPaid() * getManaCost().countX(); + } + + int requestedCMC = 0; + + if (isSplitCard()) { + switch(mode) { + case CurrentSideCMC: + // TODO: test if this returns combined CMC for the full face (then get rid of CombinedCMC mode?) + requestedCMC = getManaCost().getCMC() + xPaid; + break; + case LeftSplitCMC: + requestedCMC = getState(CardCharacteristicName.LeftSplit).getManaCost().getCMC() + xPaid; + break; + case RightSplitCMC: + requestedCMC = getState(CardCharacteristicName.RightSplit).getManaCost().getCMC() + xPaid; + break; + case CombinedCMC: + requestedCMC += getState(CardCharacteristicName.LeftSplit).getManaCost().getCMC(); + requestedCMC += getState(CardCharacteristicName.RightSplit).getManaCost().getCMC(); + requestedCMC += xPaid; + break; + default: + System.out.println(String.format("Illegal Split Card CMC mode %s passed to getCMC!", mode.toString())); + break; + } + } else { + requestedCMC = getManaCost().getCMC() + xPaid; + } + + return requestedCMC; + } + + public final boolean canBeSacrificedBy(final SpellAbility source) + { + if (isImmutable()) { + System.out.println("Trying to sacrifice immutables: " + this); + return false; + } + if (isPhasedOut()) { + return false; + } + if (source != null && getController().isOpponentOf(source.getActivatingPlayer()) + && getController().hasKeyword("Spells and abilities your opponents control can't cause you to sacrifice permanents.")) { + return false; + } + return true; + } + + CardRules cardRules; + public CardRules getRules() { + return cardRules; + } + public void setRules(CardRules r) { + cardRules = r; + } + + public boolean isCommander() { + // TODO - have a field + return this.isCommander; + } + + public void setCommander(boolean b) { + this.isCommander = b; + } + + public void setSplitStateToPlayAbility(SpellAbility sa) { + if (!isSplitCard()) return; // just in case + // Split card support + for (SpellAbility a : getState(CardCharacteristicName.LeftSplit).getSpellAbility()) { + if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { + setState(CardCharacteristicName.LeftSplit); + return; + } + } + for (SpellAbility a : getState(CardCharacteristicName.RightSplit).getSpellAbility()) { + if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { + setState(CardCharacteristicName.RightSplit); + return; + } + } + if (sa.getSourceCard().hasKeyword("Fuse")) // it's ok that such card won't change its side + return; + + throw new RuntimeException("Not found which part to choose for ability " + sa + " from card " + this); + } + + // Optional costs paid + private final EnumSet costsPaid = EnumSet.noneOf(OptionalCost.class); + public void clearOptionalCostsPaid() { costsPaid.clear(); } + public void addOptionalCostPaid(OptionalCost cost) { costsPaid.add(cost); } + public Iterable getOptionalCostsPaid() { return costsPaid; } + public boolean isOptionalCostPaid(OptionalCost cost) { return costsPaid.contains(cost); } + + /** + * Fetch GameState for this card from references to players who may own or control this card. + */ + @Override + public Game getGame() { + Player controller = getController(); + if (null != controller) + return controller.getGame(); + + Player owner = getOwner(); + if (null != owner) + return owner.getGame(); + + throw new IllegalStateException("Card " + toString() + " has no means to determine the game it belongs to!"); + } + + /** + * TODO: Write javadoc for this method. + * @param card + * @param game TODO + * @param player + * @return + */ + public List getAllPossibleAbilities(Player player, boolean removeUnplayable) { + // this can only be called by the Human + + final List abilities = new ArrayList(); + for (SpellAbility sa : getSpellAbilities()) { + //add alternative costs as additional spell abilities + abilities.add(sa); + abilities.addAll(GameActionUtil.getAlternativeCosts(sa)); + } + + for (int i = abilities.size() - 1; i >= 0; i--) { + SpellAbility sa = abilities.get(i); + sa.setActivatingPlayer(player); + if (removeUnplayable && !sa.canPlay()) { + abilities.remove(i); + } + else if (!sa.isPossible()) { + abilities.remove(i); + } + } + + if (isLand() && player.canPlayLand(this)) { + Ability.PLAY_LAND_SURROGATE.setSourceCard(this); + abilities.add(Ability.PLAY_LAND_SURROGATE); + } + + return abilities; + } + + public static Card fromPaperCard(IPaperCard pc, Player owner) { + return CardFactory.getCard(pc, owner); + } + + private static final Map cp2card = new HashMap(); + public static Card getCardForUi(IPaperCard pc) { + if (pc instanceof PaperCard) { + Card res = cp2card.get(pc); + if (res == null) { + res = fromPaperCard(pc, null); + cp2card.put((PaperCard) pc, res); + } + return res; + } + return fromPaperCard(pc, null); + } + + // Fetch from Forge's Card instance. Well, there should be no errors, but + // we'll still check + public PaperCard getPaperCard() { + final String name = getName(); + final String set = getCurSetCode(); + + if (StringUtils.isNotBlank(set)) { + PaperCard cp = Singletons.getMagicDb().getVariantCards().tryGetCard(name, set); + return cp == null ? Singletons.getMagicDb().getCommonCards().getCard(name, set) : cp; + } + PaperCard cp = Singletons.getMagicDb().getVariantCards().tryGetCard(name, true); + return cp == null ? Singletons.getMagicDb().getCommonCards().getCard(name) : cp; + } + + /** + * Update Card instance for the given PaperCard if any + * @param pc + */ + public static void updateCard(PaperCard pc) { + Card res = cp2card.get(pc); + if (res != null) { + cp2card.put(pc, fromPaperCard(pc, null)); + } + } + + /** return staticCommanderList */ + public List getStaticCommandList() { + return staticCommandList; + } + + /** return staticCommanderList */ + public void addStaticCommandList(Object[] objects) { + this.staticCommandList.add(objects); + } +} // end Card class diff --git a/forge-game/src/main/java/forge/game/card/CardCharacteristics.java b/forge-gui/src/main/java/forge/game/card/CardCharacteristics.java similarity index 100% rename from forge-game/src/main/java/forge/game/card/CardCharacteristics.java rename to forge-gui/src/main/java/forge/game/card/CardCharacteristics.java diff --git a/forge-game/src/main/java/forge/game/card/CardColor.java b/forge-gui/src/main/java/forge/game/card/CardColor.java similarity index 94% rename from forge-game/src/main/java/forge/game/card/CardColor.java rename to forge-gui/src/main/java/forge/game/card/CardColor.java index 3c66ba14a1a..0aeeb190b29 100644 --- a/forge-game/src/main/java/forge/game/card/CardColor.java +++ b/forge-gui/src/main/java/forge/game/card/CardColor.java @@ -1,114 +1,114 @@ -/* - * 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 . - */ -package forge.game.card; - -import forge.card.ColorSet; -import forge.card.mana.ManaCost; -import forge.card.mana.ManaCostParser; - -/** - *

- * Card_Color class. - *

- * - * @author Forge - * @version $Id: CardColor.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class CardColor { - private static long timeStamp = 0; - public static long getTimestamp() { return CardColor.timeStamp; } - static void increaseTimestamp() { CardColor.timeStamp++; } - - // takes care of individual card color, for global color change effects use - // AllZone.getGameInfo().getColorChanges() - private final byte colorMask; - public final byte getColorMask() { return colorMask; } - - private final boolean additional; - public final boolean isAdditional() { - return this.additional; - } - - private long stamp = 0; - - /** - *

- * Getter for the field stamp. - *

- * - * @return a long. - */ - public final long getStamp() { - return this.stamp; - } - - /** - *

- * Constructor for Card_Color. - *

- * - * @param mc - * a {@link forge.game.mana.ManaCostBeingPaid} object. - * @param c - * a {@link forge.game.card.Card} object. - * @param addToColors - * a boolean. - * @param baseColor - * a boolean. - */ - CardColor(final String colors, final boolean addToColors) { - this.additional = addToColors; - ManaCost mc = new ManaCost(new ManaCostParser(colors)); - this.colorMask = mc.getColorProfile(); - this.stamp = CardColor.timeStamp; - } - - public CardColor(byte mask) { - this.colorMask = mask; - this.additional = false; - this.stamp = 0; - } - - - public CardColor() { - this((byte)0); - } - - /** - *

- * equals. - *

- * - * @param cost - * a {@link java.lang.String} object. - * @param c - * a {@link forge.game.card.Card} object. - * @param addToColors - * a boolean. - * @param time - * a long. - * @return a boolean. - */ - public final boolean equals(final String cost, final Card c, final boolean addToColors, final long time) { - return (addToColors == this.additional) && (this.stamp == time); - } - - public final ColorSet toColorSet() { - return ColorSet.fromMask(colorMask); - } -} +/* + * 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 . + */ +package forge.game.card; + +import forge.card.ColorSet; +import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostParser; + +/** + *

+ * Card_Color class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class CardColor { + private static long timeStamp = 0; + public static long getTimestamp() { return CardColor.timeStamp; } + static void increaseTimestamp() { CardColor.timeStamp++; } + + // takes care of individual card color, for global color change effects use + // AllZone.getGameInfo().getColorChanges() + private final byte colorMask; + public final byte getColorMask() { return colorMask; } + + private final boolean additional; + public final boolean isAdditional() { + return this.additional; + } + + private long stamp = 0; + + /** + *

+ * Getter for the field stamp. + *

+ * + * @return a long. + */ + public final long getStamp() { + return this.stamp; + } + + /** + *

+ * Constructor for Card_Color. + *

+ * + * @param mc + * a {@link forge.game.mana.ManaCostBeingPaid} object. + * @param c + * a {@link forge.game.card.Card} object. + * @param addToColors + * a boolean. + * @param baseColor + * a boolean. + */ + CardColor(final String colors, final boolean addToColors) { + this.additional = addToColors; + ManaCost mc = new ManaCost(new ManaCostParser(colors)); + this.colorMask = mc.getColorProfile(); + this.stamp = CardColor.timeStamp; + } + + public CardColor(byte mask) { + this.colorMask = mask; + this.additional = false; + this.stamp = 0; + } + + + public CardColor() { + this((byte)0); + } + + /** + *

+ * equals. + *

+ * + * @param cost + * a {@link java.lang.String} object. + * @param c + * a {@link forge.game.card.Card} object. + * @param addToColors + * a boolean. + * @param time + * a long. + * @return a boolean. + */ + public final boolean equals(final String cost, final Card c, final boolean addToColors, final long time) { + return (addToColors == this.additional) && (this.stamp == time); + } + + public final ColorSet toColorSet() { + return ColorSet.fromMask(colorMask); + } +} diff --git a/forge-game/src/main/java/forge/game/card/CardDamageHistory.java b/forge-gui/src/main/java/forge/game/card/CardDamageHistory.java similarity index 100% rename from forge-game/src/main/java/forge/game/card/CardDamageHistory.java rename to forge-gui/src/main/java/forge/game/card/CardDamageHistory.java diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-gui/src/main/java/forge/game/card/CardFactory.java similarity index 96% rename from forge-game/src/main/java/forge/game/card/CardFactory.java rename to forge-gui/src/main/java/forge/game/card/CardFactory.java index 187b4ab3b8d..0cae6c466d7 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-gui/src/main/java/forge/game/card/CardFactory.java @@ -1,658 +1,658 @@ -/* - * 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 . - */ -package forge.game.card; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map.Entry; - -import forge.ImageCacheBridge; -import forge.card.CardCharacteristicName; -import forge.card.CardRules; -import forge.card.CardSplitType; -import forge.card.ICardFace; -import forge.card.mana.ManaCost; -import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; -import forge.game.ability.effects.CharmEffect; -import forge.game.cost.Cost; -import forge.game.player.Player; -import forge.game.replacement.ReplacementHandler; -import forge.game.spellability.AbilityActivated; -import forge.game.spellability.AbilitySub; -import forge.game.spellability.OptionalCost; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.SpellPermanent; -import forge.game.spellability.TargetRestrictions; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerHandler; -import forge.game.trigger.WrappedAbility; -import forge.item.PaperCard; -import forge.item.IPaperCard; - -/** - *

- * AbstractCardFactory class. - *

- * - * TODO The map field contains Card instances that have not gone through - * getCard2, and thus lack abilities. However, when a new Card is requested via - * getCard, it is this map's values that serve as the templates for the values - * it returns. This class has another field, allCards, which is another copy of - * the card database. These cards have abilities attached to them, and are owned - * by the human player by default. It would be better memory-wise if we had - * only one or the other. We may experiment in the future with using - * allCard-type values for the map instead of the less complete ones that exist - * there today. - * - * @author Forge - * @version $Id: CardFactory.java 24008 2013-12-21 03:16:16Z swordshine $ - */ -public class CardFactory { - /** - *

- * copyCard. - *

- * - * @param in - * a {@link forge.game.card.Card} object. - * @return a {@link forge.game.card.Card} object. - */ - public final static Card copyCard(final Card in, boolean assignNewId) { - final CardCharacteristicName curState = in.getCurState(); - boolean alternate = false; - if (in.isInAlternateState()) { - alternate = true; - in.setState(CardCharacteristicName.Original); - } - Card out = null; - if (!in.isToken() || in.isCopiedToken()) { - out = assignNewId ? getCard(in.getPaperCard(), in.getOwner()) - : getCard(in.getPaperCard(), in.getOwner(), in.getUniqueNumber()); - } else { // token - out = assignNewId ? new Card(in.getGame().nextCardId()) : new Card(in.getUniqueNumber()); - out = CardFactory.copyStats(in, in.getController()); - - out.setName(in.getName()); - out.setImageKey(in.getImageKey()); - out.setManaCost(in.getManaCost()); - out.setColor(in.getColor()); - out.setType(in.getType()); - out.setBaseAttack(in.getBaseAttack()); - out.setBaseDefense(in.getBaseDefense()); - out.setToken(true); - - CardFactoryUtil.addAbilityFactoryAbilities(out); - for (String s : out.getStaticAbilityStrings()) { - out.addStaticAbility(s); - } - } - - CardFactory.copyCharacteristics(in, out); - if (in.hasAlternateState()) { - for (final CardCharacteristicName state : in.getStates()) { - in.setState(state); - if (state == CardCharacteristicName.Cloner) { - out.addAlternateState(state); - } - out.setState(state); - CardFactory.copyCharacteristics(in, out); - } - } - if (alternate) { - in.setState(curState); - } - out.setState(curState); - - // I'm not sure if we really should be copying enchant/equip stuff over. - out.setEquipping(in.getEquipping()); - out.setEquippedBy(in.getEquippedBy()); - out.setFortifying(in.getFortifying()); - out.setFortifiedBy(in.getFortifiedBy()); - out.setEnchantedBy(in.getEnchantedBy()); - out.setEnchanting(in.getEnchanting()); - out.setClones(in.getClones()); - out.setZone(in.getZone()); - for (final Object o : in.getRemembered()) { - out.addRemembered(o); - } - for (final Card o : in.getImprinted()) { - out.addImprinted(o); - } - out.setCommander(in.isCommander()); - /* - if(out.isCommander()) - { - out.addStaticAbility("Mode$ RaiseCost | Amount$ CommanderCostRaise | Type$ Spell | ValidCard$ Card.Self+wasCastFromCommand | EffectZone$ All | AffectedZone$ Stack"); - SpellAbility sa = AbilityFactory.getAbility( - "SP$ PermanentCreature | SorcerySpeed$ True | ActivationZone$ Command | SubAbility$ DBCommanderIncCast | Cost$ " + out.getManaCost().toString(), - out); - - out.addSpellAbility(sa); - } - */ - return out; - - } - - /** - *

- * copySpellontoStack. - *

- * - * @param source - * a {@link forge.game.card.Card} object. - * @param original - * a {@link forge.game.card.Card} object. - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @param bCopyDetails - * a boolean. - */ - public final static SpellAbility copySpellAbilityAndSrcCard(final Card source, final Card original, final SpellAbility sa, final boolean bCopyDetails) { - //Player originalController = original.getController(); - Player controller = sa.getActivatingPlayer(); - final Card c = copyCard(original, true); - - // change the color of the copy (eg: Fork) - final SpellAbility sourceSA = source.getFirstSpellAbility(); - if (null != sourceSA && sourceSA.hasParam("CopyIsColor")) { - String tmp = ""; - final String newColor = sourceSA.getParam("CopyIsColor"); - if (newColor.equals("ChosenColor")) { - tmp = CardUtil.getShortColorsString(source.getChosenColor()); - } else { - tmp = CardUtil.getShortColorsString(new ArrayList(Arrays.asList(newColor.split(",")))); - } - final String finalColors = tmp; - - c.addColor(finalColors, !sourceSA.hasParam("OverwriteColors"), true); - } - - c.clearControllers(); - c.setOwner(controller); - c.setCopiedSpell(true); - - final SpellAbility copySA; - if(sa instanceof AbilityActivated) - { - copySA = ((AbilityActivated)sa).getCopy(); - copySA.setSourceCard(original); - } - else if (sa.isTrigger()) { - copySA = getCopiedTriggeredAbility(sa); - } - else - { - copySA = sa.copy(); - copySA.setSourceCard(c); - SpellAbility parentSA = copySA; - SpellAbility subSA = copySA.getSubAbility(); - while (subSA != null) { - AbilitySub copySubSA = ((AbilitySub) subSA).getCopy(); - parentSA.setSubAbility(copySubSA); - copySubSA.setParent(parentSA); - copySubSA.setSourceCard(c); - copySubSA.setCopied(true); - parentSA = copySubSA; - subSA = copySubSA.getSubAbility(); - } - } - copySA.setCopied(true); - //remove all costs - if (!copySA.isTrigger()) { - copySA.setPayCosts(new Cost("", sa.isAbility())); - } - if (sa.getTargetRestrictions() != null) { - TargetRestrictions target = new TargetRestrictions(sa.getTargetRestrictions()); - copySA.setTargetRestrictions(target); - } - copySA.setActivatingPlayer(controller); - - if (bCopyDetails) { - c.addXManaCostPaid(original.getXManaCostPaid()); - c.setKickerMagnitude(original.getKickerMagnitude()); - - for (OptionalCost cost : original.getOptionalCostsPaid()) { - c.addOptionalCostPaid(cost); - } - copySA.setPaidHash(sa.getPaidHash()); - } - return copySA; - } - - /** - *

- * getCard. - *

- * - * @param cardName - * a {@link java.lang.String} object. - * @param owner - * a {@link forge.game.player.Player} object. - * @return a {@link forge.game.card.Card} instance, owned by owner; or the special - * blankCard - */ - - public final static Card getCard(final IPaperCard cp, final Player owner) { - return getCard(cp, owner, owner == null ? 0 : owner.getGame().nextCardId()); - } - public final static Card getCard(final IPaperCard cp, final Player owner, final int cardId) { - //System.out.println(cardName); - CardRules cardRules = cp.getRules(); - final Card c = readCard(cardRules, cardId); - c.setRules(cardRules); - c.setOwner(owner); - buildAbilities(c); - - c.setCurSetCode(cp.getEdition()); - c.setRarity(cp.getRarity()); - - // Would like to move this away from in-game entities - String originalPicture = ImageCacheBridge.instance.getImageKey(cp, false); - //System.out.println(c.getName() + " -> " + originalPicture); - c.setImageKey(originalPicture); - c.setToken(cp.isToken()); - - if (c.hasAlternateState()) { - if (c.isFlipCard()) { - c.setState(CardCharacteristicName.Flipped); - c.setImageKey(ImageCacheBridge.instance.getImageKey(cp, true)); - } - else if (c.isDoubleFaced() && cp instanceof PaperCard) { - c.setState(CardCharacteristicName.Transformed); - c.setImageKey(ImageCacheBridge.instance.getImageKey(cp, true)); - } - else if (c.isSplitCard()) { - c.setState(CardCharacteristicName.LeftSplit); - c.setImageKey(originalPicture); - c.setCurSetCode(cp.getEdition()); - c.setRarity(cp.getRarity()); - c.setState(CardCharacteristicName.RightSplit); - c.setImageKey(originalPicture); - } - - c.setCurSetCode(cp.getEdition()); - c.setRarity(cp.getRarity()); - c.setState(CardCharacteristicName.Original); - } - - return c; - } - - private static void buildAbilities(final Card card) { - final String cardName = card.getName(); - - // may have to change the spell - - // this is the "default" spell for permanents like creatures and artifacts - if (card.isPermanent() && !card.isAura() && !card.isLand()) { - card.addSpellAbility(new SpellPermanent(card)); - } - - CardFactoryUtil.parseKeywords(card, cardName); - - for (final CardCharacteristicName state : card.getStates()) { - if (card.isDoubleFaced() && state == CardCharacteristicName.FaceDown) { - continue; // Ignore FaceDown for DFC since they have none. - } - card.setState(state); - CardFactoryUtil.addAbilityFactoryAbilities(card); - for (String stAb : card.getStaticAbilityStrings()) { - card.addStaticAbility(stAb); - } - - - if ( state == CardCharacteristicName.LeftSplit || state == CardCharacteristicName.RightSplit ) - { - CardCharacteristics original = card.getState(CardCharacteristicName.Original); - original.getSpellAbility().addAll(card.getCharacteristics().getSpellAbility()); - original.getIntrinsicKeyword().addAll(card.getIntrinsicKeyword()); // Copy 'Fuse' to original side - original.getSVars().putAll(card.getCharacteristics().getSVars()); // Unfortunately need to copy these to (Effect looks for sVars on execute) - } - } - - card.setState(CardCharacteristicName.Original); - - // ****************************************************************** - // ************** Link to different CardFactories ******************* - - if (card.isPlaneswalker()) { - buildPlaneswalkerAbilities(card); - } else if (card.isType("Plane")) { - buildPlaneAbilities(card); - } - - CardFactoryUtil.setupKeywordedAbilities(card); - } // getCard2 - - private static void buildPlaneAbilities(Card card) { - StringBuilder triggerSB = new StringBuilder(); - triggerSB.append("Mode$ PlanarDice | Result$ Planeswalk | TriggerZones$ Command | Execute$ RolledWalk | "); - triggerSB.append("Secondary$ True | TriggerDescription$ Whenever you roll Planeswalk, put this card on the "); - triggerSB.append("bottom of its owner's planar deck face down, then move the top card of your planar deck off "); - triggerSB.append("that planar deck and turn it face up"); - - StringBuilder saSB = new StringBuilder(); - saSB.append("AB$ RollPlanarDice | Cost$ X | References$ X | SorcerySpeed$ True | AnyPlayer$ True | ActivationZone$ Command | "); - saSB.append("SpellDescription$ Roll the planar dice. X is equal to the amount of times the planar die has been rolled this turn."); - - card.setSVar("RolledWalk", "DB$ Planeswalk | Cost$ 0"); - Trigger planesWalkTrigger = TriggerHandler.parseTrigger(triggerSB.toString(), card, true); - card.addTrigger(planesWalkTrigger); - - card.setSVar("X", "Count$RolledThisTurn"); - SpellAbility planarRoll = AbilityFactory.getAbility(saSB.toString(), card); - card.addSpellAbility(planarRoll); - } - - private static void buildPlaneswalkerAbilities(Card card) { - if (card.getBaseLoyalty() > 0) { - final String loyalty = Integer.toString(card.getBaseLoyalty()); - card.addIntrinsicKeyword("etbCounter:LOYALTY:" + loyalty + ":no Condition:no desc"); - } - - //Planeswalker damage redirection - card.addReplacementEffect(ReplacementHandler.parseReplacement("Event$ DamageDone | ActiveZones$ Battlefield | IsCombat$ False | ValidSource$ Card.YouDontCtrl" - + " | ValidTarget$ You | Optional$ True | OptionalDecider$ Opponent | ReplaceWith$ DamagePW | Secondary$ True" - + " | AICheckSVar$ DamagePWAI | AISVarCompare$ GT4 | Description$ Redirect damage to " + card.toString(), card, true)); - card.setSVar("DamagePW", "AB$DealDamage | Cost$ 0 | Defined$ Self | NumDmg$ DamagePWX | DamageSource$ ReplacedSource | References$ DamagePWX,DamagePWAI"); - card.setSVar("DamagePWX", "ReplaceCount$DamageAmount"); - card.setSVar("DamagePWAI", "ReplaceCount$DamageAmount/NMinus.DamagePWY"); - card.setSVar("DamagePWY", "Count$YourLifeTotal"); - } - - private static Card readCard(final CardRules rules, int cardId) { - - final Card card = new Card(cardId); - - // 1. The states we may have: - CardSplitType st = rules.getSplitType(); - if ( st == CardSplitType.Split) { - card.addAlternateState(CardCharacteristicName.LeftSplit); - card.setState(CardCharacteristicName.LeftSplit); - } - - readCardFace(card, rules.getMainPart()); - - if ( st != CardSplitType.None) { - card.addAlternateState(st.getChangedStateName()); - card.setState(st.getChangedStateName()); - readCardFace(card, rules.getOtherPart()); - } - - if (card.isInAlternateState()) { - card.setState(CardCharacteristicName.Original); - } - - if ( st == CardSplitType.Split ) { - card.setName(rules.getName()); - - // Combined mana cost - ManaCost combinedManaCost = ManaCost.combine(rules.getMainPart().getManaCost(), rules.getOtherPart().getManaCost()); - card.setManaCost(combinedManaCost); - - // Combined card color - int combinedColor = rules.getMainPart().getColor().getColor() | rules.getOtherPart().getColor().getColor(); - CardColor combinedCardColor = new CardColor((byte)combinedColor); - ArrayList combinedCardColorArr = new ArrayList(); - combinedCardColorArr.add(combinedCardColor); - card.setColor(combinedCardColorArr); - - // Super and 'middle' types should use enums. - List coreTypes = rules.getType().getTypesBeforeDash(); - coreTypes.addAll(rules.getType().getSubTypes()); - card.setType(coreTypes); - - // Combined text based on Oracle text - might not be necessary, temporarily disabled. - //String combinedText = String.format("%s: %s\n%s: %s", rules.getMainPart().getName(), rules.getMainPart().getOracleText(), rules.getOtherPart().getName(), rules.getOtherPart().getOracleText()); - //card.setText(combinedText); - } - - return card; - } - - private static void readCardFace(Card c, ICardFace face) { - for(String a : face.getAbilities()) c.addIntrinsicAbility(a); - for(String k : face.getKeywords()) c.addIntrinsicKeyword(k); - for(String r : face.getReplacements()) c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true)); - for(String s : face.getStaticAbilities()) c.addStaticAbilityString(s); - for(String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true)); - for(Entry v : face.getVariables()) c.setSVar(v.getKey(), v.getValue()); - - c.setName(face.getName()); - c.setManaCost(face.getManaCost()); - c.setText(face.getNonAbilityText()); - if( face.getInitialLoyalty() > 0 ) c.setBaseLoyalty(face.getInitialLoyalty()); - - // Super and 'middle' types should use enums. - List coreTypes = face.getType().getTypesBeforeDash(); - coreTypes.addAll(face.getType().getSubTypes()); - c.setType(coreTypes); - - // What a perverted color code we have! - CardColor col1 = new CardColor(face.getColor().getColor()); - ArrayList ccc = new ArrayList(); - ccc.add(col1); - c.setColor(ccc); - - if ( face.getIntPower() >= 0 ) { - c.setBaseAttack(face.getIntPower()); - c.setBaseAttackString(face.getPower()); - } - if ( face.getIntToughness() >= 0 ) { - c.setBaseDefense(face.getIntToughness()); - c.setBaseDefenseString(face.getToughness()); - } - } - - /** - *

- * Copies stats like power, toughness, etc. - *

- * - * @param sim - * a {@link java.lang.Object} object. - * @param newOwner - * @return a {@link forge.game.card.Card} object. - */ - public static Card copyStats(final Card sim, Player newOwner) { - int id = newOwner == null ? 0 : newOwner.getGame().nextCardId(); - final Card c = new Card(id); - - c.setOwner(newOwner); - c.setCurSetCode(sim.getCurSetCode()); - - final CardCharacteristicName origState = sim.getCurState(); - for (final CardCharacteristicName state : sim.getStates()) { - c.addAlternateState(state); - c.setState(state); - sim.setState(state); - CardFactory.copyCharacteristics(sim, c); - } - - sim.setState(origState); - c.setState(origState); - c.setRules(sim.getRules()); - - return c; - } // copyStats() - - /** - * Copy characteristics. - * - * @param from - * the from - * @param to - * the to - */ - private static void copyCharacteristics(final Card from, final Card to) { - to.setBaseAttack(from.getBaseAttack()); - to.setBaseDefense(from.getBaseDefense()); - to.setBaseLoyalty(from.getBaseLoyalty()); - to.setBaseAttackString(from.getBaseAttackString()); - to.setBaseDefenseString(from.getBaseDefenseString()); - to.setIntrinsicKeyword(from.getIntrinsicKeyword()); - to.setName(from.getName()); - to.setType(from.getCharacteristics().getType()); - to.setText(from.getSpellText()); - to.setManaCost(from.getManaCost()); - to.setColor(from.getColor()); - to.setSVars(from.getSVars()); - to.setIntrinsicAbilities(from.getUnparsedAbilities()); - - to.setImageKey(from.getImageKey()); - to.setTriggers(from.getTriggers(), true); - to.setReplacementEffects(from.getReplacementEffects()); - to.setStaticAbilityStrings(from.getStaticAbilityStrings()); - - } - - /** - * Copy characteristics. - * - * @param from - * the from - * @param stateToCopy - * the state to copy - * @param to - * the to - */ - public static void copyState(final Card from, final CardCharacteristicName stateToCopy, final Card to) { - - // copy characteristics not associated with a state - to.setBaseLoyalty(from.getBaseLoyalty()); - to.setBaseAttackString(from.getBaseAttackString()); - to.setBaseDefenseString(from.getBaseDefenseString()); - to.setText(from.getSpellText()); - - // get CardCharacteristics for desired state - CardCharacteristics characteristics = from.getState(stateToCopy); - to.getCharacteristics().copyFrom(characteristics); - // handle triggers and replacement effect through Card class interface - to.setTriggers(characteristics.getTriggers(), true); - to.setReplacementEffects(characteristics.getReplacementEffects()); - } - - public static void copySpellAbility(SpellAbility from, SpellAbility to) { - to.setDescription(from.getDescription()); - to.setStackDescription(from.getDescription()); - - if (from.getSubAbility() != null) { - to.setSubAbility(from.getSubAbility().getCopy()); - } - if (from.getRestrictions() != null) { - to.setRestrictions(from.getRestrictions()); - } - if (from.getConditions() != null) { - to.setConditions(from.getConditions()); - } - - for (String sVar : from.getSVars()) { - to.setSVar(sVar, from.getSVar(sVar)); - } - } - - public static List makeToken(final String name, final String imageName, final Player controller, - final String manaCost, final String[] types, final int baseAttack, final int baseDefense, - final String[] intrinsicKeywords) { - final List list = new ArrayList(); - final Card c = new Card(controller.getGame().nextCardId()); - c.setName(name); - c.setImageKey(ImageCacheBridge.instance.getTokenKey(imageName)); - - // TODO - most tokens mana cost is 0, this needs to be fixed - // c.setManaCost(manaCost); - c.addColor(manaCost); - c.setToken(true); - - for (final String t : types) { - c.addType(t); - } - - c.setBaseAttack(baseAttack); - c.setBaseDefense(baseDefense); - - final int multiplier = controller.getTokenDoublersMagnitude(); - for (int i = 0; i < multiplier; i++) { - Card temp = copyStats(c, controller); - - for (final String kw : intrinsicKeywords) { - temp.addIntrinsicKeyword(kw); - } - temp.setOwner(controller); - temp.setToken(true); - CardFactoryUtil.parseKeywords(temp, temp.getName()); - CardFactoryUtil.setupKeywordedAbilities(temp); - list.add(temp); - } - return list; - } - /** - * Copy triggered ability - * - * return a wrapped ability - */ - public static SpellAbility getCopiedTriggeredAbility(final SpellAbility sa) { - if (!sa.isTrigger()) { - return null; - } - // Find trigger - Trigger t = null; - if (sa.isWrapper()) { - // copy trigger? - t = ((WrappedAbility) sa).getTrigger(); - } else { // some keyword ability, e.g. Exalted, Annihilator - return sa.copy(); - } - // set up copied wrapped ability - SpellAbility trig = t.getOverridingAbility(); - if (trig == null) { - trig = AbilityFactory.getAbility(sa.getSourceCard().getSVar(t.getMapParams().get("Execute")), sa.getSourceCard()); - } - trig.setSourceCard(sa.getSourceCard()); - trig.setTrigger(true); - trig.setSourceTrigger(t.getId()); - t.setTriggeringObjects(trig); - if (t.getStoredTriggeredObjects() != null) { - trig.setAllTriggeringObjects(t.getStoredTriggeredObjects()); - } - - trig.setActivatingPlayer(sa.getActivatingPlayer()); - if (t.getMapParams().containsKey("TriggerController")) { - Player p = AbilityUtils.getDefinedPlayers(t.getHostCard(), t.getMapParams().get("TriggerController"), trig).get(0); - trig.setActivatingPlayer(p); - } - - trig.setStackDescription(trig.toString()); - if (trig.getApi() == ApiType.Charm && !trig.isWrapper()) { - CharmEffect.makeChoices(trig); - } - - WrappedAbility wrapperAbility = new WrappedAbility(t, trig, ((WrappedAbility) sa).getDecider()); - wrapperAbility.setTrigger(true); - wrapperAbility.setMandatory(((WrappedAbility) sa).isMandatory()); - wrapperAbility.setDescription(wrapperAbility.getStackDescription()); - t.setTriggeredSA(wrapperAbility); - return wrapperAbility; - } - - -} // end class AbstractCardFactory +/* + * 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 . + */ +package forge.game.card; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map.Entry; + +import forge.ImageCache; +import forge.card.CardCharacteristicName; +import forge.card.CardRules; +import forge.card.CardSplitType; +import forge.card.ICardFace; +import forge.card.mana.ManaCost; +import forge.game.ability.AbilityFactory; +import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; +import forge.game.ability.effects.CharmEffect; +import forge.game.cost.Cost; +import forge.game.player.Player; +import forge.game.replacement.ReplacementHandler; +import forge.game.spellability.AbilityActivated; +import forge.game.spellability.AbilitySub; +import forge.game.spellability.OptionalCost; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellPermanent; +import forge.game.spellability.TargetRestrictions; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerHandler; +import forge.game.trigger.WrappedAbility; +import forge.item.PaperCard; +import forge.item.IPaperCard; + +/** + *

+ * AbstractCardFactory class. + *

+ * + * TODO The map field contains Card instances that have not gone through + * getCard2, and thus lack abilities. However, when a new Card is requested via + * getCard, it is this map's values that serve as the templates for the values + * it returns. This class has another field, allCards, which is another copy of + * the card database. These cards have abilities attached to them, and are owned + * by the human player by default. It would be better memory-wise if we had + * only one or the other. We may experiment in the future with using + * allCard-type values for the map instead of the less complete ones that exist + * there today. + * + * @author Forge + * @version $Id$ + */ +public class CardFactory { + /** + *

+ * copyCard. + *

+ * + * @param in + * a {@link forge.game.card.Card} object. + * @return a {@link forge.game.card.Card} object. + */ + public final static Card copyCard(final Card in, boolean assignNewId) { + final CardCharacteristicName curState = in.getCurState(); + boolean alternate = false; + if (in.isInAlternateState()) { + alternate = true; + in.setState(CardCharacteristicName.Original); + } + Card out = null; + if (!in.isToken() || in.isCopiedToken()) { + out = assignNewId ? getCard(in.getPaperCard(), in.getOwner()) + : getCard(in.getPaperCard(), in.getOwner(), in.getUniqueNumber()); + } else { // token + out = assignNewId ? new Card(in.getGame().nextCardId()) : new Card(in.getUniqueNumber()); + out = CardFactory.copyStats(in, in.getController()); + + out.setName(in.getName()); + out.setImageKey(in.getImageKey()); + out.setManaCost(in.getManaCost()); + out.setColor(in.getColor()); + out.setType(in.getType()); + out.setBaseAttack(in.getBaseAttack()); + out.setBaseDefense(in.getBaseDefense()); + out.setToken(true); + + CardFactoryUtil.addAbilityFactoryAbilities(out); + for (String s : out.getStaticAbilityStrings()) { + out.addStaticAbility(s); + } + } + + CardFactory.copyCharacteristics(in, out); + if (in.hasAlternateState()) { + for (final CardCharacteristicName state : in.getStates()) { + in.setState(state); + if (state == CardCharacteristicName.Cloner) { + out.addAlternateState(state); + } + out.setState(state); + CardFactory.copyCharacteristics(in, out); + } + } + if (alternate) { + in.setState(curState); + } + out.setState(curState); + + // I'm not sure if we really should be copying enchant/equip stuff over. + out.setEquipping(in.getEquipping()); + out.setEquippedBy(in.getEquippedBy()); + out.setFortifying(in.getFortifying()); + out.setFortifiedBy(in.getFortifiedBy()); + out.setEnchantedBy(in.getEnchantedBy()); + out.setEnchanting(in.getEnchanting()); + out.setClones(in.getClones()); + out.setZone(in.getZone()); + for (final Object o : in.getRemembered()) { + out.addRemembered(o); + } + for (final Card o : in.getImprinted()) { + out.addImprinted(o); + } + out.setCommander(in.isCommander()); + /* + if(out.isCommander()) + { + out.addStaticAbility("Mode$ RaiseCost | Amount$ CommanderCostRaise | Type$ Spell | ValidCard$ Card.Self+wasCastFromCommand | EffectZone$ All | AffectedZone$ Stack"); + SpellAbility sa = AbilityFactory.getAbility( + "SP$ PermanentCreature | SorcerySpeed$ True | ActivationZone$ Command | SubAbility$ DBCommanderIncCast | Cost$ " + out.getManaCost().toString(), + out); + + out.addSpellAbility(sa); + } + */ + return out; + + } + + /** + *

+ * copySpellontoStack. + *

+ * + * @param source + * a {@link forge.game.card.Card} object. + * @param original + * a {@link forge.game.card.Card} object. + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @param bCopyDetails + * a boolean. + */ + public final static SpellAbility copySpellAbilityAndSrcCard(final Card source, final Card original, final SpellAbility sa, final boolean bCopyDetails) { + //Player originalController = original.getController(); + Player controller = sa.getActivatingPlayer(); + final Card c = copyCard(original, true); + + // change the color of the copy (eg: Fork) + final SpellAbility sourceSA = source.getFirstSpellAbility(); + if (null != sourceSA && sourceSA.hasParam("CopyIsColor")) { + String tmp = ""; + final String newColor = sourceSA.getParam("CopyIsColor"); + if (newColor.equals("ChosenColor")) { + tmp = CardUtil.getShortColorsString(source.getChosenColor()); + } else { + tmp = CardUtil.getShortColorsString(new ArrayList(Arrays.asList(newColor.split(",")))); + } + final String finalColors = tmp; + + c.addColor(finalColors, !sourceSA.hasParam("OverwriteColors"), true); + } + + c.clearControllers(); + c.setOwner(controller); + c.setCopiedSpell(true); + + final SpellAbility copySA; + if(sa instanceof AbilityActivated) + { + copySA = ((AbilityActivated)sa).getCopy(); + copySA.setSourceCard(original); + } + else if (sa.isTrigger()) { + copySA = getCopiedTriggeredAbility(sa); + } + else + { + copySA = sa.copy(); + copySA.setSourceCard(c); + SpellAbility parentSA = copySA; + SpellAbility subSA = copySA.getSubAbility(); + while (subSA != null) { + AbilitySub copySubSA = ((AbilitySub) subSA).getCopy(); + parentSA.setSubAbility(copySubSA); + copySubSA.setParent(parentSA); + copySubSA.setSourceCard(c); + copySubSA.setCopied(true); + parentSA = copySubSA; + subSA = copySubSA.getSubAbility(); + } + } + copySA.setCopied(true); + //remove all costs + if (!copySA.isTrigger()) { + copySA.setPayCosts(new Cost("", sa.isAbility())); + } + if (sa.getTargetRestrictions() != null) { + TargetRestrictions target = new TargetRestrictions(sa.getTargetRestrictions()); + copySA.setTargetRestrictions(target); + } + copySA.setActivatingPlayer(controller); + + if (bCopyDetails) { + c.addXManaCostPaid(original.getXManaCostPaid()); + c.setKickerMagnitude(original.getKickerMagnitude()); + + for (OptionalCost cost : original.getOptionalCostsPaid()) { + c.addOptionalCostPaid(cost); + } + copySA.setPaidHash(sa.getPaidHash()); + } + return copySA; + } + + /** + *

+ * getCard. + *

+ * + * @param cardName + * a {@link java.lang.String} object. + * @param owner + * a {@link forge.game.player.Player} object. + * @return a {@link forge.game.card.Card} instance, owned by owner; or the special + * blankCard + */ + + public final static Card getCard(final IPaperCard cp, final Player owner) { + return getCard(cp, owner, owner == null ? 0 : owner.getGame().nextCardId()); + } + public final static Card getCard(final IPaperCard cp, final Player owner, final int cardId) { + //System.out.println(cardName); + CardRules cardRules = cp.getRules(); + final Card c = readCard(cardRules, cardId); + c.setRules(cardRules); + c.setOwner(owner); + buildAbilities(c); + + c.setCurSetCode(cp.getEdition()); + c.setRarity(cp.getRarity()); + + // Would like to move this away from in-game entities + String originalPicture = ImageCache.getImageKey(cp, false); + //System.out.println(c.getName() + " -> " + originalPicture); + c.setImageKey(originalPicture); + c.setToken(cp.isToken()); + + if (c.hasAlternateState()) { + if (c.isFlipCard()) { + c.setState(CardCharacteristicName.Flipped); + c.setImageKey(ImageCache.getImageKey(cp, true)); + } + else if (c.isDoubleFaced() && cp instanceof PaperCard) { + c.setState(CardCharacteristicName.Transformed); + c.setImageKey(ImageCache.getImageKey(cp, true)); + } + else if (c.isSplitCard()) { + c.setState(CardCharacteristicName.LeftSplit); + c.setImageKey(originalPicture); + c.setCurSetCode(cp.getEdition()); + c.setRarity(cp.getRarity()); + c.setState(CardCharacteristicName.RightSplit); + c.setImageKey(originalPicture); + } + + c.setCurSetCode(cp.getEdition()); + c.setRarity(cp.getRarity()); + c.setState(CardCharacteristicName.Original); + } + + return c; + } + + private static void buildAbilities(final Card card) { + final String cardName = card.getName(); + + // may have to change the spell + + // this is the "default" spell for permanents like creatures and artifacts + if (card.isPermanent() && !card.isAura() && !card.isLand()) { + card.addSpellAbility(new SpellPermanent(card)); + } + + CardFactoryUtil.parseKeywords(card, cardName); + + for (final CardCharacteristicName state : card.getStates()) { + if (card.isDoubleFaced() && state == CardCharacteristicName.FaceDown) { + continue; // Ignore FaceDown for DFC since they have none. + } + card.setState(state); + CardFactoryUtil.addAbilityFactoryAbilities(card); + for (String stAb : card.getStaticAbilityStrings()) { + card.addStaticAbility(stAb); + } + + + if ( state == CardCharacteristicName.LeftSplit || state == CardCharacteristicName.RightSplit ) + { + CardCharacteristics original = card.getState(CardCharacteristicName.Original); + original.getSpellAbility().addAll(card.getCharacteristics().getSpellAbility()); + original.getIntrinsicKeyword().addAll(card.getIntrinsicKeyword()); // Copy 'Fuse' to original side + original.getSVars().putAll(card.getCharacteristics().getSVars()); // Unfortunately need to copy these to (Effect looks for sVars on execute) + } + } + + card.setState(CardCharacteristicName.Original); + + // ****************************************************************** + // ************** Link to different CardFactories ******************* + + if (card.isPlaneswalker()) { + buildPlaneswalkerAbilities(card); + } else if (card.isType("Plane")) { + buildPlaneAbilities(card); + } + + CardFactoryUtil.setupKeywordedAbilities(card); + } // getCard2 + + private static void buildPlaneAbilities(Card card) { + StringBuilder triggerSB = new StringBuilder(); + triggerSB.append("Mode$ PlanarDice | Result$ Planeswalk | TriggerZones$ Command | Execute$ RolledWalk | "); + triggerSB.append("Secondary$ True | TriggerDescription$ Whenever you roll Planeswalk, put this card on the "); + triggerSB.append("bottom of its owner's planar deck face down, then move the top card of your planar deck off "); + triggerSB.append("that planar deck and turn it face up"); + + StringBuilder saSB = new StringBuilder(); + saSB.append("AB$ RollPlanarDice | Cost$ X | References$ X | SorcerySpeed$ True | AnyPlayer$ True | ActivationZone$ Command | "); + saSB.append("SpellDescription$ Roll the planar dice. X is equal to the amount of times the planar die has been rolled this turn."); + + card.setSVar("RolledWalk", "DB$ Planeswalk | Cost$ 0"); + Trigger planesWalkTrigger = TriggerHandler.parseTrigger(triggerSB.toString(), card, true); + card.addTrigger(planesWalkTrigger); + + card.setSVar("X", "Count$RolledThisTurn"); + SpellAbility planarRoll = AbilityFactory.getAbility(saSB.toString(), card); + card.addSpellAbility(planarRoll); + } + + private static void buildPlaneswalkerAbilities(Card card) { + if (card.getBaseLoyalty() > 0) { + final String loyalty = Integer.toString(card.getBaseLoyalty()); + card.addIntrinsicKeyword("etbCounter:LOYALTY:" + loyalty + ":no Condition:no desc"); + } + + //Planeswalker damage redirection + card.addReplacementEffect(ReplacementHandler.parseReplacement("Event$ DamageDone | ActiveZones$ Battlefield | IsCombat$ False | ValidSource$ Card.YouDontCtrl" + + " | ValidTarget$ You | Optional$ True | OptionalDecider$ Opponent | ReplaceWith$ DamagePW | Secondary$ True" + + " | AICheckSVar$ DamagePWAI | AISVarCompare$ GT4 | Description$ Redirect damage to " + card.toString(), card, true)); + card.setSVar("DamagePW", "AB$DealDamage | Cost$ 0 | Defined$ Self | NumDmg$ DamagePWX | DamageSource$ ReplacedSource | References$ DamagePWX,DamagePWAI"); + card.setSVar("DamagePWX", "ReplaceCount$DamageAmount"); + card.setSVar("DamagePWAI", "ReplaceCount$DamageAmount/NMinus.DamagePWY"); + card.setSVar("DamagePWY", "Count$YourLifeTotal"); + } + + private static Card readCard(final CardRules rules, int cardId) { + + final Card card = new Card(cardId); + + // 1. The states we may have: + CardSplitType st = rules.getSplitType(); + if ( st == CardSplitType.Split) { + card.addAlternateState(CardCharacteristicName.LeftSplit); + card.setState(CardCharacteristicName.LeftSplit); + } + + readCardFace(card, rules.getMainPart()); + + if ( st != CardSplitType.None) { + card.addAlternateState(st.getChangedStateName()); + card.setState(st.getChangedStateName()); + readCardFace(card, rules.getOtherPart()); + } + + if (card.isInAlternateState()) { + card.setState(CardCharacteristicName.Original); + } + + if ( st == CardSplitType.Split ) { + card.setName(rules.getName()); + + // Combined mana cost + ManaCost combinedManaCost = ManaCost.combine(rules.getMainPart().getManaCost(), rules.getOtherPart().getManaCost()); + card.setManaCost(combinedManaCost); + + // Combined card color + int combinedColor = rules.getMainPart().getColor().getColor() | rules.getOtherPart().getColor().getColor(); + CardColor combinedCardColor = new CardColor((byte)combinedColor); + ArrayList combinedCardColorArr = new ArrayList(); + combinedCardColorArr.add(combinedCardColor); + card.setColor(combinedCardColorArr); + + // Super and 'middle' types should use enums. + List coreTypes = rules.getType().getTypesBeforeDash(); + coreTypes.addAll(rules.getType().getSubTypes()); + card.setType(coreTypes); + + // Combined text based on Oracle text - might not be necessary, temporarily disabled. + //String combinedText = String.format("%s: %s\n%s: %s", rules.getMainPart().getName(), rules.getMainPart().getOracleText(), rules.getOtherPart().getName(), rules.getOtherPart().getOracleText()); + //card.setText(combinedText); + } + + return card; + } + + private static void readCardFace(Card c, ICardFace face) { + for(String a : face.getAbilities()) c.addIntrinsicAbility(a); + for(String k : face.getKeywords()) c.addIntrinsicKeyword(k); + for(String r : face.getReplacements()) c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true)); + for(String s : face.getStaticAbilities()) c.addStaticAbilityString(s); + for(String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true)); + for(Entry v : face.getVariables()) c.setSVar(v.getKey(), v.getValue()); + + c.setName(face.getName()); + c.setManaCost(face.getManaCost()); + c.setText(face.getNonAbilityText()); + if( face.getInitialLoyalty() > 0 ) c.setBaseLoyalty(face.getInitialLoyalty()); + + // Super and 'middle' types should use enums. + List coreTypes = face.getType().getTypesBeforeDash(); + coreTypes.addAll(face.getType().getSubTypes()); + c.setType(coreTypes); + + // What a perverted color code we have! + CardColor col1 = new CardColor(face.getColor().getColor()); + ArrayList ccc = new ArrayList(); + ccc.add(col1); + c.setColor(ccc); + + if ( face.getIntPower() >= 0 ) { + c.setBaseAttack(face.getIntPower()); + c.setBaseAttackString(face.getPower()); + } + if ( face.getIntToughness() >= 0 ) { + c.setBaseDefense(face.getIntToughness()); + c.setBaseDefenseString(face.getToughness()); + } + } + + /** + *

+ * Copies stats like power, toughness, etc. + *

+ * + * @param sim + * a {@link java.lang.Object} object. + * @param newOwner + * @return a {@link forge.game.card.Card} object. + */ + public static Card copyStats(final Card sim, Player newOwner) { + int id = newOwner == null ? 0 : newOwner.getGame().nextCardId(); + final Card c = new Card(id); + + c.setOwner(newOwner); + c.setCurSetCode(sim.getCurSetCode()); + + final CardCharacteristicName origState = sim.getCurState(); + for (final CardCharacteristicName state : sim.getStates()) { + c.addAlternateState(state); + c.setState(state); + sim.setState(state); + CardFactory.copyCharacteristics(sim, c); + } + + sim.setState(origState); + c.setState(origState); + c.setRules(sim.getRules()); + + return c; + } // copyStats() + + /** + * Copy characteristics. + * + * @param from + * the from + * @param to + * the to + */ + private static void copyCharacteristics(final Card from, final Card to) { + to.setBaseAttack(from.getBaseAttack()); + to.setBaseDefense(from.getBaseDefense()); + to.setBaseLoyalty(from.getBaseLoyalty()); + to.setBaseAttackString(from.getBaseAttackString()); + to.setBaseDefenseString(from.getBaseDefenseString()); + to.setIntrinsicKeyword(from.getIntrinsicKeyword()); + to.setName(from.getName()); + to.setType(from.getCharacteristics().getType()); + to.setText(from.getSpellText()); + to.setManaCost(from.getManaCost()); + to.setColor(from.getColor()); + to.setSVars(from.getSVars()); + to.setIntrinsicAbilities(from.getUnparsedAbilities()); + + to.setImageKey(from.getImageKey()); + to.setTriggers(from.getTriggers(), true); + to.setReplacementEffects(from.getReplacementEffects()); + to.setStaticAbilityStrings(from.getStaticAbilityStrings()); + + } + + /** + * Copy characteristics. + * + * @param from + * the from + * @param stateToCopy + * the state to copy + * @param to + * the to + */ + public static void copyState(final Card from, final CardCharacteristicName stateToCopy, final Card to) { + + // copy characteristics not associated with a state + to.setBaseLoyalty(from.getBaseLoyalty()); + to.setBaseAttackString(from.getBaseAttackString()); + to.setBaseDefenseString(from.getBaseDefenseString()); + to.setText(from.getSpellText()); + + // get CardCharacteristics for desired state + CardCharacteristics characteristics = from.getState(stateToCopy); + to.getCharacteristics().copyFrom(characteristics); + // handle triggers and replacement effect through Card class interface + to.setTriggers(characteristics.getTriggers(), true); + to.setReplacementEffects(characteristics.getReplacementEffects()); + } + + public static void copySpellAbility(SpellAbility from, SpellAbility to) { + to.setDescription(from.getDescription()); + to.setStackDescription(from.getDescription()); + + if (from.getSubAbility() != null) { + to.setSubAbility(from.getSubAbility().getCopy()); + } + if (from.getRestrictions() != null) { + to.setRestrictions(from.getRestrictions()); + } + if (from.getConditions() != null) { + to.setConditions(from.getConditions()); + } + + for (String sVar : from.getSVars()) { + to.setSVar(sVar, from.getSVar(sVar)); + } + } + + public static List makeToken(final String name, final String imageName, final Player controller, + final String manaCost, final String[] types, final int baseAttack, final int baseDefense, + final String[] intrinsicKeywords) { + final List list = new ArrayList(); + final Card c = new Card(controller.getGame().nextCardId()); + c.setName(name); + c.setImageKey(ImageCache.TOKEN_PREFIX + imageName); + + // TODO - most tokens mana cost is 0, this needs to be fixed + // c.setManaCost(manaCost); + c.addColor(manaCost); + c.setToken(true); + + for (final String t : types) { + c.addType(t); + } + + c.setBaseAttack(baseAttack); + c.setBaseDefense(baseDefense); + + final int multiplier = controller.getTokenDoublersMagnitude(); + for (int i = 0; i < multiplier; i++) { + Card temp = copyStats(c, controller); + + for (final String kw : intrinsicKeywords) { + temp.addIntrinsicKeyword(kw); + } + temp.setOwner(controller); + temp.setToken(true); + CardFactoryUtil.parseKeywords(temp, temp.getName()); + CardFactoryUtil.setupKeywordedAbilities(temp); + list.add(temp); + } + return list; + } + /** + * Copy triggered ability + * + * return a wrapped ability + */ + public static SpellAbility getCopiedTriggeredAbility(final SpellAbility sa) { + if (!sa.isTrigger()) { + return null; + } + // Find trigger + Trigger t = null; + if (sa.isWrapper()) { + // copy trigger? + t = ((WrappedAbility) sa).getTrigger(); + } else { // some keyword ability, e.g. Exalted, Annihilator + return sa.copy(); + } + // set up copied wrapped ability + SpellAbility trig = t.getOverridingAbility(); + if (trig == null) { + trig = AbilityFactory.getAbility(sa.getSourceCard().getSVar(t.getMapParams().get("Execute")), sa.getSourceCard()); + } + trig.setSourceCard(sa.getSourceCard()); + trig.setTrigger(true); + trig.setSourceTrigger(t.getId()); + t.setTriggeringObjects(trig); + if (t.getStoredTriggeredObjects() != null) { + trig.setAllTriggeringObjects(t.getStoredTriggeredObjects()); + } + + trig.setActivatingPlayer(sa.getActivatingPlayer()); + if (t.getMapParams().containsKey("TriggerController")) { + Player p = AbilityUtils.getDefinedPlayers(t.getHostCard(), t.getMapParams().get("TriggerController"), trig).get(0); + trig.setActivatingPlayer(p); + } + + trig.setStackDescription(trig.toString()); + if (trig.getApi() == ApiType.Charm && !trig.isWrapper()) { + CharmEffect.makeChoices(trig); + } + + WrappedAbility wrapperAbility = new WrappedAbility(t, trig, ((WrappedAbility) sa).getDecider()); + wrapperAbility.setTrigger(true); + wrapperAbility.setMandatory(((WrappedAbility) sa).isMandatory()); + wrapperAbility.setDescription(wrapperAbility.getStackDescription()); + t.setTriggeredSA(wrapperAbility); + return wrapperAbility; + } + + +} // end class AbstractCardFactory diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-gui/src/main/java/forge/game/card/CardFactoryUtil.java similarity index 97% rename from forge-game/src/main/java/forge/game/card/CardFactoryUtil.java rename to forge-gui/src/main/java/forge/game/card/CardFactoryUtil.java index 74b4f883828..7937f234910 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-gui/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1,3469 +1,3469 @@ -/* - * 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 . - */ -package forge.game.card; - -import java.util.*; -import java.util.Map.Entry; - -import org.apache.commons.lang3.StringUtils; - -import com.google.common.base.Predicate; -import com.google.common.collect.Lists; - -import forge.Command; -import forge.ai.ComputerUtil; -import forge.ai.ComputerUtilCost; -import forge.card.CardCharacteristicName; -import forge.card.CardType; -import forge.card.ColorSet; -import forge.card.MagicColor; -import forge.card.mana.ManaCost; -import forge.card.mana.ManaCostParser; -import forge.card.mana.ManaCostShard; -import forge.game.Game; -import forge.game.GameEntity; -import forge.game.GameLogEntryType; -import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; -import forge.game.card.CardPredicates.Presets; -import forge.game.cost.Cost; -import forge.game.event.GameEventCardStatsChanged; -import forge.game.phase.PhaseHandler; -import forge.game.phase.PhaseType; -import forge.game.player.Player; -import forge.game.replacement.ReplacementEffect; -import forge.game.replacement.ReplacementHandler; -import forge.game.replacement.ReplacementLayer; -import forge.game.spellability.Ability; -import forge.game.spellability.AbilityActivated; -import forge.game.spellability.AbilityStatic; -import forge.game.spellability.AbilitySub; -import forge.game.spellability.OptionalCost; -import forge.game.spellability.Spell; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.SpellAbilityRestriction; -import forge.game.spellability.SpellPermanent; -import forge.game.spellability.TargetRestrictions; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerHandler; -import forge.game.zone.Zone; -import forge.game.zone.ZoneType; -import forge.util.Aggregates; -import forge.util.Lang; - -/** - *

- * CardFactoryUtil class. - *

- * - * @author Forge - * @version $Id: CardFactoryUtil.java 24379 2014-01-20 14:13:13Z swordshine $ - */ -public class CardFactoryUtil { - - /** - *

- * abilityUnearth. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - * @param manaCost - * a {@link java.lang.String} object. - * @return a {@link forge.game.spellability.AbilityActivated} object. - */ - public static AbilityActivated abilityUnearth(final Card sourceCard, final String manaCost) { - - final Cost cost = new Cost(manaCost, true); - class AbilityUnearth extends AbilityActivated { - public AbilityUnearth(final Card ca, final Cost co, final TargetRestrictions t) { - super(ca, co, t); - } - - @Override - public AbilityActivated getCopy() { - AbilityActivated res = new AbilityUnearth(getSourceCard(), - getPayCosts(), getTargetRestrictions() == null ? null : new TargetRestrictions(getTargetRestrictions())); - CardFactory.copySpellAbility(this, res); - final SpellAbilityRestriction restrict = new SpellAbilityRestriction(); - restrict.setZone(ZoneType.Graveyard); - restrict.setSorcerySpeed(true); - res.setRestrictions(restrict); - return res; - } - - private static final long serialVersionUID = -5633945565395478009L; - - @Override - public void resolve() { - final Card card = sourceCard.getGame().getAction().moveToPlay(sourceCard); - - card.addHiddenExtrinsicKeyword("At the beginning of the end step, exile CARDNAME."); - card.addIntrinsicKeyword("Haste"); - card.setUnearthed(true); - } - - @Override - public boolean canPlayAI(Player aiPlayer) { - PhaseHandler phase = sourceCard.getGame().getPhaseHandler(); - if (phase.getPhase().isAfter(PhaseType.MAIN1) || !phase.isPlayerTurn(getActivatingPlayer())) { - return false; - } - return ComputerUtilCost.canPayCost(this, getActivatingPlayer()); - } - } - final AbilityActivated unearth = new AbilityUnearth(sourceCard, cost, null); - - final SpellAbilityRestriction restrict = new SpellAbilityRestriction(); - restrict.setZone(ZoneType.Graveyard); - restrict.setSorcerySpeed(true); - unearth.setRestrictions(restrict); - - final StringBuilder sbStack = new StringBuilder(); - sbStack.append("Unearth: ").append(sourceCard.getName()); - unearth.setStackDescription(sbStack.toString()); - - return unearth; - } // abilityUnearth() - - /** - *

- * abilityMorphDown. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - * @return a {@link forge.game.spellability.SpellAbility} object. - */ - public static SpellAbility abilityMorphDown(final Card sourceCard) { - final Spell morphDown = new Spell(sourceCard, new Cost(ManaCost.THREE, false)) { - private static final long serialVersionUID = -1438810964807867610L; - - @Override - public void resolve() { - Card c = sourceCard.getGame().getAction().moveToPlay(sourceCard); - c.setPreFaceDownCharacteristic(CardCharacteristicName.Original); - } - - @Override - public boolean canPlay() { - //Lands do not have SpellPermanents. - if (sourceCard.isLand()) { - return (sourceCard.getGame().getZoneOf(sourceCard).is(ZoneType.Hand) || sourceCard.hasKeyword("May be played")) - && sourceCard.getController().canCastSorcery(); - } - else { - return sourceCard.getSpellPermanent().canPlay(); - } - } - }; - - morphDown.setDescription("(You may cast this face down as a 2/2 creature for {3}.)"); - morphDown.setStackDescription("Morph - Creature 2/2"); - morphDown.setCastFaceDown(true); - - return morphDown; - } - - /** - *

- * abilityMorphUp. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - * @param cost - * a {@link forge.game.cost.Cost} object. - * @return a {@link forge.game.spellability.AbilityActivated} object. - */ - public static AbilityStatic abilityMorphUp(final Card sourceCard, final Cost cost) { - final AbilityStatic morphUp = new AbilityStatic(sourceCard, cost, null) { - - @Override - public void resolve() { - if (sourceCard.turnFaceUp()) { - String sb = this.getActivatingPlayer() + " has unmorphed " + sourceCard.getName(); - sourceCard.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb); - sourceCard.getGame().fireEvent(new GameEventCardStatsChanged(sourceCard)); - } - } - - @Override - public boolean canPlay() { - return sourceCard.getController().equals(this.getActivatingPlayer()) && sourceCard.isFaceDown() - && sourceCard.isInPlay(); - } - - }; // morph_up - - String costDesc = cost.toString(); - // get rid of the ": " at the end - costDesc = costDesc.substring(0, costDesc.length() - 2); - final StringBuilder sb = new StringBuilder(); - sb.append("Morph"); - if (!cost.isOnlyManaCost()) { - sb.append(" -"); - } - sb.append(" ").append(costDesc).append(" (Turn this face up any time for its morph cost.)"); - morphUp.setDescription(sb.toString()); - - final StringBuilder sbStack = new StringBuilder(); - sbStack.append(sourceCard.getName()).append(" - turn this card face up."); - morphUp.setStackDescription(sbStack.toString()); - morphUp.setIsMorphUp(true); - - return morphUp; - } - - /** - *

- * abilityCycle. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - * @param cycleCost - * a {@link java.lang.String} object. - * @return a {@link forge.game.spellability.SpellAbility} object. - */ - public static SpellAbility abilityCycle(final Card sourceCard, String cycleCost) { - StringBuilder sb = new StringBuilder(); - sb.append("AB$ Draw | Cost$ "); - sb.append(cycleCost); - sb.append(" Discard<1/CARDNAME> | ActivationZone$ Hand | PrecostDesc$ Cycling "); - sb.append("| SpellDescription$ Draw a card."); - - SpellAbility cycle = AbilityFactory.getAbility(sb.toString(), sourceCard); - cycle.setIsCycling(true); - - return cycle; - } // abilityCycle() - - /** - *

- * abilityTypecycle. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - * @param cycleCost - * a {@link java.lang.String} object. - * @param type - * a {@link java.lang.String} object. - * @return a {@link forge.game.spellability.SpellAbility} object. - */ - public static SpellAbility abilityTypecycle(final Card sourceCard, String cycleCost, final String type) { - StringBuilder sb = new StringBuilder(); - sb.append("AB$ ChangeZone | Cost$ ").append(cycleCost); - - String desc = type; - if (type.equals("Basic")) { - desc = "Basic land"; - } - - sb.append(" Discard<1/CARDNAME> | ActivationZone$ Hand | PrecostDesc$ ").append(desc).append("cycling "); - sb.append("| Origin$ Library | Destination$ Hand |"); - sb.append("ChangeType$ ").append(type); - sb.append(" | SpellDescription$ Search your library for a ").append(desc).append(" card, reveal it,"); - sb.append(" and put it into your hand. Then shuffle your library."); - - SpellAbility cycle = AbilityFactory.getAbility(sb.toString(), sourceCard); - cycle.setIsCycling(true); - - return cycle; - } // abilityTypecycle() - - /** - *

- * abilityTransmute. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - * @param transmuteCost - * a {@link java.lang.String} object. - * @return a {@link forge.game.spellability.SpellAbility} object. - */ - public static SpellAbility abilityTransmute(final Card sourceCard, String transmuteCost) { - transmuteCost += " Discard<1/CARDNAME>"; - final Cost abCost = new Cost(transmuteCost, true); - class AbilityTransmute extends AbilityActivated { - public AbilityTransmute(final Card ca, final Cost co, final TargetRestrictions t) { - super(ca, co, t); - } - - @Override - public AbilityActivated getCopy() { - AbilityActivated res = new AbilityTransmute(getSourceCard(), getPayCosts(), getTargetRestrictions()); - CardFactory.copySpellAbility(this, res); - res.getRestrictions().setZone(ZoneType.Hand); - return res; - } - - private static final long serialVersionUID = -4960704261761785512L; - - @Override - public boolean canPlayAI(Player aiPlayer) { - return false; - } - - @Override - public boolean canPlay() { - return super.canPlay() && sourceCard.getController().canCastSorcery(); - } - - @Override - public void resolve() { - final List cards = sourceCard.getController().getCardsIn(ZoneType.Library); - final List sameCost = new ArrayList(); - - for (Card c : cards) { - if (c.isSplitCard() && c.getCurState() == CardCharacteristicName.Original) { - if (c.getState(CardCharacteristicName.LeftSplit).getManaCost().getCMC() == sourceCard.getManaCost().getCMC() || - c.getState(CardCharacteristicName.RightSplit).getManaCost().getCMC() == sourceCard.getManaCost().getCMC()) { - sameCost.add(c); - } - } - else if (c.getManaCost().getCMC() == sourceCard.getManaCost().getCMC()) { - sameCost.add(c); - } - } - - if (sameCost.isEmpty()) { - return; - } - - final Card c1 = sourceCard.getController().getController().chooseSingleEntityForEffect(sameCost, this, "Select a card", true); - if (c1 != null) { - // ability.setTargetCard((Card)o); - - sourceCard.getController().discard(sourceCard, this); - sourceCard.getGame().getAction().moveToHand(c1); - - } - sourceCard.getController().shuffle(this); - } - } - final SpellAbility transmute = new AbilityTransmute(sourceCard, abCost, null); - - final StringBuilder sbDesc = new StringBuilder(); - sbDesc.append("Transmute (").append(abCost.toString()); - sbDesc.append("Search your library for a card with the same converted mana cost as this card, reveal it, "); - sbDesc.append("and put it into your hand. Then shuffle your library. Transmute only as a sorcery.)"); - transmute.setDescription(sbDesc.toString()); - - final StringBuilder sbStack = new StringBuilder(); - sbStack.append(sourceCard).append(" Transmute: Search your library "); - sbStack.append("for a card with the same converted mana cost.)"); - transmute.setStackDescription(sbStack.toString()); - - transmute.getRestrictions().setZone(ZoneType.Hand); - return transmute; - } // abilityTransmute() - - /** - *

- * abilitySuspendStatic. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - * @param suspendCost - * a {@link java.lang.String} object. - * @param timeCounters - * a int. - * @return a {@link forge.game.spellability.SpellAbility} object. - */ - public static SpellAbility abilitySuspendStatic(final Card sourceCard, final String suspendCost, final String timeCounters) { - // be careful with Suspend ability, it will not hit the stack - Cost cost = new Cost(suspendCost, true); - final SpellAbility suspend = new AbilityStatic(sourceCard, cost, null) { - @Override - public boolean canPlay() { - if (!(this.getRestrictions().canPlay(sourceCard, this))) { - return false; - } - - if (sourceCard.isInstant() || sourceCard.hasKeyword("Flash")) { - return true; - } - - return sourceCard.getOwner().canCastSorcery(); - } - - @Override - public boolean canPlayAI(Player aiPlayer) { - return true; - // Suspend currently not functional for the AI, - // seems to be an issue with regaining Priority after Suspension - } - - @Override - public void resolve() { - final Game game = sourceCard.getGame(); - final Card c = game.getAction().exile(sourceCard); - - int counters = AbilityUtils.calculateAmount(c, timeCounters, this); - c.addCounter(CounterType.TIME, counters, true); - - String sb = String.format("%s has suspended %s with %d time counters on it.", this.getActivatingPlayer(), c.getName(), counters); - game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb); - } - }; - final StringBuilder sbDesc = new StringBuilder(); - sbDesc.append("Suspend ").append(timeCounters).append(" - ").append(cost.toSimpleString()); - suspend.setDescription(sbDesc.toString()); - - String svar = "X"; // emulate "References X" here - suspend.setSVar(svar, sourceCard.getSVar(svar)); - - final StringBuilder sbStack = new StringBuilder(); - sbStack.append(sourceCard.getName()).append(" suspending for ").append(timeCounters).append(" turns.)"); - suspend.setStackDescription(sbStack.toString()); - - suspend.getRestrictions().setZone(ZoneType.Hand); - return suspend; - } // abilitySuspendStatic() - - public static void addSuspendUpkeepTrigger(Card card) { - //upkeep trigger - StringBuilder upkeepTrig = new StringBuilder(); - UUID triggerSvar = UUID.randomUUID(); - UUID removeCounterSvar = UUID.randomUUID(); - - upkeepTrig.append("Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Exile | CheckSVar$ "); - upkeepTrig.append(triggerSvar); - upkeepTrig.append(" | SVarCompare$ GE1 | References$ "); - upkeepTrig.append(triggerSvar); - upkeepTrig.append(" | Execute$ "); - upkeepTrig.append(removeCounterSvar); - upkeepTrig.append(" | TriggerDescription$ At the beginning of your upkeep, if this card is suspended, remove a time counter from it"); - - card.setSVar(removeCounterSvar.toString(), "DB$ RemoveCounter | Defined$ Self | CounterType$ TIME | CounterNum$ 1"); - card.setSVar(triggerSvar.toString(),"Count$ValidExile Card.Self+suspended"); - - final Trigger parsedUpkeepTrig = TriggerHandler.parseTrigger(upkeepTrig.toString(), card, true); - card.addTrigger(parsedUpkeepTrig); - } - - public static void addSuspendPlayTrigger(Card card) { - //play trigger - StringBuilder playTrig = new StringBuilder(); - UUID playSvar = UUID.randomUUID(); - - playTrig.append("Mode$ CounterRemoved | TriggerZones$ Exile | ValidCard$ Card.Self | NewCounterAmount$ 0 | Secondary$ True | Execute$ "); - playTrig.append(playSvar.toString()); - playTrig.append(" | TriggerDescription$ When the last time counter is removed from this card, if it's exiled, play it without paying its mana cost if able. "); - playTrig.append("If you can't, it remains exiled. If you cast a creature spell this way, it gains haste until you lose control of the spell or the permanent it becomes."); - - StringBuilder playWithoutCost = new StringBuilder(); - playWithoutCost.append("DB$ Play | Defined$ Self | WithoutManaCost$ True | SuspendCast$ True"); - - final Trigger parsedPlayTrigger = TriggerHandler.parseTrigger(playTrig.toString(), card, true); - card.addTrigger(parsedPlayTrigger); - - card.setSVar(playSvar.toString(),playWithoutCost.toString()); - } - - - /** - *

- * multiplyCost. - *

- * - * @param manacost - * a {@link java.lang.String} object. - * @param multiplier - * a int. - * @return a {@link java.lang.String} object. - */ - public static String multiplyCost(final String manacost, final int multiplier) { - if (multiplier == 0) { - return ""; - } - if (multiplier == 1) { - return manacost; - } - - final String[] tokenized = manacost.split("\\s"); - final StringBuilder sb = new StringBuilder(); - - if (Character.isDigit(tokenized[0].charAt(0))) { - // cost starts with "colorless" number cost - int cost = Integer.parseInt(tokenized[0]); - cost = multiplier * cost; - tokenized[0] = "" + cost; - sb.append(tokenized[0]); - } else { - if (tokenized[0].contains("<")) { - final String[] advCostPart = tokenized[0].split("<"); - final String costVariable = advCostPart[1].split(">")[0]; - final String[] advCostPartValid = costVariable.split("\\/", 2); - // multiply the number part of the cost object - int num = Integer.parseInt(advCostPartValid[0]); - num = multiplier * num; - tokenized[0] = advCostPart[0] + "<" + num; - if (advCostPartValid.length > 1) { - tokenized[0] = tokenized[0] + "/" + advCostPartValid[1]; - } - tokenized[0] = tokenized[0] + ">"; - sb.append(tokenized[0]); - } else { - for (int i = 0; i < multiplier; i++) { - // tokenized[0] = tokenized[0] + " " + tokenized[0]; - sb.append((" ")); - sb.append(tokenized[0]); - } - } - } - - for (int i = 1; i < tokenized.length; i++) { - if (tokenized[i].contains("<")) { - final String[] advCostParts = tokenized[i].split("<"); - final String costVariables = advCostParts[1].split(">")[0]; - final String[] advCostPartsValid = costVariables.split("\\/", 2); - // multiply the number part of the cost object - int num = Integer.parseInt(advCostPartsValid[0]); - num = multiplier * num; - tokenized[i] = advCostParts[0] + "<" + num; - if (advCostPartsValid.length > 1) { - tokenized[i] = tokenized[i] + "/" + advCostPartsValid[1]; - } - tokenized[i] = tokenized[i] + ">"; - sb.append((" ")); - sb.append(tokenized[i]); - } else { - for (int j = 0; j < multiplier; j++) { - // tokenized[i] = tokenized[i] + " " + tokenized[i]; - sb.append((" ")); - sb.append(tokenized[i]); - } - } - } - - String result = sb.toString(); - System.out.println("result: " + result); - result = result.trim(); - return result; - } - - /** - *

- * isTargetStillValid. - *

- * - * @param ability - * a {@link forge.game.spellability.SpellAbility} object. - * @param target - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean isTargetStillValid(final SpellAbility ability, final Card target) { - - Zone zone = target.getGame().getZoneOf(target); - if (zone == null) { - return false; // for tokens that disappeared - } - - final Card source = ability.getSourceCard(); - final TargetRestrictions tgt = ability.getTargetRestrictions(); - if (tgt != null) { - // Reconfirm the Validity of a TgtValid, or if the Creature is still - // a Creature - if (tgt.doesTarget() - && !target.isValid(tgt.getValidTgts(), ability.getActivatingPlayer(), ability.getSourceCard())) { - return false; - } - - // Check if the target is in the zone it needs to be in to be targeted - if (!tgt.getZone().contains(zone.getZoneType())) { - return false; - } - } else { - // If an Aura's target is removed before it resolves, the Aura - // fizzles - if (source.isAura() && !target.isInZone(ZoneType.Battlefield)) { - return false; - } - } - - // Make sure it's still targetable as well - return target.canBeTargetedBy(ability); - } - - // does "target" have protection from "card"? - /** - *

- * hasProtectionFrom. - *

- * - * @param card - * a {@link forge.game.card.Card} object. - * @param target - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean hasProtectionFrom(final Card card, final Card target) { - if (target == null) { - return false; - } - - return target.hasProtectionFrom(card); - } - - /** - *

- * isCounterable. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean isCounterable(final Card c) { - if (c.hasKeyword("CARDNAME can't be countered.") || !c.getCanCounter()) { - return false; - } - - return true; - } - - /** - *

- * isCounterableBy. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param sa - * the sa - * @return a boolean. - */ - public static boolean isCounterableBy(final Card c, final SpellAbility sa) { - if (!isCounterable(c)) { - return false; - } - // Autumn's Veil - if (c.hasKeyword("CARDNAME can't be countered by blue or black spells.") && sa.isSpell() - && (sa.getSourceCard().isBlack() || sa.getSourceCard().isBlue())) { - return false; - } - return true; - } - - /** - *

- * countOccurrences. - *

- * - * @param arg1 - * a {@link java.lang.String} object. - * @param arg2 - * a {@link java.lang.String} object. - * @return a int. - */ - public static int countOccurrences(final String arg1, final String arg2) { - - int count = 0; - int index = 0; - while ((index = arg1.indexOf(arg2, index)) != -1) { - ++index; - ++count; - } - return count; - } - - /** - *

- * parseMath. - *

- * - * @param l - * an array of {@link java.lang.String} objects. - * @return an array of {@link java.lang.String} objects. - */ - public static String extractOperators(final String expression) { - String[] l = expression.split("/"); - return l.length > 1 ? l[1] : null; - } - - /** - *

- * Parse player targeted X variables. - *

- * - * @param players - * a {@link java.util.ArrayList} object. - * @param s - * a {@link java.lang.String} object. - * @param source - * a {@link forge.game.card.Card} object. - * @return a int. - */ - public static int objectXCount(final List objects, final String s, final Card source) { - if (objects.isEmpty()) { - return 0; - } - - int n = s.startsWith("Amount") ? objects.size() : 0; - return doXMath(n, extractOperators(s), source); - } - - /** - *

- * Parse player targeted X variables. - *

- * - * @param players - * a {@link java.util.ArrayList} object. - * @param s - * a {@link java.lang.String} object. - * @param source - * a {@link forge.game.card.Card} object. - * @return a int. - */ - public static int playerXCount(final List players, final String s, final Card source) { - if (players.size() == 0) { - return 0; - } - - final String[] l = s.split("/"); - final String m = extractOperators(s); - - int n = 0; - - // methods for getting the highest/lowest playerXCount from a range of players - if (l[0].startsWith("Highest")) { - for (final Player player : players) { - final int current = playerXProperty(player, s.replace("Highest", ""), source); - if (current > n) { - n = current; - } - } - - return doXMath(n, m, source); - } else if (l[0].startsWith("Lowest")) { - n = 99999; // if no players have fewer than 99999 valids, the game is frozen anyway - for (final Player player : players) { - final int current = playerXProperty(player, s.replace("Lowest", ""), source); - if (current < n) { - n = current; - } - } - return doXMath(n, m, source); - } - - - final String[] sq; - sq = l[0].split("\\."); - - // the number of players passed in - if (sq[0].equals("Amount")) { - return doXMath(players.size(), m, source); - } - if (sq[0].contains("DamageThisTurn")) { - int totDmg = 0; - for (Player p : players) { - totDmg += p.getAssignedDamage(); - } - return doXMath(totDmg, m, source); - } - - if(players.size() > 0) - return playerXProperty(players.get(0), s, source); - - return doXMath(n, m, source); - } - - public static int playerXProperty(Player player, String s, Card source) { - final String[] l = s.split("/"); - final String m = extractOperators(s); - - final Game game = player.getGame(); - - // count valid cards in any specified zone/s - if (l[0].startsWith("Valid") && !l[0].contains("Valid ")) { - String[] lparts = l[0].split(" ", 2); - final List vZone = ZoneType.listValueOf(lparts[0].split("Valid")[1]); - String restrictions = l[0].replace(lparts[0] + " ", ""); - final String[] rest = restrictions.split(","); - List cards = game.getCardsIn(vZone); - cards = CardLists.getValidCards(cards, rest, player, source); - return doXMath(cards.size(), m, source); - } - // count valid cards on the battlefield - if (l[0].startsWith("Valid ")) { - final String restrictions = l[0].substring(6); - final String[] rest = restrictions.split(","); - List cardsonbattlefield = game.getCardsIn(ZoneType.Battlefield); - cardsonbattlefield = CardLists.getValidCards(cardsonbattlefield, rest, player, source); - return doXMath(cardsonbattlefield.size(), m, source); - } - - final String[] sq = l[0].split("\\."); - final String value = sq[0]; - - if (value.contains("CardsInHand")) { - return doXMath(player.getCardsIn(ZoneType.Hand).size(), m, source); - } - - if (value.contains("NumPowerSurgeLands")) { - return doXMath(player.getNumPowerSurgeLands(), m, source); - } - - if (value.contains("DomainPlayer")) { - int n = 0; - final List someCards = new ArrayList(); - someCards.addAll(player.getCardsIn(ZoneType.Battlefield)); - final List basic = MagicColor.Constant.BASIC_LANDS; - - for (int i = 0; i < basic.size(); i++) { - if (!CardLists.getType(someCards, basic.get(i)).isEmpty()) { - n++; - } - } - return doXMath(n, m, source); - } - - if (value.contains("CardsInLibrary")) { - return doXMath(player.getCardsIn(ZoneType.Library).size(), m, source); - } - - if (value.contains("CardsInGraveyard")) { - return doXMath(player.getCardsIn(ZoneType.Graveyard).size(), m, source); - } - if (value.contains("LandsInGraveyard")) { - return doXMath(CardLists.getType(player.getCardsIn(ZoneType.Graveyard), "Land").size(), m, source); - } - - if (value.contains("CreaturesInPlay")) { - return doXMath(player.getCreaturesInPlay().size(), m, source); - } - - if (value.contains("CardsInPlay")) { - return doXMath(player.getCardsIn(ZoneType.Battlefield).size(), m, source); - } - - if (value.contains("LifeTotal")) { - return doXMath(player.getLife(), m, source); - } - - if (value.contains("LifeLostThisTurn")) { - return doXMath(player.getLifeLostThisTurn(), m, source); - } - - if (value.contains("LifeGainedThisTurn")) { - return doXMath(player.getLifeGainedThisTurn(), m, source); - } - - if (value.contains("PoisonCounters")) { - return doXMath(player.getPoisonCounters(), m, source); - } - - if (value.contains("TopOfLibraryCMC")) { - return doXMath(Aggregates.sum(player.getCardsIn(ZoneType.Library, 1), CardPredicates.Accessors.fnGetCmc), m, source); - } - - if (value.contains("LandsPlayed")) { - return doXMath(player.getNumLandsPlayed(), m, source); - } - - if (value.contains("CardsDrawn")) { - return doXMath(player.getNumDrawnThisTurn(), m, source); - } - - if (value.contains("CardsDiscardedThisTurn")) { - return doXMath(player.getNumDiscardedThisTurn(), m, source); - } - - if (value.contains("AttackersDeclared")) { - return doXMath(player.getAttackersDeclaredThisTurn(), m, source); - } - - if (value.equals("DamageDoneToPlayerBy")) { - return doXMath(source.getDamageDoneToPlayerBy(player.getName()), m, source); - } - - if (value.contains("DamageToOppsThisTurn")) { - int oppDmg = 0; - for (Player opp : player.getOpponents()) { - oppDmg += opp.getAssignedDamage(); - } - return doXMath(oppDmg, m, source); - } - - return doXMath(0, m, source); - } - - /** - *

- * Parse non-mana X variables. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param expression - * a {@link java.lang.String} object. - * @return a int. - */ - public static int xCount(final Card c, final String expression) { - if (StringUtils.isBlank(expression)) return 0; - if (StringUtils.isNumeric(expression)) return Integer.parseInt(expression); - - final Player cc = c.getController(); - final Player ccOpp = cc.getOpponent(); - final Game game = c.getGame(); - final Player activePlayer = game.getPhaseHandler().getPlayerTurn(); - - - final String[] l = expression.split("/"); - final String m = extractOperators(expression); - - // accept straight numbers - if (l[0].startsWith("Number$")) { - final String number = l[0].substring(7); - if (number.equals("ChosenNumber")) { - return doXMath(c.getChosenNumber(), m, c); - } else { - return doXMath(Integer.parseInt(number), m, c); - } - } - - if (l[0].startsWith("Count$")) { - l[0] = l[0].substring(6); - } - - if (l[0].startsWith("SVar$")) { - return doXMath(xCount(c, c.getSVar(l[0].substring(5))), m, c); - } - - if (l[0].startsWith("Controller$")) - return playerXProperty(cc, l[0].substring(11), c); - - - // Manapool - if (l[0].startsWith("ManaPool")) { - final String color = l[0].split(":")[1]; - if (color.equals("All")) { - return cc.getManaPool().totalMana(); - } else { - return cc.getManaPool().getAmountOfColor(MagicColor.fromName(color)); - } - } - - // count valid cards in any specified zone/s - if (l[0].startsWith("Valid")) { - String[] lparts = l[0].split(" ", 2); - final String[] rest = lparts[1].split(","); - - final List cardsInZones = lparts[0].length() > 5 - ? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(5))) - : game.getCardsIn(ZoneType.Battlefield); - - List cards = CardLists.getValidCards(cardsInZones, rest, cc, c); - return doXMath(cards.size(), m, c); - } - - - if (l[0].startsWith("ImprintedCardPower") && !c.getImprinted().isEmpty()) return c.getImprinted().get(0).getNetAttack(); - if (l[0].startsWith("ImprintedCardToughness") && !c.getImprinted().isEmpty()) return c.getImprinted().get(0).getNetDefense(); - if (l[0].startsWith("ImprintedCardManaCost") && !c.getImprinted().isEmpty()) return c.getImprinted().get(0).getCMC(); - - if (l[0].startsWith("GreatestPower_")) { - final String restriction = l[0].substring(14); - final String[] rest = restriction.split(","); - List list = CardLists.getValidCards(cc.getGame().getCardsIn(ZoneType.Battlefield), rest, cc, c); - int highest = 0; - for (final Card crd : list) { - if (crd.getNetAttack() > highest) { - highest = crd.getNetAttack(); - } - } - return highest; - } - - if (l[0].startsWith("HighestCMC_")) { - final String restriction = l[0].substring(11); - final String[] rest = restriction.split(","); - List list = cc.getGame().getCardsInGame(); - list = CardLists.getValidCards(list, rest, cc, c); - int highest = 0; - for (final Card crd : list) { - if (crd.isSplitCard()) { - if (crd.getCMC(Card.SplitCMCMode.LeftSplitCMC) > highest) { - highest = crd.getCMC(Card.SplitCMCMode.LeftSplitCMC); - } - if (crd.getCMC(Card.SplitCMCMode.RightSplitCMC) > highest) { - highest = crd.getCMC(Card.SplitCMCMode.RightSplitCMC); - } - } else { - if (crd.getCMC() > highest) { - highest = crd.getCMC(); - } - } - } - return highest; - } - - if (l[0].startsWith("DifferentCardNames_")) { - final List crdname = new ArrayList(); - final String restriction = l[0].substring(19); - final String[] rest = restriction.split(","); - List list = cc.getGame().getCardsInGame(); - list = CardLists.getValidCards(list, rest, cc, c); - for (final Card card : list) { - if (!crdname.contains(card.getName())) { - crdname.add(card.getName()); - } - } - return doXMath(crdname.size(), m, c); - } - - if (l[0].startsWith("RememberedSize")) { - return doXMath(c.getRemembered().size(), m, c); - } - - // Count$CountersAdded - if (l[0].startsWith("CountersAdded")) { - final String[] components = l[0].split(" ", 3); - final CounterType counterType = CounterType.valueOf(components[1]); - String restrictions = components[2]; - final String[] rest = restrictions.split(","); - List candidates = game.getCardsInGame(); - candidates = CardLists.getValidCards(candidates, rest, cc, c); - - int added = 0; - for (final Card counterSource : candidates) { - added += c.getCountersAddedBy(counterSource, counterType); - } - return doXMath(added, m, c); - } - - if (l[0].startsWith("CommanderCastFromCommandZone")) { - // Read SVar CommanderCostRaise from Commander effect - Card commeff = CardLists.filter(cc.getCardsIn(ZoneType.Command), - CardPredicates.nameEquals("Commander effect")).get(0); - return doXMath(xCount(commeff, commeff.getSVar("CommanderCostRaise")), "DivideEvenlyDown.2", c); - } - - if (l[0].startsWith("MostProminentCreatureType")) { - String restriction = l[0].split(" ")[1]; - List list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, cc, c); - return doXMath(getMostProminentCreatureTypeSize(list), m, c); - } - - if (l[0].startsWith("SecondMostProminentColor")) { - String restriction = l[0].split(" ")[1]; - List list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, cc, c); - int[] colorSize = SortColorsFromList(list); - return doXMath(colorSize[colorSize.length - 2], m, c); - } - - if (l[0].startsWith("RolledThisTurn")) { - return game.getPhaseHandler().getPlanarDiceRolledthisTurn(); - } - - final String[] sq; - sq = l[0].split("\\."); - - if (sq[0].contains("xPaid")) return doXMath(c.getXManaCostPaid(), m, c); - - - if (sq[0].equals("YouDrewThisTurn")) return doXMath(c.getController().getNumDrawnThisTurn(), m, c); - if (sq[0].equals("OppDrewThisTurn")) return doXMath(c.getController().getOpponent().getNumDrawnThisTurn(), m, c); - - - if (sq[0].equals("StormCount")) return doXMath(game.getStack().getCardsCastThisTurn().size() - 1, m, c); - if (sq[0].equals("DamageDoneThisTurn")) return doXMath(c.getDamageDoneThisTurn(), m, c); - if (sq[0].equals("BloodthirstAmount")) return doXMath(c.getController().getBloodthirstAmount(), m, c); - if (sq[0].equals("RegeneratedThisTurn")) return doXMath(c.getRegeneratedThisTurn(), m, c); - - // TriggeringObjects - if (sq[0].startsWith("Triggered")) return doXMath(xCount((Card) c.getTriggeringObject("Card"), sq[0].substring(9)), m, c); - - if (sq[0].contains("YourStartingLife")) return doXMath(cc.getStartingLife(), m, c); - //if (sq[0].contains("OppStartingLife")) return doXMath(oppController.getStartingLife(), m, c); // found no cards using it - - - if (sq[0].contains("YourLifeTotal")) return doXMath(cc.getLife(), m, c); - if (sq[0].contains("OppLifeTotal")) return doXMath(ccOpp.getLife(), m, c); - - // Count$TargetedLifeTotal (targeted player's life total) - if (sq[0].contains("TargetedLifeTotal")) { - for (final SpellAbility sa : c.getCharacteristics().getSpellAbility()) { - final SpellAbility saTargeting = sa.getSATargetingPlayer(); - if (saTargeting != null) { - for (final Player tgtP : saTargeting.getTargets().getTargetPlayers()) { - return doXMath(tgtP.getLife(), m, c); - } - } - } - } - - if (sq[0].contains("LifeYouLostThisTurn")) return doXMath(cc.getLifeLostThisTurn(), m, c); - if (sq[0].contains("LifeYouGainedThisTurn")) return doXMath(cc.getLifeGainedThisTurn(), m, c); - if (sq[0].contains("LifeOppsLostThisTurn")) { - int lost = 0; - for (Player opp : cc.getOpponents()) { - lost += opp.getLifeLostThisTurn(); - } - return doXMath(lost, m, c); - } - - if (sq[0].equals("TotalDamageDoneByThisTurn")) return doXMath(c.getTotalDamageDoneBy(), m, c); - if (sq[0].equals("TotalDamageReceivedThisTurn")) return doXMath(c.getTotalDamageRecievedThisTurn(), m, c); - - if (sq[0].contains("YourPoisonCounters")) return doXMath(cc.getPoisonCounters(), m, c); - if (sq[0].contains("OppPoisonCounters")) return doXMath(ccOpp.getPoisonCounters(), m, c); - - if (sq[0].contains("OppDamageThisTurn")) return doXMath(ccOpp.getAssignedDamage(), m, c); - if (sq[0].contains("YourDamageThisTurn")) return doXMath(cc.getAssignedDamage(), m, c); - - // Count$YourTypeDamageThisTurn Type - if (sq[0].contains("YourTypeDamageThisTurn")) return doXMath(cc.getAssignedDamage(sq[0].split(" ")[1]), m, c); - if (sq[0].contains("YourDamageSourcesThisTurn")) { - Iterable allSrc = cc.getAssignedDamageSources(); - String restriction = sq[0].split(" ")[1]; - List filtered = CardLists.getValidCards(allSrc, restriction, cc, c); - return doXMath(filtered.size(), m, c); - } - - if (sq[0].contains("YourLandsPlayed")) return doXMath(cc.getNumLandsPlayed(), m, c); - if (sq[0].contains("OppLandsPlayed")) return doXMath(ccOpp.getNumLandsPlayed(), m, c); - - // Count$TopOfLibraryCMC - if (sq[0].contains("TopOfLibraryCMC")) { - final List library = cc.getCardsIn(ZoneType.Library); - return doXMath(library.isEmpty() ? 0 : library.get(0).getCMC(), m, c); - } - - // Count$EnchantedControllerCreatures - if (sq[0].contains("EnchantedControllerCreatures")) { - List enchantedControllerInPlay = new ArrayList(); - if (c.getEnchantingCard() != null) { - enchantedControllerInPlay = c.getEnchantingCard().getController().getCardsIn(ZoneType.Battlefield); - enchantedControllerInPlay = CardLists.filter(enchantedControllerInPlay, CardPredicates.Presets.CREATURES); - } - return enchantedControllerInPlay.size(); - } - - // Count$MonstrosityMagnitude - if (sq[0].contains("MonstrosityMagnitude")) { - return doXMath(c.getMonstrosityNum(), m, c); - } - - // Count$Chroma. - if (sq[0].contains("Chroma") || sq[0].equals("Devotion")) { - ZoneType sourceZone = sq[0].contains("ChromaInGrave") ? ZoneType.Graveyard : ZoneType.Battlefield; - String colorAbb = sq[1]; - if (colorAbb.contains("Chosen")) { - colorAbb = MagicColor.toShortString(c.getChosenColor().get(0)); - } - final List cards; - if (sq[0].contains("ChromaSource")) { // Runs Chroma for passed in Source card - cards = Lists.newArrayList(c); - } else { - cards = cc.getCardsIn(sourceZone); - } - - int colorOcurrencices = 0; - byte colorCode = MagicColor.fromName(colorAbb); - for(Card c0 : cards) { - for(ManaCostShard sh : c0.getManaCost()){ - if (sh.canBePaidWithManaOfColor(colorCode)) - colorOcurrencices++; - } - } - return doXMath(colorOcurrencices, m, c); - } - if (sq[0].contains("DevotionDual")) { - int colorOcurrencices = 0; - byte color1 = MagicColor.fromName(sq[1]); - byte color2 = MagicColor.fromName(sq[2]); - for(Card c0 : cc.getCardsIn(ZoneType.Battlefield)) { - for (ManaCostShard sh : c0.getManaCost()) { - if (sh.canBePaidWithManaOfColor(color1) || sh.canBePaidWithManaOfColor(color2)) { - colorOcurrencices++; - } - } - } - return doXMath(colorOcurrencices, m, c); - } - - if (sq[0].contains("Hellbent")) return doXMath(Integer.parseInt(sq[cc.hasHellbent() ? 1 : 2]), m, c); - if (sq[0].contains("Metalcraft")) return doXMath(Integer.parseInt(sq[cc.hasMetalcraft() ? 1 : 2]), m, c); - if (sq[0].contains("FatefulHour")) return doXMath(Integer.parseInt(sq[cc.getLife() <= 5 ? 1 : 2]), m, c); - - if (sq[0].contains("Landfall")) return doXMath(Integer.parseInt(sq[cc.hasLandfall() ? 1 : 2]), m, c); - if (sq[0].contains("Threshold")) return doXMath(Integer.parseInt(sq[cc.hasThreshold() ? 1 : 2]), m, c); - if (sq[0].startsWith("Kicked")) return doXMath(Integer.parseInt(sq[c.getKickerMagnitude() > 0 ? 1 : 2]), m, c); - if (sq[0].startsWith("AltCost")) return doXMath(Integer.parseInt(sq[c.isOptionalCostPaid(OptionalCost.AltCost) ? 1 : 2]), m, c); - - // Count$wasCastFrom.. - if (sq[0].startsWith("wasCastFrom")) { - boolean zonesMatch = c.getCastFrom() == ZoneType.smartValueOf(sq[0].substring(11)); - return doXMath(Integer.parseInt(sq[zonesMatch ? 1 : 2]), m, c); - } - - if (sq[0].startsWith("Devoured")) { - final String validDevoured = l[0].split(" ")[1]; - List cl = CardLists.getValidCards(c.getDevoured(), validDevoured.split(","), cc, c); - return doXMath(cl.size(), m, c); - } - - if (sq[0].contains("CardPower")) return doXMath(c.getNetAttack(), m, c); - if (sq[0].contains("CardToughness")) return doXMath(c.getNetDefense(), m, c); - if (sq[0].contains("CardSumPT")) return doXMath((c.getNetAttack() + c.getNetDefense()), m, c); - - // Count$SumPower_valid - if (sq[0].contains("SumPower")) { - final String[] restrictions = l[0].split("_"); - final String[] rest = restrictions[1].split(","); - List filteredCards = CardLists.getValidCards(cc.getGame().getCardsIn(ZoneType.Battlefield), rest, cc, c); - return doXMath(Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetNetAttack), m, c); - } - // Count$CardManaCost - if (sq[0].contains("CardManaCost")) { - Card ce = sq[0].contains("Equipped") && c.isEquipping() ? c.getEquipping().get(0) : c; - return doXMath(ce.getCMC(), m, c); - } - // Count$SumCMC_valid - if (sq[0].contains("SumCMC")) { - final String[] restrictions = l[0].split("_"); - final String[] rest = restrictions[1].split(","); - List cardsonbattlefield = game.getCardsIn(ZoneType.Battlefield); - List filteredCards = CardLists.getValidCards(cardsonbattlefield, rest, cc, c); - return Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetCmc); - } - - if (sq[0].contains("CardNumColors")) return doXMath(CardUtil.getColors(c).countColors(), m, c); - if (sq[0].contains("ChosenNumber")) return doXMath(c.getChosenNumber(), m, c); - if (sq[0].contains("CardCounters")) { - // CardCounters.ALL to be used for Kinsbaile Borderguard and anything that cares about all counters - int count = 0; - if (sq[1].equals("ALL")) { - for(Integer i : c.getCounters().values()) { - if (i != null && i > 0) { - count += i; - } - } - } else { - count = c.getCounters(CounterType.getType(sq[1])); - } - return doXMath(count, m, c); - } - - // Count$TotalCounters._ - if (sq[0].contains("TotalCounters")) { - final String[] restrictions = l[0].split("_"); - final CounterType cType = CounterType.getType(restrictions[1]); - final String[] validFilter = restrictions[2].split(","); - List validCards = game.getCardsIn(ZoneType.Battlefield); - validCards = CardLists.getValidCards(validCards, validFilter, cc, c); - int cCount = 0; - for (final Card card : validCards) { - cCount += card.getCounters(cType); - } - return doXMath(cCount, m, c); - } - - if (sq[0].contains("CardTypes")) return doXMath(getCardTypesFromList(game.getCardsIn(ZoneType.smartValueOf(sq[1]))), m, c); - - if (sq[0].contains("BushidoPoint")) return doXMath(c.getKeywordMagnitude("Bushido"), m, c); - if (sq[0].contains("TimesKicked")) return doXMath(c.getKickerMagnitude(), m, c); - if (sq[0].contains("NumCounters")) return doXMath(c.getCounters(CounterType.getType(sq[1])), m, c); - - - // Count$IfMainPhase.. // 7/10 - if (sq[0].contains("IfMainPhase")) { - final PhaseHandler cPhase = cc.getGame().getPhaseHandler(); - final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.getPlayerTurn().equals(cc); - return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), m, c); - } - - // Count$ThisTurnEntered [from ] - if (sq[0].contains("ThisTurnEntered")) { - final String[] workingCopy = l[0].split("_"); - - ZoneType destination = ZoneType.smartValueOf(workingCopy[1]); - final boolean hasFrom = workingCopy[2].equals("from"); - ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null; - String validFilter = workingCopy[hasFrom ? 4 : 2] ; - - final List res = CardUtil.getThisTurnEntered(destination, origin, validFilter, c); - return doXMath(res.size(), m, c); - } - - // Count$LastTurnEntered [from ] - if (sq[0].contains("LastTurnEntered")) { - final String[] workingCopy = l[0].split("_"); - - ZoneType destination = ZoneType.smartValueOf(workingCopy[1]); - final boolean hasFrom = workingCopy[2].equals("from"); - ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null; - String validFilter = workingCopy[hasFrom ? 4 : 2] ; - - final List res = CardUtil.getLastTurnEntered(destination, origin, validFilter, c); - return doXMath(res.size(), m, c); - } - - // Count$AttackersDeclared - if (sq[0].contains("AttackersDeclared")) { - return doXMath(cc.getAttackersDeclaredThisTurn(), m, c); - } - - // Count$ThisTurnCast - // Count$LastTurnCast - if (sq[0].contains("ThisTurnCast") || sq[0].contains("LastTurnCast")) { - - final String[] workingCopy = l[0].split("_"); - final String validFilter = workingCopy[1]; - - List res; - - if (workingCopy[0].contains("This")) { - res = CardUtil.getThisTurnCast(validFilter, c); - } else { - res = CardUtil.getLastTurnCast(validFilter, c); - } - - final int ret = doXMath(res.size(), m, c); - return ret; - } - - // Count$Morbid.. - if (sq[0].startsWith("Morbid")) { - final List res = CardUtil.getThisTurnEntered(ZoneType.Graveyard, ZoneType.Battlefield, "Creature", c); - if (res.size() > 0) { - return doXMath(Integer.parseInt(sq[1]), m, c); - } else { - return doXMath(Integer.parseInt(sq[2]), m, c); - } - } - - if (sq[0].equals("YourTurns")) { - return doXMath(cc.getTurn(), m, c); - } - - if (sq[0].equals("TotalTurns")) { - // Sorry for the Singleton use, replace this once this function has game passed into it - return doXMath(game.getPhaseHandler().getTurn(), m, c); - } - - //Count$Random.. - if (sq[0].equals("Random")) { - int min = StringUtils.isNumeric(sq[1]) ? Integer.parseInt(sq[1]) : xCount(c, c.getSVar(sq[1])); - int max = StringUtils.isNumeric(sq[2]) ? Integer.parseInt(sq[2]) : xCount(c, c.getSVar(sq[2])); - - return forge.util.MyRandom.getRandom().nextInt(1+max-min) + min; - } - - - // Count$Domain - if (sq[0].startsWith("Domain")) { - int n = 0; - Player neededPlayer = sq[0].equals("DomainActivePlayer") ? activePlayer : cc; - List someCards = CardLists.filter(neededPlayer.getCardsIn(ZoneType.Battlefield), Presets.LANDS); - for (String basic : MagicColor.Constant.BASIC_LANDS) { - if (!CardLists.getType(someCards, basic).isEmpty()) { - n++; - } - } - return doXMath(n, m, c); - } - - // Count$ColoredCreatures *a DOMAIN for creatures* - if (sq[0].contains("ColoredCreatures")) { - int mask = 0; - List someCards = CardLists.filter(cc.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); - for (final Card crd : someCards) { - mask |= CardUtil.getColors(crd).getColor(); - } - return doXMath(ColorSet.fromMask(mask).countColors(), m, c); - } - - // Count$CardMulticolor.. - if (sq[0].contains("CardMulticolor")) { - final boolean isMulti = CardUtil.getColors(c).isMulticolor(); - return doXMath(Integer.parseInt(sq[isMulti ? 1 : 2]), m, c); - } - - - // Complex counting methods - List someCards = getCardListForXCount(c, cc, ccOpp, sq); - - // 1/10 - Count$MaxCMCYouCtrl - if (sq[0].contains("MaxCMC")) { - int mmc = Aggregates.max(someCards, CardPredicates.Accessors.fnGetCmc); - return doXMath(mmc, m, c); - } - - return doXMath(someCards.size(), m, c); - } - - private static List getCardListForXCount(final Card c, final Player cc, final Player ccOpp, final String[] sq) { - List someCards = new ArrayList(); - final Game game = c.getGame(); - - // Generic Zone-based counting - // Count$QualityAndZones.Subquality - - // build a list of cards in each possible specified zone - - // if a card was ever written to count two different zones, - // make sure they don't get added twice. - boolean mf = false, my = false, mh = false; - boolean of = false, oy = false, oh = false; - - if (sq[0].contains("YouCtrl") && !mf) { - someCards.addAll(cc.getCardsIn(ZoneType.Battlefield)); - mf = true; - } - - if (sq[0].contains("InYourYard") && !my) { - someCards.addAll(cc.getCardsIn(ZoneType.Graveyard)); - my = true; - } - - if (sq[0].contains("InYourLibrary") && !my) { - someCards.addAll(cc.getCardsIn(ZoneType.Library)); - my = true; - } - - if (sq[0].contains("InYourHand") && !mh) { - someCards.addAll(cc.getCardsIn(ZoneType.Hand)); - mh = true; - } - - if (sq[0].contains("InYourSideboard") && !mh) { - someCards.addAll(cc.getCardsIn(ZoneType.Sideboard)); - mh = true; - } - - if (sq[0].contains("OppCtrl")) { - if (!of) { - someCards.addAll(ccOpp.getCardsIn(ZoneType.Battlefield)); - of = true; - } - } - - if (sq[0].contains("InOppYard")) { - if (!oy) { - someCards.addAll(ccOpp.getCardsIn(ZoneType.Graveyard)); - oy = true; - } - } - - if (sq[0].contains("InOppHand")) { - if (!oh) { - someCards.addAll(ccOpp.getCardsIn(ZoneType.Hand)); - oh = true; - } - } - - if (sq[0].contains("InChosenHand")) { - if (!oh) { - if (c.getChosenPlayer() != null) { - someCards.addAll(c.getChosenPlayer().getCardsIn(ZoneType.Hand)); - } - oh = true; - } - } - - if (sq[0].contains("InChosenYard")) { - if (!oh) { - if (c.getChosenPlayer() != null) { - someCards.addAll(c.getChosenPlayer().getCardsIn(ZoneType.Graveyard)); - } - oh = true; - } - } - - if (sq[0].contains("OnBattlefield")) { - if (!mf) { - someCards.addAll(cc.getCardsIn(ZoneType.Battlefield)); - } - if (!of) { - someCards.addAll(ccOpp.getCardsIn(ZoneType.Battlefield)); - } - } - - if (sq[0].contains("InAllYards")) { - if (!my) { - someCards.addAll(cc.getCardsIn(ZoneType.Graveyard)); - } - if (!oy) { - someCards.addAll(ccOpp.getCardsIn(ZoneType.Graveyard)); - } - } - - if (sq[0].contains("SpellsOnStack")) { - someCards.addAll(game.getCardsIn(ZoneType.Stack)); - } - - if (sq[0].contains("InAllHands")) { - if (!mh) { - someCards.addAll(cc.getCardsIn(ZoneType.Hand)); - } - if (!oh) { - someCards.addAll(ccOpp.getCardsIn(ZoneType.Hand)); - } - } - - // Count$InTargetedHand (targeted player's cards in hand) - if (sq[0].contains("InTargetedHand")) { - for (final SpellAbility sa : c.getCharacteristics().getSpellAbility()) { - final SpellAbility saTargeting = sa.getSATargetingPlayer(); - if (saTargeting != null) { - for (final Player tgtP : saTargeting.getTargets().getTargetPlayers()) { - someCards.addAll(tgtP.getCardsIn(ZoneType.Hand)); - } - } - } - } - - // Count$InTargetedHand (targeted player's cards in hand) - if (sq[0].contains("InEnchantedHand")) { - GameEntity o = c.getEnchanting(); - Player controller = null; - if (o instanceof Card) { - controller = ((Card) o).getController(); - } - else { - controller = (Player) o; - } - if (controller != null) { - someCards.addAll(controller.getCardsIn(ZoneType.Hand)); - } - } - if (sq[0].contains("InEnchantedYard")) { - GameEntity o = c.getEnchanting(); - Player controller = null; - if (o instanceof Card) { - controller = ((Card) o).getController(); - } - else { - controller = (Player) o; - } - if (controller != null) { - someCards.addAll(controller.getCardsIn(ZoneType.Graveyard)); - } - } - // filter lists based on the specified quality - - // "Clerics you control" - Count$TypeYouCtrl.Cleric - if (sq[0].contains("Type")) { - someCards = CardLists.filter(someCards, CardPredicates.isType(sq[1])); - } - - // "Named in all graveyards" - Count$NamedAllYards. - - if (sq[0].contains("Named")) { - if (sq[1].equals("CARDNAME")) { - sq[1] = c.getName(); - } - someCards = CardLists.filter(someCards, CardPredicates.nameEquals(sq[1])); - } - - // Refined qualities - - // "Untapped Lands" - Count$UntappedTypeYouCtrl.Land - // if (sq[0].contains("Untapped")) { someCards = CardLists.filter(someCards, Presets.UNTAPPED); } - - // if (sq[0].contains("Tapped")) { someCards = CardLists.filter(someCards, Presets.TAPPED); } - -// String sq0 = sq[0].toLowerCase(); -// for(String color : MagicColor.Constant.ONLY_COLORS) { -// if( sq0.contains(color) ) -// someCards = someCards.filter(CardListFilter.WHITE); -// } - // "White Creatures" - Count$WhiteTypeYouCtrl.Creature - // if (sq[0].contains("White")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.WHITE)); - // if (sq[0].contains("Blue")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.BLUE)); - // if (sq[0].contains("Black")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.BLACK)); - // if (sq[0].contains("Red")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.RED)); - // if (sq[0].contains("Green")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.GREEN)); - - if (sq[0].contains("Multicolor")) { - someCards = CardLists.filter(someCards, new Predicate() { - @Override - public boolean apply(final Card c) { - return CardUtil.getColors(c).isMulticolor(); - } - }); - } - - if (sq[0].contains("Monocolor")) { - someCards = CardLists.filter(someCards, new Predicate() { - @Override - public boolean apply(final Card c) { - return CardUtil.getColors(c).isMonoColor(); - } - }); - } - return someCards; - } - - public static int doXMath(final int num, final String operators, final Card c) { - if (operators == null || operators.equals("none")) { - return num; - } - - final String[] s = operators.split("\\."); - int secondaryNum = 0; - - try { - if (s.length == 2) { - secondaryNum = Integer.parseInt(s[1]); - } - } catch (final Exception e) { - secondaryNum = xCount(c, c.getSVar(s[1])); - } - - if (s[0].contains("Plus")) { - return num + secondaryNum; - } else if (s[0].contains("NMinus")) { - return secondaryNum - num; - } else if (s[0].contains("Minus")) { - return num - secondaryNum; - } else if (s[0].contains("Twice")) { - return num * 2; - } else if (s[0].contains("Thrice")) { - return num * 3; - } else if (s[0].contains("HalfUp")) { - return (int) (Math.ceil(num / 2.0)); - } else if (s[0].contains("HalfDown")) { - return (int) (Math.floor(num / 2.0)); - } else if (s[0].contains("ThirdUp")) { - return (int) (Math.ceil(num / 3.0)); - } else if (s[0].contains("ThirdDown")) { - return (int) (Math.floor(num / 3.0)); - } else if (s[0].contains("Negative")) { - return num * -1; - } else if (s[0].contains("Times")) { - return num * secondaryNum; - } else if (s[0].contains("DivideEvenlyDown")) { - if (secondaryNum == 0) { - return 0; - } else { - return num / secondaryNum; - } - } else if (s[0].contains("Mod")) { - return num % secondaryNum; - } else if (s[0].contains("Abs")) { - return Math.abs(num); - } else if (s[0].contains("LimitMax")) { - if (num < secondaryNum) { - return num; - } else { - return secondaryNum; - } - } else if (s[0].contains("LimitMin")) { - if (num > secondaryNum) { - return num; - } else { - return secondaryNum; - } - - } else { - return num; - } - } - - /** - *

- * handlePaid. - *

- * - * @param paidList - * a {@link forge.CardList} object. - * @param string - * a {@link java.lang.String} object. - * @param source - * a {@link forge.game.card.Card} object. - * @return a int. - */ - public static int handlePaid(final List paidList, final String string, final Card source) { - if (paidList == null) { - if (string.contains(".")) { - final String[] splitString = string.split("\\.", 2); - return doXMath(0, splitString[1], source); - } else { - return 0; - } - } - if (string.startsWith("Amount")) { - if (string.contains(".")) { - final String[] splitString = string.split("\\.", 2); - return doXMath(paidList.size(), splitString[1], source); - } else { - return paidList.size(); - } - - } - if (string.startsWith("Valid")) { - - final String[] splitString = string.split("/", 2); - String valid = splitString[0].substring(6); - final List list = CardLists.getValidCards(paidList, valid, source.getController(), source); - return doXMath(list.size(), splitString.length > 1 ? splitString[1] : null, source); - } - - String filteredString = string; - List filteredList = new ArrayList(paidList); - final String[] filter = filteredString.split("_"); - - if (string.startsWith("FilterControlledBy")) { - final String pString = filter[0].substring(18); - List controllers = new ArrayList(AbilityUtils.getDefinedPlayers(source, pString, null)); - filteredList = CardLists.filterControlledBy(filteredList, controllers); - filteredString = filteredString.replace(pString, ""); - filteredString = filteredString.replace("FilterControlledBy_", ""); - } - - int tot = 0; - for (final Card c : filteredList) { - tot += xCount(c, filteredString); - } - - return tot; - } - - /** - *

- * isMostProminentColor. - *

- * - * @param list - * a {@link forge.CardList} object. - * @return a boolean. - */ - public static byte getMostProminentColors(final List list) { - int cntColors = MagicColor.WUBRG.length; - final Integer[] map = new Integer[cntColors]; - for(int i = 0; i < cntColors; i++) { - map[i] = 0; - } - - for (final Card crd : list) { - ColorSet color = CardUtil.getColors(crd); - for(int i = 0; i < cntColors; i++) { - if( color.hasAnyColor(MagicColor.WUBRG[i])) - map[i]++; - } - } // for - - byte mask = 0; - int nMax = -1; - for(int i = 0; i < cntColors; i++) { - if ( map[i] > nMax ) - mask = MagicColor.WUBRG[i]; - else if ( map[i] == nMax ) - mask |= MagicColor.WUBRG[i]; - else - continue; - nMax = map[i]; - } - return mask; - } - - /** - *

- * SortColorsFromList. - *

- * - * @param list - * a {@link forge.CardList} object. - * @return a List. - */ - public static int[] SortColorsFromList(final List list) { - int cntColors = MagicColor.WUBRG.length; - final int[] map = new int[cntColors]; - for(int i = 0; i < cntColors; i++) { - map[i] = 0; - } - - for (final Card crd : list) { - ColorSet color = CardUtil.getColors(crd); - for(int i = 0; i < cntColors; i++) { - if( color.hasAnyColor(MagicColor.WUBRG[i])) - map[i]++; - } - } // for - Arrays.sort(map); - return map; - } - - /** - *

- * getMostProminentColorsFromList. - *

- * - * @param list - * a {@link forge.CardList} object. - * @return a boolean. - */ - public static byte getMostProminentColorsFromList(final List list, final List restrictedToColors) { - List colorRestrictions = new ArrayList(); - for (final String col : restrictedToColors) { - colorRestrictions.add(MagicColor.fromName(col)); - } - int cntColors = colorRestrictions.size(); - final Integer[] map = new Integer[cntColors]; - for(int i = 0; i < cntColors; i++) { - map[i] = 0; - } - - for (final Card crd : list) { - ColorSet color = CardUtil.getColors(crd); - for (int i = 0; i < cntColors; i++) { - if (color.hasAnyColor(colorRestrictions.get(i))) { - map[i]++; - } - } - } - - byte mask = 0; - int nMax = -1; - for(int i = 0; i < cntColors; i++) { - if ( map[i] > nMax ) - mask = colorRestrictions.get(i); - else if ( map[i] == nMax ) - mask |= colorRestrictions.get(i); - else - continue; - nMax = map[i]; - } - return mask; - } - - /** - *

- * getMostProminentCreatureType. - *

- * - * @param list - * a {@link forge.CardList} object. - * @return an int. - */ - public static int getMostProminentCreatureTypeSize(final List list) { - - if (list.isEmpty()) { - return 0; - } - int allCreatureType = 0; - - final Map map = new HashMap(); - for (final Card c : list) { - // Remove Duplicated types - final Set types = new HashSet(c.getType()); - if (types.contains("AllCreatureTypes")) { - allCreatureType++; - continue; - } - for (final String var : types) { - if (CardType.isACreatureType(var)) { - if (!map.containsKey(var)) { - map.put(var, 1); - } else { - map.put(var, map.get(var) + 1); - } - } - } - } - - int max = 0; - for (final Entry entry : map.entrySet()) { - if (max < entry.getValue()) { - max = entry.getValue(); - } - } - - return max + allCreatureType; - } - - /** - *

- * sharedKeywords. - *

- * - * @param kw - * a {@link forge.CardList} object. - * @return a List. - */ - public static List sharedKeywords(final String[] kw, final String[] restrictions, - final List zones, final Card host) { - final List filteredkw = new ArrayList(); - final Player p = host.getController(); - List cardlist = new ArrayList(p.getGame().getCardsIn(zones)); - final List landkw = new ArrayList(); - final List protectionkw = new ArrayList(); - final List allkw = new ArrayList(); - - cardlist = CardLists.getValidCards(cardlist, restrictions, p, host); - for (Card c : cardlist) { - for (String k : c.getKeyword()) { - if (k.endsWith("walk")) { - if (!landkw.contains(k)) { - landkw.add(k); - } - } else if (k.startsWith("Protection")) { - if (!protectionkw.contains(k)) { - protectionkw.add(k); - } - } - if (!allkw.contains(k)) { - allkw.add(k); - } - } - } - for (String keyword : kw) { - if (keyword.equals("Protection")) { - filteredkw.addAll(protectionkw); - } else if (keyword.equals("Landwalk")) { - filteredkw.addAll(landkw); - } else if (allkw.contains(keyword)) { - filteredkw.add(keyword); - } - } - return filteredkw; - } - - /** - *

- * getCardTypesFromList. - *

- * - * @param list - * a {@link forge.CardList} object. - * @return a int. - */ - public static int getCardTypesFromList(final List list) { - int count = 0; - for (Card c1 : list) { - if (c1.isCreature()) { - count++; - break; - } - } - for (Card c1 : list) { - if (c1.isSorcery()) { - count++; - break; - } - } - for (Card c1 : list) { - if (c1.isInstant()) { - count++; - break; - } - } - for (Card c1 : list) { - if (c1.isArtifact()) { - count++; - break; - } - } - for (Card c1 : list) { - if (c1.isEnchantment()) { - count++; - break; - } - } - for (Card c1 : list) { - if (c1.isLand()) { - count++; - break; - } - } - for (Card c1 : list) { - if (c1.isPlaneswalker()) { - count++; - break; - } - } - for (Card c1 : list) { - if (c1.isTribal()) { - count++; - break; - } - } - return count; - } - - /** - *

- * getBushidoEffects. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link java.util.ArrayList} object. - */ - public static ArrayList getBushidoEffects(final Card c) { - final ArrayList list = new ArrayList(); - for (final String kw : c.getKeyword()) { - if (kw.contains("Bushido")) { - final String[] parse = kw.split(" "); - final String s = parse[1]; - final int magnitude = Integer.parseInt(s); - - String description = String.format("Bushido %d (When this blocks or becomes blocked, it gets +%d/+%d until end of turn).", magnitude, magnitude, magnitude); - String regularPart = String.format("AB$ Pump | Cost$ 0 | Defined$ CardUID_%d | NumAtt$ +%d | NumDef$ +%d | StackDescription$ %s", c.getUniqueNumber(), magnitude, magnitude, description); - - SpellAbility ability = AbilityFactory.getAbility( regularPart, c); - ability.setDescription(ability.getStackDescription()); - ability.setTrigger(true); // can be copied by Strionic Resonator - list.add(ability); - } - } - return list; - } - - /** - *

- * getNeededXDamage. - *

- * - * @param ability - * a {@link forge.game.spellability.SpellAbility} object. - * @return a int. - */ - public static int getNeededXDamage(final SpellAbility ability) { - // when targeting a creature, make sure the AI won't overkill on X - // damage - final Card target = ability.getTargetCard(); - int neededDamage = -1; - - if ((target != null)) { - neededDamage = target.getNetDefense() - target.getDamage(); - } - - return neededDamage; - } - - public static void correctAbilityChainSourceCard(final SpellAbility sa, final Card card) { - - sa.setSourceCard(card); - - if (sa.getSubAbility() != null) { - correctAbilityChainSourceCard(sa.getSubAbility(), card); - } - } - - /** - * Adds the ability factory abilities. - * - * @param card - * the card - */ - public static final void addAbilityFactoryAbilities(final Card card) { - // ************************************************** - // AbilityFactory cards - for (String rawAbility : card.getUnparsedAbilities()) { - card.addSpellAbility(AbilityFactory.getAbility(rawAbility, card)); - } - } - /* - public static final void addCommanderAbilities(final Card cmd) { - ReplacementEffect re = ReplacementHandler.parseReplacement( - "Event$ Moved | Destination$ Graveyard,Exile | ValidCard$ Card.Self | Secondary$ True | Optional$ True | OptionalDecider$ You | ReplaceWith$ CommanderMoveReplacement | " + - "Description$ If a commander would be put into its owner's graveyard or exile from anywhere, that player may put it into the command zone instead.", - cmd, true); - cmd.addReplacementEffect(re); - if(StringUtils.isBlank(cmd.getSVar("CommanderCostRaise"))) // why condition check is needed? - cmd.setSVar("CommanderCostRaise", "Number$0"); - - String cmdManaCost = cmd.getManaCost().toString(); - cmd.setSVar("CommanderMoveReplacement", "DB$ ChangeZone | Origin$ Battlefield,Graveyard,Exile,Library | Destination$ Command | Defined$ ReplacedCard"); - cmd.setSVar("DBCommanderIncCast", "DB$ StoreSVar | SVar$ CommanderCostRaise | Type$ CountSVar | Expression$ CommanderCostRaise/Plus.2"); - SpellAbility sa = AbilityFactory.getAbility("SP$ PermanentCreature | SorcerySpeed$ True | ActivationZone$ Command | SubAbility$ DBCommanderIncCast | Cost$ " + cmdManaCost, cmd); - cmd.addSpellAbility(sa); - - cmd.addIntrinsicAbility("SP$ PermanentCreature | SorcerySpeed$ True | ActivationZone$ Command | SubAbility$ DBCommanderIncCast | Cost$ " + cmdManaCost); - cmd.addStaticAbility("Mode$ RaiseCost | Amount$ CommanderCostRaise | Type$ Spell | ValidCard$ Card.Self+wasCastFromCommand | EffectZone$ All | AffectedZone$ Stack"); - } - */ - public static final String getCommanderInfo(final Player originPlayer ) { - StringBuilder sb = new StringBuilder(); - for(Player p : originPlayer.getGame().getPlayers()) { - if(p.equals(originPlayer)) - continue; - - Map map = p.getCommanderDamage(); - if(map.containsKey(originPlayer.getCommander())) { - sb.append("Commander Damage to " + p.getName() + ": "+ map.get(originPlayer.getCommander()) + "\r\n"); - } - } - - return sb.toString(); - } - - /** - *

- * postFactoryKeywords. - *

- * - * @param card - * a {@link forge.game.card.Card} object. - */ - public static void setupKeywordedAbilities(final Card card) { - // this function should handle any keywords that need to be added after - // a spell goes through the factory - // Cards with Cycling abilities - // -1 means keyword "Cycling" not found - - if (hasKeyword(card, "Multikicker") != -1) { - final int n = hasKeyword(card, "Multikicker"); - if (n != -1) { - final String parse = card.getKeyword().get(n).toString(); - final String[] k = parse.split("kicker "); - - final SpellAbility sa = card.getFirstSpellAbility(); - sa.setMultiKickerManaCost(new ManaCost(new ManaCostParser(k[1]))); - } - } - - if(hasKeyword(card, "Fuse") != -1) { - card.getState(CardCharacteristicName.Original).getSpellAbility().add(AbilityFactory.buildFusedAbility(card)); - } - - final int evokePos = hasKeyword(card, "Evoke"); - if (evokePos != -1) { - card.addSpellAbility(makeEvokeSpell(card, card.getKeyword().get(evokePos))); - } - final int monstrousPos = hasKeyword(card, "Monstrosity"); - if (monstrousPos != -1) { - final String parse = card.getKeyword().get(monstrousPos).toString(); - final String[] k = parse.split(":"); - final String magnitude = k[0].substring(12); - final String manacost = k[1]; - card.removeIntrinsicKeyword(parse); - - String ref = "X".equals(magnitude) ? " | References$ X" : ""; - String counters = StringUtils.isNumeric(magnitude) - ? Lang.nounWithNumeral(Integer.parseInt(magnitude), "+1/+1 counter"): "X +1/+1 counters"; - String effect = "AB$ PutCounter | Cost$ " + manacost + " | ConditionPresent$ " + - "Card.Self+IsNotMonstrous | Monstrosity$ True | CounterNum$ " + - magnitude + " | CounterType$ P1P1 | SpellDescription$ Monstrosity " + - magnitude + " (If this creature isn't monstrous, put " + - counters + " on it and it becomes monstrous.) | StackDescription$ SpellDescription" + ref; - - card.addSpellAbility(AbilityFactory.getAbility(effect, card)); - // add ability to instrinic strings so copies/clones create the ability also - card.getUnparsedAbilities().add(effect); - } - - final int iLvlUp = hasKeyword(card, "Level up"); - final int iLvlMax = hasKeyword(card, "maxLevel"); - - if (iLvlUp != -1 && iLvlMax != -1) { - final String strLevelCost = card.getKeyword().get(iLvlUp); - final String strMaxLevel = card.getKeyword().get(iLvlMax); - card.removeIntrinsicKeyword(strLevelCost); - card.removeIntrinsicKeyword(strMaxLevel); - final String[] k = strLevelCost.split(":"); - final String manacost = k[1]; - - final String[] l = strMaxLevel.split(":"); - final int maxLevel = Integer.parseInt(l[1]); - - String effect = "AB$ PutCounter | Cost$ " + manacost + " | " + - "SorcerySpeed$ True | LevelUp$ True | CounterNum$ 1" + - " | CounterType$ LEVEL | PrecostDesc$ Level Up | MaxLevel$ " + - maxLevel + " | SpellDescription$ (Put a level counter on" + - " this permanent. Activate this ability only any time you" + - " could cast a sorcery.)"; - - card.addSpellAbility(AbilityFactory.getAbility(effect, card)); - // add ability to instrinic strings so copies/clones create the ability also - card.getUnparsedAbilities().add(effect); - } // level up - - if (hasKeyword(card, "Cycling") != -1) { - final int n = hasKeyword(card, "Cycling"); - if (n != -1) { - final String parse = card.getKeyword().get(n).toString(); - card.removeIntrinsicKeyword(parse); - - final String[] k = parse.split(":"); - final String manacost = k[1]; - - card.addSpellAbility(abilityCycle(card, manacost)); - } - } // Cycling - - while (hasKeyword(card, "TypeCycling") != -1) { - final int n = hasKeyword(card, "TypeCycling"); - if (n != -1) { - final String parse = card.getKeyword().get(n).toString(); - card.removeIntrinsicKeyword(parse); - - final String[] k = parse.split(":"); - final String type = k[1]; - final String manacost = k[2]; - - card.addSpellAbility(abilityTypecycle(card, manacost, type)); - } - } // TypeCycling - - if (hasKeyword(card, "Transmute") != -1) { - final int n = hasKeyword(card, "Transmute"); - if (n != -1) { - final String parse = card.getKeyword().get(n); - card.removeIntrinsicKeyword(parse); - final String manacost = parse.split(":")[1]; - - card.addSpellAbility(abilityTransmute(card, manacost)); - } - } // transmute - - int shiftPos = hasKeyword(card, "Soulshift"); - while (shiftPos != -1) { - final int n = shiftPos; - final String parse = card.getKeyword().get(n); - final String[] k = parse.split(" "); - final int manacost = Integer.parseInt(k[1]); - - final String actualTrigger = "Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard" - + "| OptionalDecider$ You | ValidCard$ Card.Self | Execute$ SoulshiftAbility" - + "| TriggerController$ TriggeredCardController | TriggerDescription$ " + parse - + " (When this creature dies, you may return target Spirit card with converted mana cost " - + manacost + " or less from your graveyard to your hand.)"; - final String abString = "DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand" - + "| ValidTgts$ Spirit.YouOwn+cmcLE" + manacost; - final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, true); - card.addTrigger(parsedTrigger); - card.setSVar("SoulshiftAbility", abString); - shiftPos = hasKeyword(card, "Soulshift", n + 1); - } // Soulshift - - final int championPos = hasKeyword(card, "Champion"); - if (championPos != -1) { - String parse = card.getKeyword().get(championPos); - card.removeIntrinsicKeyword(parse); - - final String[] k = parse.split(":"); - final String[] valid = k[1].split(","); - String desc = k.length > 2 ? k[2] : k[1]; - - StringBuilder changeType = new StringBuilder(); - for (String v : valid) { - if (changeType.length() != 0) { - changeType.append(","); - } - changeType.append(v).append(".YouCtrl+Other"); - } - - StringBuilder trig = new StringBuilder(); - trig.append("Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | "); - trig.append("Execute$ ChampionAbility | TriggerDescription$ Champion a(n) "); - trig.append(desc).append(" (When this enters the battlefield, sacrifice it unless you exile another "); - trig.append(desc).append(" you control. When this leaves the battlefield, that card returns to the battlefield.)"); - - StringBuilder trigReturn = new StringBuilder(); - trigReturn.append("Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | "); - trigReturn.append("Execute$ ChampionReturn | Secondary$ True | TriggerDescription$ When this leaves the battlefield, that card returns to the battlefield."); - - StringBuilder ab = new StringBuilder(); - ab.append("DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | Champion$ True | "); - ab.append("Hidden$ True | Optional$ True | SubAbility$ DBSacrifice | ChangeType$ ").append(changeType); - - StringBuilder subAb = new StringBuilder(); - subAb.append("DB$ Sacrifice | Defined$ Card.Self | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"); - - String returnChampion = "DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield"; - final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig.toString(), card, true); - final Trigger parsedTrigReturn = TriggerHandler.parseTrigger(trigReturn.toString(), card, true); - card.addTrigger(parsedTrigger); - card.addTrigger(parsedTrigReturn); - card.setSVar("ChampionAbility", ab.toString()); - card.setSVar("ChampionReturn", returnChampion); - card.setSVar("DBSacrifice", subAb.toString()); - } - - if (card.hasKeyword("If CARDNAME would be put into a graveyard " - + "from anywhere, reveal CARDNAME and shuffle it into its owner's library instead.")) { - - String replacement = "Event$ Moved | Destination$ Graveyard | ValidCard$ Card.Self | ReplaceWith$ GraveyardToLibrary"; - String ab = "DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Library | Defined$ ReplacedCard | Reveal$ True | Shuffle$ True"; - - card.addReplacementEffect(ReplacementHandler.parseReplacement(replacement, card, true)); - card.setSVar("GraveyardToLibrary", ab); - } - - final int echoPos = hasKeyword(card, "Echo"); - if (echoPos != -1) { - // card.removeIntrinsicKeyword(parse); - final String[] k = card.getKeyword().get(echoPos).split(":"); - final String manacost = k[1]; - - card.setEchoCost(manacost); - - final Command intoPlay = new Command() { - - private static final long serialVersionUID = -7913835645603984242L; - - @Override - public void run() { - card.addExtrinsicKeyword("(Echo unpaid)"); - } - }; - card.addComesIntoPlayCommand(intoPlay); - } // echo - - if (hasKeyword(card, "Suspend") != -1) { - // Suspend:: - final int n = hasKeyword(card, "Suspend"); - if (n != -1) { - final String parse = card.getKeyword().get(n); - card.removeIntrinsicKeyword(parse); - card.setSuspend(true); - final String[] k = parse.split(":"); - - final String timeCounters = k[1]; - final String cost = k[2]; - card.addSpellAbility(abilitySuspendStatic(card, cost, timeCounters)); - addSuspendUpkeepTrigger(card); - addSuspendPlayTrigger(card); - } - } // Suspend - - if (hasKeyword(card, "Fading") != -1) { - final int n = hasKeyword(card, "Fading"); - if (n != -1) { - final String[] k = card.getKeyword().get(n).split(":"); - - card.addIntrinsicKeyword("etbCounter:FADE:" + k[1] + ":no Condition:no desc"); - - String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield " + - " | Execute$ TrigUpkeepFading | Secondary$ True | TriggerDescription$ At the beginning of " + - "your upkeep, remove a fade counter from CARDNAME. If you can't, sacrifice CARDNAME."; - - card.setSVar("TrigUpkeepFading", "AB$ RemoveCounter | Cost$ 0 | Defined$ Self | CounterType$ FADE" + - " | CounterNum$ 1 | RememberRemoved$ True | SubAbility$ DBUpkeepFadingSac"); - card.setSVar("DBUpkeepFadingSac","DB$ Sacrifice | SacValid$ Self | ConditionCheckSVar$ FadingCheckSVar" + - " | ConditionSVarCompare$ EQ0 | References$ FadingCheckSVar | SubAbility$ FadingCleanup"); - card.setSVar("FadingCleanup","DB$ Cleanup | ClearRemembered$ True"); - card.setSVar("FadingCheckSVar","Count$RememberedSize"); - final Trigger parsedUpkeepTrig = TriggerHandler.parseTrigger(upkeepTrig, card, true); - card.addTrigger(parsedUpkeepTrig); - } - } // Fading - - if (hasKeyword(card, "Vanishing") != -1) { - final int n = hasKeyword(card, "Vanishing"); - if (n != -1) { - final String[] k = card.getKeyword().get(n).split(":"); - // etbcounter - card.addIntrinsicKeyword("etbCounter:TIME:" + k[1] + ":no Condition:no desc"); - // Remove Time counter trigger - String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | " + - "TriggerZones$ Battlefield | IsPresent$ Card.Self+counters_GE1_TIME" + - " | Execute$ TrigUpkeepVanishing | TriggerDescription$ At the " + - "beginning of your upkeep, if CARDNAME has a time counter on it, " + - "remove a time counter from it. | Secondary$ True"; - card.setSVar("TrigUpkeepVanishing", "AB$ RemoveCounter | Cost$ 0 | Defined$ Self" + - " | CounterType$ TIME | CounterNum$ 1"); - final Trigger parsedUpkeepTrig = TriggerHandler.parseTrigger(upkeepTrig, card, true); - card.addTrigger(parsedUpkeepTrig); - // sacrifice trigger - String sacTrig = "Mode$ CounterRemoved | TriggerZones$ Battlefield | ValidCard$" + - " Card.Self | NewCounterAmount$ 0 | Secondary$ True | CounterType$ TIME |" + - " Execute$ TrigVanishingSac | TriggerDescription$ When the last time " + - "counter is removed from CARDNAME, sacrifice it."; - card.setSVar("TrigVanishingSac", "AB$ Sacrifice | Cost$ 0 | SacValid$ Self"); - - final Trigger parsedSacTrigger = TriggerHandler.parseTrigger(sacTrig, card, true); - card.addTrigger(parsedSacTrigger); - } - } // Vanishing - - // AddCost - if (card.hasSVar("FullCost")) { - final SpellAbility sa1 = card.getFirstSpellAbility(); - if (sa1 != null && sa1.isSpell()) { - sa1.setPayCosts(new Cost(card.getSVar("FullCost"), sa1.isAbility())); - } - } - - // AltCost - String altCost = card.getSVar("AltCost"); - if (StringUtils.isNotBlank(altCost)) { - final SpellAbility sa1 = card.getFirstSpellAbility(); - if (sa1 != null && sa1.isSpell()) { - card.addSpellAbility(makeAltCostAbility(card, altCost, sa1)); - } - } - if (card.hasKeyword("Delve")) { - card.getSpellAbilities().get(0).setDelve(true); - } - - if (card.hasStartOfKeyword("Haunt")) { - setupHauntSpell(card); - } - - if (card.hasKeyword("Provoke")) { - final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | " - + "OptionalDecider$ You | Execute$ ProvokeAbility | Secondary$ True | TriggerDescription$ " - + "When this attacks, you may have target creature defending player " - + "controls untap and block it if able."; - final String abString = "DB$ MustBlock | ValidTgts$ Creature.DefenderCtrl | " - + "TgtPrompt$ Select target creature defending player controls | SubAbility$ DBUntap"; - final String dbString = "DB$ Untap | Defined$ Targeted"; - final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, true); - card.addTrigger(parsedTrigger); - card.setSVar("ProvokeAbility", abString); - card.setSVar("DBUntap", dbString); - } - - if (card.hasKeyword("Living Weapon")) { - card.removeIntrinsicKeyword("Living Weapon"); - - final StringBuilder sbTrig = new StringBuilder(); - sbTrig.append("Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | "); - sbTrig.append("ValidCard$ Card.Self | Execute$ TrigGerm | TriggerDescription$ "); - sbTrig.append("Living Weapon (When this Equipment enters the battlefield, "); - sbTrig.append("put a 0/0 black Germ creature token onto the battlefield, then attach this to it.)"); - - final StringBuilder sbGerm = new StringBuilder(); - sbGerm.append("DB$ Token | TokenAmount$ 1 | TokenName$ Germ | TokenTypes$ Creature,Germ | RememberTokens$ True | "); - sbGerm.append("TokenOwner$ You | TokenColors$ Black | TokenPower$ 0 | TokenToughness$ 0 | TokenImage$ B 0 0 Germ | SubAbility$ DBGermAttach"); - - final StringBuilder sbAttach = new StringBuilder(); - sbAttach.append("DB$ Attach | Defined$ Remembered | SubAbility$ DBGermClear"); - - final StringBuilder sbClear = new StringBuilder(); - sbClear.append("DB$ Cleanup | ClearRemembered$ True"); - - card.setSVar("TrigGerm", sbGerm.toString()); - card.setSVar("DBGermAttach", sbAttach.toString()); - card.setSVar("DBGermClear", sbClear.toString()); - - final Trigger etbTrigger = TriggerHandler.parseTrigger(sbTrig.toString(), card, true); - card.addTrigger(etbTrigger); - } - - if (card.hasKeyword("Epic")) { - makeEpic(card); - } - - if (card.hasKeyword("Soulbond")) { - // Setup ETB trigger for card with Soulbond keyword - final String actualTriggerSelf = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | " - + "ValidCard$ Card.Self | Execute$ TrigBondOther | OptionalDecider$ You | " - + "IsPresent$ Creature.Other+YouCtrl+NotPaired | Secondary$ True | " - + "TriggerDescription$ When CARDNAME enters the battlefield, " - + "you may pair CARDNAME with another unpaired creature you control"; - final String abStringSelf = "AB$ Bond | Cost$ 0 | Defined$ Self | ValidCards$ Creature.Other+YouCtrl+NotPaired"; - final Trigger parsedTriggerSelf = TriggerHandler.parseTrigger(actualTriggerSelf, card, true); - card.addTrigger(parsedTriggerSelf); - card.setSVar("TrigBondOther", abStringSelf); - // Setup ETB trigger for other creatures you control - final String actualTriggerOther = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | " - + "ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | OptionalDecider$ You | " - + "Execute$ TrigBondSelf | IsPresent$ Creature.Self+NotPaired | Secondary$ True | " - + " TriggerDescription$ When another unpaired creature you control enters the battlefield, " - + "you may pair it with CARDNAME"; - final String abStringOther = "AB$ Bond | Cost$ 0 | Defined$ TriggeredCard | ValidCards$ Creature.Self+NotPaired"; - final Trigger parsedTriggerOther = TriggerHandler.parseTrigger(actualTriggerOther, card, true); - card.addTrigger(parsedTriggerOther); - card.setSVar("TrigBondSelf", abStringOther); - } - - if (card.hasKeyword("Extort")) { - final String extortTrigger = "Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | " - + "TriggerZones$ Battlefield | Execute$ ExtortOpps | Secondary$ True" - + " | TriggerDescription$ Extort (Whenever you cast a spell, you may pay W/B. If you do, " - + "each opponent loses 1 life and you gain that much life.)"; - final String abString = "AB$ LoseLife | Cost$ WB | Defined$ Player.Opponent | " - + "LifeAmount$ 1 | SubAbility$ ExtortGainLife"; - final String dbString = "DB$ GainLife | Defined$ You | LifeAmount$ AFLifeLost | References$ AFLifeLost"; - final Trigger parsedTrigger = TriggerHandler.parseTrigger(extortTrigger, card, true); - card.addTrigger(parsedTrigger); - card.setSVar("ExtortOpps", abString); - card.setSVar("ExtortGainLife", dbString); - card.setSVar("AFLifeLost", "Number$0"); - } - - if (card.hasKeyword("Evolve")) { - final String evolveTrigger = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | " - + " ValidCard$ Creature.YouCtrl+Other | EvolveCondition$ True | " - + "TriggerZones$ Battlefield | Execute$ EvolveAddCounter | Secondary$ True | " - + "TriggerDescription$ Evolve (Whenever a creature enters the battlefield under your " - + "control, if that creature has greater power or toughness than this creature, put a " - + "+1/+1 counter on this creature.)"; - final String abString = "AB$ PutCounter | Cost$ 0 | Defined$ Self | CounterType$ P1P1 | " - + "CounterNum$ 1 | Evolve$ True"; - final Trigger parsedTrigger = TriggerHandler.parseTrigger(evolveTrigger, card, true); - card.addTrigger(parsedTrigger); - card.setSVar("EvolveAddCounter", abString); - } - - if (card.hasStartOfKeyword("Dredge")) { - final int dredgeAmount = card.getKeywordMagnitude("Dredge"); - - final String actualRep = "Event$ Draw | ActiveZones$ Graveyard | ValidPlayer$ You | " - + "ReplaceWith$ DredgeCards | Secondary$ True | Optional$ True | CheckSVar$ " - + "DredgeCheckLib | SVarCompare$ GE" + dredgeAmount + " | References$ " - + "DredgeCheckLib | AICheckDredge$ True | Description$ " + card.getName() - + " - Dredge " + dredgeAmount; - final String abString = "DB$ Mill | Defined$ You | NumCards$ " + dredgeAmount + " | " - + "SubAbility$ DredgeMoveToPlay"; - final String moveToPlay = "DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | " - + "Defined$ Self"; - final String checkSVar = "Count$ValidLibrary Card.YouOwn"; - card.setSVar("DredgeCards", abString); - card.setSVar("DredgeMoveToPlay", moveToPlay); - card.setSVar("DredgeCheckLib", checkSVar); - card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true)); - } - - if (card.hasStartOfKeyword("Tribute")) { - final int tributeAmount = card.getKeywordMagnitude("Tribute"); - - final String actualRep = "Event$ Moved | Destination$ Battlefield | ValidCard$ Card.Self |" - + " ReplaceWith$ TributeAddCounter | Secondary$ True | Description$ Tribute " - + tributeAmount + " (As this creature enters the battlefield, an opponent of your" - + " choice may place " + tributeAmount + " +1/+1 counter on it.)"; - final String abString = "DB$ PutCounter | Defined$ ReplacedCard | Tribute$ True | " - + "CounterType$ P1P1 | CounterNum$ " + tributeAmount - + " | SubAbility$ TributeMoveToPlay"; - final String moveToPlay = "DB$ ChangeZone | Origin$ All | Destination$ Battlefield | " - + "Defined$ ReplacedCard | Hidden$ True"; - card.setSVar("TributeAddCounter", abString); - card.setSVar("TributeMoveToPlay", moveToPlay); - card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true)); - } - - if (card.hasStartOfKeyword("Amplify")) { - // find position of Amplify keyword - final int ampPos = card.getKeywordPosition("Amplify"); - final String[] ampString = card.getKeyword().get(ampPos).split(":"); - final String amplifyMagnitude = ampString[1]; - final String suffix = !amplifyMagnitude.equals("1") ? "s" : ""; - final String ampTypes = ampString[2]; - String[] refinedTypes = ampTypes.split(","); - final StringBuilder types = new StringBuilder(); - for (int i = 0; i < refinedTypes.length; i++) { - types.append("Card.").append(refinedTypes[i]).append("+YouCtrl"); - if (i + 1 != refinedTypes.length) { - types.append(","); - } - } - // Setup ETB replacement effects - final String actualRep = "Event$ Moved | Destination$ Battlefield | ValidCard$ Card.Self |" - + " ReplaceWith$ AmplifyReveal | Secondary$ True | Description$ As this creature " - + "enters the battlefield, put " + amplifyMagnitude + " +1/+1 counter" + suffix - + " on it for each " + ampTypes.replace(",", " and/or ") - + " card you reveal in your hand.)"; - final String abString = "DB$ Reveal | AnyNumber$ True | RevealValid$ " - + types.toString() + " | RememberRevealed$ True | SubAbility$ AmplifyMoveToPlay"; - final String moveToPlay = "DB$ ChangeZone | Origin$ All | Destination$ Battlefield | " - + "Defined$ ReplacedCard | Hidden$ True | SubAbility$ Amplify"; - final String dbString = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | " - + "CounterNum$ AmpMagnitude | References$ Revealed,AmpMagnitude | SubAbility$" - + " DBCleanup"; - card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true)); - card.setSVar("AmplifyReveal", abString); - card.setSVar("AmplifyMoveToPlay", moveToPlay); - card.setSVar("Amplify", dbString); - card.setSVar("DBCleanup", "DB$ Cleanup | ClearRemembered$ True"); - card.setSVar("AmpMagnitude", "SVar$Revealed/Times." + amplifyMagnitude); - card.setSVar("Revealed", "Remembered$Amount"); - } - - if (card.hasStartOfKeyword("Equip")) { - // find position of Equip keyword - final int equipPos = card.getKeywordPosition("Equip"); - // Check for additional params such as preferred AI targets - final String equipString = card.getKeyword().get(equipPos).substring(5); - final String[] equipExtras = equipString.contains("|") ? equipString.split("\\|", 2) : null; - // Get cost string - String equipCost = ""; - if (equipExtras != null) { - equipCost = equipExtras[0].trim(); - } else { - equipCost = equipString.trim(); - } - // Create attach ability string - final StringBuilder abilityStr = new StringBuilder(); - abilityStr.append("AB$ Attach | Cost$ "); - abilityStr.append(equipCost); - abilityStr.append(" | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control "); - abilityStr.append("| SorcerySpeed$ True | Equip$ True | AILogic$ Pump | IsPresent$ Card.Self+nonCreature "); - if (equipExtras != null) { - abilityStr.append("| ").append(equipExtras[1]).append(" "); - } - abilityStr.append("| PrecostDesc$ Equip "); - Cost cost = new Cost(equipCost, true); - if (!cost.isOnlyManaCost()) { //Something other than a mana cost - abilityStr.append("- "); - } - abilityStr.append("| CostDesc$ " + cost.toSimpleString() + " "); - abilityStr.append("| SpellDescription$ (" + cost.toSimpleString() + ": Attach to target creature you control. Equip only as a sorcery.)"); - // instantiate attach ability - final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); - card.addSpellAbility(sa); - // add ability to instrinic strings so copies/clones create the ability also - card.getUnparsedAbilities().add(abilityStr.toString()); - } - - if (card.hasStartOfKeyword("Fortify")) { - final int equipPos = card.getKeywordPosition("Fortify"); - final String equipString = card.getKeyword().get(equipPos).substring(7); - final String[] equipExtras = equipString.contains("|") ? equipString.split("\\|", 2) : null; - // Get cost string - String equipCost = ""; - if (equipExtras != null) { - equipCost = equipExtras[0].trim(); - } else { - equipCost = equipString.trim(); - } - // Create attach ability string - final StringBuilder abilityStr = new StringBuilder(); - abilityStr.append("AB$ Attach | Cost$ "); - abilityStr.append(equipCost); - abilityStr.append(" | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select target land you control "); - abilityStr.append("| SorcerySpeed$ True | AILogic$ Pump | IsPresent$ Card.Self+nonCreature "); - if (equipExtras != null) { - abilityStr.append("| ").append(equipExtras[1]).append(" "); - } - abilityStr.append("| PrecostDesc$ Fortify "); - Cost cost = new Cost(equipCost, true); - if (!cost.isOnlyManaCost()) { //Something other than a mana cost - abilityStr.append("- "); - } - abilityStr.append("| CostDesc$ " + cost.toSimpleString() + " "); - abilityStr.append("| SpellDescription$ (" + cost.toSimpleString() + ": Attach to target land you control. Fortify only as a sorcery.)"); - - // instantiate attach ability - final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); - card.addSpellAbility(sa); - // add ability to instrinic strings so copies/clones create the ability also - card.getUnparsedAbilities().add(abilityStr.toString()); - } - - if (card.hasStartOfKeyword("Bestow")) { - final int bestowPos = card.getKeywordPosition("Bestow"); - final String cost = card.getKeyword().get(bestowPos).split(":")[1]; - card.removeIntrinsicKeyword(card.getKeyword().get(bestowPos)); - - final StringBuilder sbAttach = new StringBuilder(); - sbAttach.append("SP$ Attach | Cost$ "); - sbAttach.append(cost); - sbAttach.append(" | AILogic$ Pump | Bestow$ True | ValidTgts$ Creature"); - final SpellAbility bestow = AbilityFactory.getAbility(sbAttach.toString(), card); - - bestow.setDescription("Bestow " + ManaCostParser.parse(cost) + " (If you cast this" - + " card for its bestow cost, it's an Aura spell with enchant creature. It" - + " becomes a creature again if it's not attached to a creature.)"); - bestow.setStackDescription("Bestow - " + card.getName()); - bestow.setBasicSpell(false); - card.addSpellAbility(bestow); - card.getUnparsedAbilities().add(sbAttach.toString()); - - } - - setupEtbKeywords(card); - } - - /** - * TODO: Write javadoc for this method. - * @param card - */ - private static void setupEtbKeywords(final Card card) { - for (String kw : card.getKeyword()) { - - if (kw.startsWith("ETBReplacement")) { - String[] splitkw = kw.split(":"); - ReplacementLayer layer = ReplacementLayer.smartValueOf(splitkw[1]); - SpellAbility repAb = AbilityFactory.getAbility(card.getSVar(splitkw[2]), card); - String desc = repAb.getDescription(); - setupETBReplacementAbility(repAb); - - final String valid = splitkw.length >= 6 ? splitkw[5] : "Card.Self"; - - StringBuilder repEffsb = new StringBuilder(); - repEffsb.append("Event$ Moved | ValidCard$ ").append(valid); - repEffsb.append(" | Destination$ Battlefield | Description$ ").append(desc); - if (splitkw.length >= 4) { - if (splitkw[3].contains("Optional")) { - repEffsb.append(" | Optional$ True"); - } - } - if (splitkw.length >= 5) { - if (!splitkw[4].isEmpty()) { - repEffsb.append(" | ActiveZones$ " + splitkw[4]); - } - } - - ReplacementEffect re = ReplacementHandler.parseReplacement(repEffsb.toString(), card, true); - re.setLayer(layer); - re.setOverridingAbility(repAb); - - card.addReplacementEffect(re); - } else if (kw.startsWith("etbCounter")) { - String parse = kw; - card.removeIntrinsicKeyword(parse); - - String[] splitkw = parse.split(":"); - - String desc = "CARDNAME enters the battlefield with " + splitkw[2] + " " - + CounterType.valueOf(splitkw[1]).getName() + " counters on it."; - String extraparams = ""; - String amount = splitkw[2]; - if (splitkw.length > 3) { - if (!splitkw[3].equals("no Condition")) { - extraparams = splitkw[3]; - } - } - if (splitkw.length > 4) { - desc = !splitkw[4].equals("no desc") ? splitkw[4] : ""; - } - String abStr = "AB$ ChangeZone | Cost$ 0 | Hidden$ True | Origin$ All | Destination$ Battlefield" - + "| Defined$ ReplacedCard | SubAbility$ ETBCounterDBSVar"; - String dbStr = "DB$ PutCounter | Defined$ Self | CounterType$ " + splitkw[1] + " | CounterNum$ " + amount; - try { - Integer.parseInt(amount); - } - catch (NumberFormatException ignored) { - dbStr += " | References$ " + amount; - } - card.setSVar("ETBCounterSVar", abStr); - card.setSVar("ETBCounterDBSVar", dbStr); - - String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield " - + "| ReplaceWith$ ETBCounterSVar | Description$ " + desc + (!extraparams.equals("") ? " | " + extraparams : ""); - - ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true); - re.setLayer(ReplacementLayer.Other); - - card.addReplacementEffect(re); - } else if (kw.equals("CARDNAME enters the battlefield tapped.")) { - String parse = kw; - card.removeIntrinsicKeyword(parse); - - String abStr = "AB$ Tap | Cost$ 0 | Defined$ Self | ETB$ True | SubAbility$ MoveETB"; - String dbStr = "DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Battlefield" - + "| Defined$ ReplacedCard"; - - card.setSVar("ETBTappedSVar", abStr); - card.setSVar("MoveETB", dbStr); - - String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield " - + "| ReplaceWith$ ETBTappedSVar | Description$ CARDNAME enters the battlefield tapped."; - - ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true); - re.setLayer(ReplacementLayer.Other); - - card.addReplacementEffect(re); - } - } - } - - /** - * TODO: Write javadoc for this method. - * @param card - * @return - */ - private static void makeEpic(final Card card) { - - // Add the Epic effect as a subAbility - String dbStr = "DB$ Effect | Triggers$ EpicTrigger | SVars$ EpicCopy | StaticAbilities$ EpicCantBeCast | Duration$ Permanent | Unique$ True"; - - final AbilitySub newSA = (AbilitySub) AbilityFactory.getAbility(dbStr.toString(), card); - - card.setSVar("EpicCantBeCast", "Mode$ CantBeCast | ValidCard$ Card | Caster$ You | EffectZone$ Command | Description$ For the rest of the game, you can't cast spells."); - card.setSVar("EpicTrigger", "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ EpicCopy | TriggerDescription$ " - + "At the beginning of each of your upkeeps, copy " + card.toString() + " except for its epic ability."); - card.setSVar("EpicCopy", "DB$ CopySpellAbility | Defined$ EffectSource"); - - final SpellAbility origSA = card.getSpellAbilities().get(0); - - SpellAbility child = origSA; - while (child.getSubAbility() != null) { - child = child.getSubAbility(); - } - child.setSubAbility(newSA); - newSA.setParent(child); - } - - /** - * TODO: Write javadoc for this method. - * @param card - */ - private static void setupHauntSpell(final Card card) { - final int hauntPos = card.getKeywordPosition("Haunt"); - final String[] splitKeyword = card.getKeyword().get(hauntPos).split(":"); - final String hauntSVarName = splitKeyword[1]; - final String abilityDescription = splitKeyword[2]; - final String hauntAbilityDescription = abilityDescription.substring(0, 1).toLowerCase() - + abilityDescription.substring(1); - String hauntDescription; - if (card.isCreature()) { - final StringBuilder sb = new StringBuilder(); - sb.append("When ").append(card.getName()); - sb.append(" enters the battlefield or the creature it haunts dies, "); - sb.append(hauntAbilityDescription); - hauntDescription = sb.toString(); - } else { - final StringBuilder sb = new StringBuilder(); - sb.append("When the creature ").append(card.getName()); - sb.append(" haunts dies, ").append(hauntAbilityDescription); - hauntDescription = sb.toString(); - } - - card.getKeyword().remove(hauntPos); - - // First, create trigger that runs when the haunter goes to the - // graveyard - final StringBuilder sbHaunter = new StringBuilder(); - sbHaunter.append("Mode$ ChangesZone | Origin$ Battlefield | "); - sbHaunter.append("Destination$ Graveyard | ValidCard$ Card.Self | "); - sbHaunter.append("Static$ True | Secondary$ True | TriggerDescription$ Blank"); - - final Trigger haunterDies = TriggerHandler.parseTrigger(sbHaunter.toString(), card, true); - - final Ability haunterDiesWork = new Ability(card, ManaCost.ZERO) { - @Override - public void resolve() { - this.getTargets().getFirstTargetedCard().addHauntedBy(card); - card.getGame().getAction().exile(card); - } - }; - haunterDiesWork.setDescription(hauntDescription); - haunterDiesWork.setTargetRestrictions(new TargetRestrictions(null, new String[]{"Creature"}, "1", "1")); // not null to make stack preserve targets set - - final Ability haunterDiesSetup = new Ability(card, ManaCost.ZERO) { - @Override - public void resolve() { - final Game game = card.getGame(); - this.setActivatingPlayer(card.getController()); - haunterDiesWork.setActivatingPlayer(card.getController()); - List allCreatures = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); - final List creats = CardLists.getTargetableCards(allCreatures, haunterDiesWork); - if (creats.isEmpty()) { - return; - } - - final Card toHaunt = card.getController().getController().chooseSingleEntityForEffect(creats, new SpellAbility.EmptySa(ApiType.InternalHaunt, card), "Choose target creature to haunt."); - haunterDiesWork.setTargetCard(toHaunt); - haunterDiesWork.setActivatingPlayer(card.getController()); - game.getStack().add(haunterDiesWork); - } - }; - - haunterDies.setOverridingAbility(haunterDiesSetup); - - // Second, create the trigger that runs when the haunted creature dies - final StringBuilder sbDies = new StringBuilder(); - sbDies.append("Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | "); - sbDies.append("ValidCard$ Creature.HauntedBy | Execute$ ").append(hauntSVarName); - sbDies.append(" | TriggerDescription$ ").append(hauntDescription); - - final Trigger hauntedDies = forge.game.trigger.TriggerHandler.parseTrigger(sbDies.toString(), card, true); - - // Third, create the trigger that runs when the haunting creature - // enters the battlefield - final StringBuilder sbETB = new StringBuilder(); - sbETB.append("Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ "); - sbETB.append(hauntSVarName).append(" | Secondary$ True | TriggerDescription$ "); - sbETB.append(hauntDescription); - - final Trigger haunterETB = forge.game.trigger.TriggerHandler.parseTrigger(sbETB.toString(), card, true); - - // Fourth, create a trigger that removes the haunting status if the - // haunter leaves the exile - final StringBuilder sbUnExiled = new StringBuilder(); - sbUnExiled.append("Mode$ ChangesZone | Origin$ Exile | Destination$ Any | "); - sbUnExiled.append("ValidCard$ Card.Self | Static$ True | Secondary$ True | "); - sbUnExiled.append("TriggerDescription$ Blank"); - - final Trigger haunterUnExiled = forge.game.trigger.TriggerHandler.parseTrigger(sbUnExiled.toString(), card, - true); - - final Ability haunterUnExiledWork = new Ability(card, ManaCost.ZERO) { - @Override - public void resolve() { - if (card.getHaunting() != null) { - card.getHaunting().removeHauntedBy(card); - card.setHaunting(null); - } - } - }; - - haunterUnExiled.setOverridingAbility(haunterUnExiledWork); - - // Fifth, add all triggers and abilities to the card. - if (card.isCreature()) { - card.addTrigger(haunterETB); - card.addTrigger(haunterDies); - } else { - final String abString = card.getSVar(hauntSVarName).replace("AB$", "SP$") - .replace("Cost$ 0", "Cost$ " + card.getManaCost()) - + " | SpellDescription$ " + abilityDescription; - - final SpellAbility sa = AbilityFactory.getAbility(abString, card); - card.addSpellAbility(sa); - } - - card.addTrigger(hauntedDies); - card.addTrigger(haunterUnExiled); - } - - /** - * TODO: Write javadoc for this method. - * @param card - * @param abilities - * @return - */ - private static SpellAbility makeAltCostAbility(final Card card, final String altCost, final SpellAbility sa) { - final Map params = AbilityFactory.getMapParams(altCost); - - final SpellAbility altCostSA = sa.copy(); - final Cost abCost = new Cost(params.get("Cost"), altCostSA.isAbility()); - altCostSA.setPayCosts(abCost); - altCostSA.setBasicSpell(false); - altCostSA.addOptionalCost(OptionalCost.AltCost); - - final SpellAbilityRestriction restriction = new SpellAbilityRestriction(); - restriction.setRestrictions(params); - if (!params.containsKey("ActivationZone")) { - restriction.setZone(ZoneType.Hand); - } - altCostSA.setRestrictions(restriction); - - final String costDescription = params.containsKey("Description") ? params.get("Description") - : String.format("You may %s rather than pay %s's mana cost.", abCost.toStringAlt(), card.getName()); - - altCostSA.setDescription(costDescription); - return altCostSA; - } - - /** - * TODO: Write javadoc for this method. - * @param card - * @param evokeKeyword - * @return - */ - private static SpellAbility makeEvokeSpell(final Card card, final String evokeKeyword) { - final String[] k = evokeKeyword.split(":"); - final Cost evokedCost = new Cost(k[1], false); - - final SpellAbility evokedSpell = new Spell(card, evokedCost) { - private static final long serialVersionUID = -1598664196463358630L; - - @Override - public void resolve() { - final Game game = card.getGame(); - card.setEvoked(true); - game.getAction().moveToPlay(card); - } - - @Override - public boolean canPlayAI(Player aiPlayer) { - final Game game = card.getGame(); - if (!SpellPermanent.checkETBEffects(card, aiPlayer)) { - return false; - } - // Wait for Main2 if possible - if (game.getPhaseHandler().is(PhaseType.MAIN1) - && game.getPhaseHandler().isPlayerTurn(aiPlayer) - && aiPlayer.getManaPool().totalMana() <= 0 - && !ComputerUtil.castPermanentInMain1(aiPlayer, this)) { - return false; - } - - return super.canPlayAI(aiPlayer); - } - }; - card.removeIntrinsicKeyword(evokeKeyword); - final StringBuilder desc = new StringBuilder(); - desc.append("Evoke ").append(evokedCost.toSimpleString()); - desc.append(" (You may cast this spell for its evoke cost. "); - desc.append("If you do, when it enters the battlefield, sacrifice it.)"); - - evokedSpell.setDescription(desc.toString()); - - final StringBuilder sb = new StringBuilder(); - sb.append(card.getName()).append(" (Evoked)"); - evokedSpell.setStackDescription(sb.toString()); - evokedSpell.setBasicSpell(false); - return evokedSpell; - } - - private static final Map emptyMap = new TreeMap(); - public static void setupETBReplacementAbility(SpellAbility sa) { - sa.appendSubAbility(new AbilitySub(ApiType.InternalEtbReplacement, sa.getSourceCard(), null, emptyMap)); - // ETBReplacementMove(sa.getSourceCard(), null)); - } - - /** - *

- * hasKeyword. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param k - * a {@link java.lang.String} object. - * @return a int. - */ - public static final int hasKeyword(final Card c, final String k) { - return hasKeyword(c, k, 0); - } - - /** - *

- * hasKeyword. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param k - * a {@link java.lang.String} object. - * @param startPos - * a int. - * @return a int. - */ - private static final int hasKeyword(final Card c, final String k, final int startPos) { - final List a = c.getKeyword(); - for (int i = startPos; i < a.size(); i++) { - if (a.get(i).startsWith(k)) { - return i; - } - } - - return -1; - } - - /** - *

- * parseKeywords. - *

- * Pulling out the parsing of keywords so it can be used by the token - * generator - * - * @param card - * a {@link forge.game.card.Card} object. - * @param cardName - * a {@link java.lang.String} object. - * - */ - public static final void parseKeywords(final Card card, final String cardName) { - if (card.hasKeyword("CARDNAME enters the battlefield tapped unless you control two or fewer other lands.")) { - card.addComesIntoPlayCommand(new Command() { - private static final long serialVersionUID = 6436821515525468682L; - - @Override - public void run() { - final List lands = card.getController().getLandsInPlay(); - lands.remove(card); - if (!(lands.size() <= 2)) { - // it enters the battlefield this way, and should not - // fire triggers - card.setTapped(true); - } - } - }); - } - if (hasKeyword(card, "CARDNAME enters the battlefield tapped unless you control a") != -1) { - final int n = hasKeyword(card, - "CARDNAME enters the battlefield tapped unless you control a"); - final String parse = card.getKeyword().get(n).toString(); - - String splitString; - if (parse.contains(" or a ")) { - splitString = " or a "; - } else { - splitString = " or an "; - } - - final String[] types = parse.substring(60, parse.length() - 1).split(splitString); - - card.addComesIntoPlayCommand(new Command() { - private static final long serialVersionUID = 403635232455049834L; - - @Override - public void run() { - final List clICtrl = card.getOwner().getCardsIn(ZoneType.Battlefield); - - boolean fnd = false; - - for (int i = 0; i < clICtrl.size(); i++) { - final Card c = clICtrl.get(i); - for (final String type : types) { - if (c.isType(type.trim())) { - fnd = true; - } - } - } - - if (!fnd) { - // it enters the battlefield this way, and should not - // fire triggers - card.setTapped(true); - } - } - }); - } - if (hasKeyword(card, "Sunburst") != -1) { - final Command sunburstCIP = new Command() { - private static final long serialVersionUID = 1489845860231758299L; - - @Override - public void run() { - if (card.isCreature()) { - card.addCounter(CounterType.P1P1, card.getSunburstValue(), true); - } else { - card.addCounter(CounterType.CHARGE, card.getSunburstValue(), true); - } - - } - }; - - final Command sunburstLP = new Command() { - private static final long serialVersionUID = -7564420917490677427L; - - @Override - public void run() { - card.setSunburstValue(0); - } - }; - - card.addComesIntoPlayCommand(sunburstCIP); - card.addLeavesPlayCommand(sunburstLP); - } - - // Enforce the "World rule" - if (card.isType("World")) { - final Command intoPlay = new Command() { - private static final long serialVersionUID = 6536398032388958127L; - - @Override - public void run() { - final Game game = card.getGame(); - final List cardsInPlay = CardLists.getType(game.getCardsIn(ZoneType.Battlefield), "World"); - cardsInPlay.remove(card); - for (int i = 0; i < cardsInPlay.size(); i++) { - game.getAction().sacrificeDestroy(cardsInPlay.get(i)); - } - } // execute() - }; // Command - card.addComesIntoPlayCommand(intoPlay); - } - - if (hasKeyword(card, "Morph") != -1) { - final int n = hasKeyword(card, "Morph"); - if (n != -1) { - - final String parse = card.getKeyword().get(n).toString(); - Map sVars = card.getSVars(); - - final String[] k = parse.split(":"); - final Cost cost = new Cost(k[1], true); - - card.addSpellAbility(abilityMorphDown(card)); - - card.turnFaceDown(); - - card.addSpellAbility(abilityMorphUp(card, cost)); - card.setSVars(sVars); // for Warbreak Trumpeter. - - card.setState(CardCharacteristicName.Original); - } - } // Morph - - if (hasKeyword(card, "Unearth") != -1) { - final int n = hasKeyword(card, "Unearth"); - if (n != -1) { - final String parse = card.getKeyword().get(n).toString(); - // card.removeIntrinsicKeyword(parse); - - final String[] k = parse.split(":"); - - final String manacost = k[1]; - - card.addSpellAbility(abilityUnearth(card, manacost)); - } - } // unearth - - if (hasKeyword(card, "Madness") != -1) { - final int n = hasKeyword(card, "Madness"); - if (n != -1) { - // Set Madness Replacement effects - String repeffstr = "Event$ Discard | ActiveZones$ Hand | ValidCard$ Card.Self | " + - "ReplaceWith$ DiscardMadness | Secondary$ True | Description$ If you would" + - " discard this card, you discard it, but may exile it instead of putting it" + - " into your graveyard"; - ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true); - card.addReplacementEffect(re); - String sVarMadness = "DB$ Discard | Defined$ ReplacedPlayer" + - " | Mode$ Defined | DefinedCards$ ReplacedCard | Madness$ True"; - card.setSVar("DiscardMadness", sVarMadness); - - // Set Madness Triggers - final String parse = card.getKeyword().get(n).toString(); - // card.removeIntrinsicKeyword(parse); - final String[] k = parse.split(":"); - String trigStr = "Mode$ Discarded | ValidCard$ Card.Self | IsMadness$ True | " + - "Execute$ TrigPlayMadness | Secondary$ True | TriggerDescription$ " + - "Play Madness - " + card.getName(); - final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, true); - card.addTrigger(myTrigger); - String playMadness = "AB$ Play | Cost$ 0 | Defined$ Self | PlayMadness$ " + k[1] + - " | Optional$ True | SubAbility$ DBWasNotPlayMadness | RememberPlayed$ True"; - String moveToYard = "DB$ ChangeZone | Defined$ Self | Origin$ Exile | " + - "Destination$ Graveyard | ConditionDefined$ Remembered | ConditionPresent$" + - " Card | ConditionCompare$ EQ0 | SubAbility$ DBMadnessCleanup"; - String cleanUp = "DB$ Cleanup | ClearRemembered$ True"; - card.setSVar("TrigPlayMadness", playMadness); - card.setSVar("DBWasNotPlayMadness", moveToYard); - card.setSVar("DBMadnessCleanup", cleanUp); - } - } // madness - - if (hasKeyword(card, "Miracle") != -1) { - final int n = hasKeyword(card, "Miracle"); - if (n != -1) { - final String parse = card.getKeyword().get(n).toString(); - // card.removeIntrinsicKeyword(parse); - - final String[] k = parse.split(":"); - card.setMiracleCost(new Cost(k[1], false)); - } - } // miracle - - if (hasKeyword(card, "Devour") != -1) { - final int n = hasKeyword(card, "Devour"); - if (n != -1) { - - final String parse = card.getKeyword().get(n).toString(); - // card.removeIntrinsicKeyword(parse); - - final String[] k = parse.split(":"); - final String magnitude = k[1]; - - String abStr = "AB$ ChangeZone | Cost$ 0 | Hidden$ True | Origin$ All | " - + "Destination$ Battlefield | Defined$ ReplacedCard | SubAbility$ DevourSac"; - String dbStr = "DB$ Sacrifice | Defined$ You | Amount$ DevourSacX | " - + "References$ DevourSacX | SacValid$ Creature.Other | SacMessage$ creature (Devour " - + magnitude + ") | RememberSacrificed$ True | Optional$ True | " - + "Devour$ True | SubAbility$ DevourCounters"; - String counterStr = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ DevourX" - + " | References$ DevourX,DevourSize | SubAbility$ DevourCleanup"; - - card.setSVar("DevourETB", abStr); - card.setSVar("DevourSac", dbStr); - card.setSVar("DevourSacX", "Count$Valid Creature.YouCtrl+Other"); - card.setSVar("DevourCounters", counterStr); - card.setSVar("DevourX", "SVar$DevourSize/Times." + magnitude); - card.setSVar("DevourSize", "Count$RememberedSize"); - card.setSVar("DevourCleanup", "DB$ Cleanup | ClearRemembered$ True"); - - String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$ DevourETB"; - - ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true); - re.setLayer(ReplacementLayer.Other); - card.addReplacementEffect(re); - } - } // Devour - - if (hasKeyword(card, "Modular") != -1) { - final int n = hasKeyword(card, "Modular"); - if (n != -1) { - final String parse = card.getKeyword().get(n).toString(); - card.getKeyword().remove(parse); - - final int m = Integer.parseInt(parse.substring(8)); - - card.addIntrinsicKeyword("etbCounter:P1P1:" + m + ":no Condition: " + - "Modular " + m + " (This enters the battlefield with " + m + " +1/+1 counters on it. When it's put into a graveyard, " + - "you may put its +1/+1 counters on target artifact creature.)"); - - final String abStr = "AB$ PutCounter | Cost$ 0 | References$ ModularX | ValidTgts$ Artifact.Creature | " + - "TgtPrompt$ Select target artifact creature | CounterType$ P1P1 | CounterNum$ ModularX"; - card.setSVar("ModularTrig", abStr); - card.setSVar("ModularX", "TriggeredCard$CardCounters.P1P1"); - - String trigStr = "Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Graveyard" + - " | OptionalDecider$ TriggeredCardController | TriggerController$ TriggeredCardController | Execute$ ModularTrig | " + - "Secondary$ True | TriggerDescription$ When CARDNAME is put into a graveyard from the battlefield, " + - "you may put a +1/+1 counter on target artifact creature for each +1/+1 counter on CARDNAME"; - final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, true); - card.addTrigger(myTrigger); - } - } // Modular - - /* - * WARNING: must keep this keyword processing before etbCounter keyword - * processing. - */ - final int graft = hasKeyword(card, "Graft"); - if (graft != -1) { - final String parse = card.getKeyword().get(graft).toString(); - - final int m = Integer.parseInt(parse.substring(6)); - final String abStr = "AB$ MoveCounter | Cost$ 0 | Source$ Self | " - + "Defined$ TriggeredCard | CounterType$ P1P1 | CounterNum$ 1"; - card.setSVar("GraftTrig", abStr); - - String trigStr = "Mode$ ChangesZone | ValidCard$ Creature.Other | " - + "Origin$ Any | Destination$ Battlefield" - + " | TriggerZones$ Battlefield | OptionalDecider$ You | " - + "IsPresent$ Card.Self+counters_GE1_P1P1 | " - + "Execute$ GraftTrig | TriggerDescription$ " - + "Whenever another creature enters the battlefield, you " - + "may move a +1/+1 counter from this creature onto it."; - final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, true); - card.addTrigger(myTrigger); - - card.addIntrinsicKeyword("etbCounter:P1P1:" + m); - } - - final int bloodthirst = hasKeyword(card, "Bloodthirst"); - if (bloodthirst != -1) { - final String numCounters = card.getKeyword().get(bloodthirst).split(" ")[1]; - String desc = "Bloodthirst " - + numCounters + " (If an opponent was dealt damage this turn, this creature enters the battlefield with " - + numCounters + " +1/+1 counters on it.)"; - if (numCounters.equals("X")) { - desc = "Bloodthirst X (This creature enters the battlefield with X +1/+1 counters on it, " - + "where X is the damage dealt to your opponents this turn.)"; - card.setSVar("X", "Count$BloodthirstAmount"); - } - - card.addIntrinsicKeyword("etbCounter:P1P1:" + numCounters + ":Bloodthirst$ True:" + desc); - } // bloodthirst - - final int storm = card.getKeywordAmount("Storm"); - for (int i = 0; i < storm; i++) { - final StringBuilder trigScript = new StringBuilder( - "Mode$ SpellCast | ValidCard$ Card.Self | Execute$ Storm " - + "| TriggerDescription$ Storm (When you cast this spell, " - + "copy it for each spell cast before it this turn.)"); - - card.setSVar("Storm", "AB$ CopySpellAbility | Cost$ 0 | Defined$ TriggeredSpellAbility | Amount$ StormCount | References$ StormCount"); - card.setSVar("StormCount", "Count$StormCount"); - final Trigger stormTrigger = TriggerHandler.parseTrigger(trigScript.toString(), card, true); - - card.addTrigger(stormTrigger); - } // Storm - final int cascade = card.getKeywordAmount("Cascade"); - for (int i = 0; i < cascade; i++) { - final StringBuilder trigScript = new StringBuilder( - "Mode$ SpellCast | ValidCard$ Card.Self | Execute$ TrigCascade | Secondary$ " + - "True | TriggerDescription$ Cascade - CARDNAME"); - - final String abString = "AB$ DigUntil | Cost$ 0 | Defined$ You | Amount$ 1 | Valid$ " - + "Card.nonLand+cmcLTCascadeX | FoundDestination$ Exile | RevealedDestination$" - + " Exile | References$ CascadeX | ImprintRevealed$ True | RememberFound$ True" - + " | SubAbility$ CascadeCast"; - final String dbCascadeCast = "DB$ Play | Defined$ Remembered | WithoutManaCost$ True | " - + "Optional$ True | SubAbility$ CascadeMoveToLib"; - final String dbMoveToLib = "DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered,Card.IsImprinted" - + " | Origin$ Exile | Destination$ Library | RandomOrder$ True | LibraryPosition$ -1" - + " | SubAbility$ CascadeCleanup"; - card.setSVar("TrigCascade", abString); - card.setSVar("CascadeCast", dbCascadeCast); - card.setSVar("CascadeMoveToLib", dbMoveToLib); - card.setSVar("CascadeX", "Count$CardManaCost"); - card.setSVar("CascadeCleanup", "DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True"); - final Trigger cascadeTrigger = TriggerHandler.parseTrigger(trigScript.toString(), card, true); - - card.addTrigger(cascadeTrigger); - } // Cascade - - if (hasKeyword(card, "Recover") != -1) { - final String recoverCost = card.getKeyword().get(card.getKeywordPosition("Recover")).split(":")[1]; - final String abStr = "AB$ ChangeZone | Cost$ 0 | Defined$ Self" - + " | Origin$ Graveyard | Destination$ Hand | UnlessCost$ " - + recoverCost + " | UnlessPayer$ You | UnlessSwitched$ True" - + " | UnlessResolveSubs$ WhenNotPaid | SubAbility$ RecoverExile"; - card.setSVar("RecoverTrig", abStr); - card.setSVar("RecoverExile", "DB$ ChangeZone | Defined$ Self" - + " | Origin$ Graveyard | Destination$ Exile"); - String trigObject = card.isCreature() ? "Creature.Other+YouOwn" : "Creature.YouOwn"; - String trigArticle = card.isCreature() ? "another" : "a"; - String trigStr = "Mode$ ChangesZone | ValidCard$ " + trigObject - + " | Origin$ Battlefield | Destination$ Graveyard | " - + "TriggerZones$ Graveyard | Execute$ RecoverTrig | " - + "TriggerDescription$ When " + trigArticle + " creature is " - + "put into your graveyard from the battlefield, you " - + "may pay " + recoverCost + ". If you do, return " - + "CARDNAME from your graveyard to your hand. Otherwise," - + " exile CARDNAME. | Secondary$ True"; - final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, true); - card.addTrigger(myTrigger); - } // Recover - - int ripplePos = hasKeyword(card, "Ripple"); - while (ripplePos != -1) { - final int n = ripplePos; - final String parse = card.getKeyword().get(n); - final String[] k = parse.split(":"); - final int num = Integer.parseInt(k[1]); - UUID triggerSvar = UUID.randomUUID(); - - final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | " + - "Execute$ " + triggerSvar + " | Secondary$ True | TriggerDescription$" + - " Ripple " + num + " - CARDNAME | OptionalDecider$ You"; - final String abString = "AB$ Dig | Cost$ 0 | NoMove$ True | DigNum$ " + num + - " | Reveal$ True | RememberRevealed$ True | SubAbility$ DBCastRipple"; - final String dbCast = "DB$ Play | Valid$ Card.IsRemembered+sameName | " + - "ValidZone$ Library | WithoutManaCost$ True | Optional$ True | " + - "Amount$ All | SubAbility$ RippleMoveToBottom"; - - card.setSVar(triggerSvar.toString(), abString); - card.setSVar("DBCastRipple", dbCast); - card.setSVar("RippleMoveToBottom", "DB$ ChangeZoneAll | ChangeType$ " + - "Card.IsRemembered | Origin$ Library | Destination$ Library | " + - "LibraryPosition$ -1 | SubAbility$ RippleCleanup"); - card.setSVar("RippleCleanup", "DB$ Cleanup | ClearRemembered$ True"); - - final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, true); - card.addTrigger(parsedTrigger); - - ripplePos = hasKeyword(card, "Ripple", n + 1); - } // Ripple - } - - - public final static void refreshTotemArmor(Card c) { - boolean hasKw = c.hasKeyword("Totem armor"); - - List res = c.getReplacementEffects(); - for ( int ix = 0; ix < res.size(); ix++ ) { - ReplacementEffect re = res.get(ix); - if( re.getMapParams().containsKey("TotemArmor") ) { - if(hasKw) return; // has re and kw - nothing to do here - res.remove(ix--); - } - } - - if( hasKw ) { - ReplacementEffect re = ReplacementHandler.parseReplacement("Event$ Destroy | ActiveZones$ Battlefield | ValidCard$ Card.EnchantedBy | ReplaceWith$ RegenTA | Secondary$ True | TotemArmor$ True | Description$ Totem armor - " + c, c, true); - c.getSVars().put("RegenTA", "AB$ DealDamage | Cost$ 0 | Defined$ ReplacedCard | Remove$ All | SubAbility$ DestroyMe"); - c.getSVars().put("DestroyMe", "DB$ Destroy | Defined$ Self"); - c.getReplacementEffects().add(re); - } - } - -} // end class CardFactoryUtil +/* + * 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 . + */ +package forge.game.card; + +import java.util.*; +import java.util.Map.Entry; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.base.Predicate; +import com.google.common.collect.Lists; + +import forge.Command; +import forge.ai.ComputerUtil; +import forge.ai.ComputerUtilCost; +import forge.card.CardCharacteristicName; +import forge.card.CardType; +import forge.card.ColorSet; +import forge.card.MagicColor; +import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostParser; +import forge.card.mana.ManaCostShard; +import forge.game.Game; +import forge.game.GameEntity; +import forge.game.GameLogEntryType; +import forge.game.ability.AbilityFactory; +import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; +import forge.game.card.CardPredicates.Presets; +import forge.game.cost.Cost; +import forge.game.event.GameEventCardStatsChanged; +import forge.game.phase.PhaseHandler; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementHandler; +import forge.game.replacement.ReplacementLayer; +import forge.game.spellability.Ability; +import forge.game.spellability.AbilityActivated; +import forge.game.spellability.AbilityStatic; +import forge.game.spellability.AbilitySub; +import forge.game.spellability.OptionalCost; +import forge.game.spellability.Spell; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityRestriction; +import forge.game.spellability.SpellPermanent; +import forge.game.spellability.TargetRestrictions; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerHandler; +import forge.game.zone.Zone; +import forge.game.zone.ZoneType; +import forge.util.Aggregates; +import forge.util.Lang; + +/** + *

+ * CardFactoryUtil class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class CardFactoryUtil { + + /** + *

+ * abilityUnearth. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + * @param manaCost + * a {@link java.lang.String} object. + * @return a {@link forge.game.spellability.AbilityActivated} object. + */ + public static AbilityActivated abilityUnearth(final Card sourceCard, final String manaCost) { + + final Cost cost = new Cost(manaCost, true); + class AbilityUnearth extends AbilityActivated { + public AbilityUnearth(final Card ca, final Cost co, final TargetRestrictions t) { + super(ca, co, t); + } + + @Override + public AbilityActivated getCopy() { + AbilityActivated res = new AbilityUnearth(getSourceCard(), + getPayCosts(), getTargetRestrictions() == null ? null : new TargetRestrictions(getTargetRestrictions())); + CardFactory.copySpellAbility(this, res); + final SpellAbilityRestriction restrict = new SpellAbilityRestriction(); + restrict.setZone(ZoneType.Graveyard); + restrict.setSorcerySpeed(true); + res.setRestrictions(restrict); + return res; + } + + private static final long serialVersionUID = -5633945565395478009L; + + @Override + public void resolve() { + final Card card = sourceCard.getGame().getAction().moveToPlay(sourceCard); + + card.addHiddenExtrinsicKeyword("At the beginning of the end step, exile CARDNAME."); + card.addIntrinsicKeyword("Haste"); + card.setUnearthed(true); + } + + @Override + public boolean canPlayAI(Player aiPlayer) { + PhaseHandler phase = sourceCard.getGame().getPhaseHandler(); + if (phase.getPhase().isAfter(PhaseType.MAIN1) || !phase.isPlayerTurn(getActivatingPlayer())) { + return false; + } + return ComputerUtilCost.canPayCost(this, getActivatingPlayer()); + } + } + final AbilityActivated unearth = new AbilityUnearth(sourceCard, cost, null); + + final SpellAbilityRestriction restrict = new SpellAbilityRestriction(); + restrict.setZone(ZoneType.Graveyard); + restrict.setSorcerySpeed(true); + unearth.setRestrictions(restrict); + + final StringBuilder sbStack = new StringBuilder(); + sbStack.append("Unearth: ").append(sourceCard.getName()); + unearth.setStackDescription(sbStack.toString()); + + return unearth; + } // abilityUnearth() + + /** + *

+ * abilityMorphDown. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + * @return a {@link forge.game.spellability.SpellAbility} object. + */ + public static SpellAbility abilityMorphDown(final Card sourceCard) { + final Spell morphDown = new Spell(sourceCard, new Cost(ManaCost.THREE, false)) { + private static final long serialVersionUID = -1438810964807867610L; + + @Override + public void resolve() { + Card c = sourceCard.getGame().getAction().moveToPlay(sourceCard); + c.setPreFaceDownCharacteristic(CardCharacteristicName.Original); + } + + @Override + public boolean canPlay() { + //Lands do not have SpellPermanents. + if (sourceCard.isLand()) { + return (sourceCard.getGame().getZoneOf(sourceCard).is(ZoneType.Hand) || sourceCard.hasKeyword("May be played")) + && sourceCard.getController().canCastSorcery(); + } + else { + return sourceCard.getSpellPermanent().canPlay(); + } + } + }; + + morphDown.setDescription("(You may cast this face down as a 2/2 creature for {3}.)"); + morphDown.setStackDescription("Morph - Creature 2/2"); + morphDown.setCastFaceDown(true); + + return morphDown; + } + + /** + *

+ * abilityMorphUp. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + * @param cost + * a {@link forge.game.cost.Cost} object. + * @return a {@link forge.game.spellability.AbilityActivated} object. + */ + public static AbilityStatic abilityMorphUp(final Card sourceCard, final Cost cost) { + final AbilityStatic morphUp = new AbilityStatic(sourceCard, cost, null) { + + @Override + public void resolve() { + if (sourceCard.turnFaceUp()) { + String sb = this.getActivatingPlayer() + " has unmorphed " + sourceCard.getName(); + sourceCard.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb); + sourceCard.getGame().fireEvent(new GameEventCardStatsChanged(sourceCard)); + } + } + + @Override + public boolean canPlay() { + return sourceCard.getController().equals(this.getActivatingPlayer()) && sourceCard.isFaceDown() + && sourceCard.isInPlay(); + } + + }; // morph_up + + String costDesc = cost.toString(); + // get rid of the ": " at the end + costDesc = costDesc.substring(0, costDesc.length() - 2); + final StringBuilder sb = new StringBuilder(); + sb.append("Morph"); + if (!cost.isOnlyManaCost()) { + sb.append(" -"); + } + sb.append(" ").append(costDesc).append(" (Turn this face up any time for its morph cost.)"); + morphUp.setDescription(sb.toString()); + + final StringBuilder sbStack = new StringBuilder(); + sbStack.append(sourceCard.getName()).append(" - turn this card face up."); + morphUp.setStackDescription(sbStack.toString()); + morphUp.setIsMorphUp(true); + + return morphUp; + } + + /** + *

+ * abilityCycle. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + * @param cycleCost + * a {@link java.lang.String} object. + * @return a {@link forge.game.spellability.SpellAbility} object. + */ + public static SpellAbility abilityCycle(final Card sourceCard, String cycleCost) { + StringBuilder sb = new StringBuilder(); + sb.append("AB$ Draw | Cost$ "); + sb.append(cycleCost); + sb.append(" Discard<1/CARDNAME> | ActivationZone$ Hand | PrecostDesc$ Cycling "); + sb.append("| SpellDescription$ Draw a card."); + + SpellAbility cycle = AbilityFactory.getAbility(sb.toString(), sourceCard); + cycle.setIsCycling(true); + + return cycle; + } // abilityCycle() + + /** + *

+ * abilityTypecycle. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + * @param cycleCost + * a {@link java.lang.String} object. + * @param type + * a {@link java.lang.String} object. + * @return a {@link forge.game.spellability.SpellAbility} object. + */ + public static SpellAbility abilityTypecycle(final Card sourceCard, String cycleCost, final String type) { + StringBuilder sb = new StringBuilder(); + sb.append("AB$ ChangeZone | Cost$ ").append(cycleCost); + + String desc = type; + if (type.equals("Basic")) { + desc = "Basic land"; + } + + sb.append(" Discard<1/CARDNAME> | ActivationZone$ Hand | PrecostDesc$ ").append(desc).append("cycling "); + sb.append("| Origin$ Library | Destination$ Hand |"); + sb.append("ChangeType$ ").append(type); + sb.append(" | SpellDescription$ Search your library for a ").append(desc).append(" card, reveal it,"); + sb.append(" and put it into your hand. Then shuffle your library."); + + SpellAbility cycle = AbilityFactory.getAbility(sb.toString(), sourceCard); + cycle.setIsCycling(true); + + return cycle; + } // abilityTypecycle() + + /** + *

+ * abilityTransmute. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + * @param transmuteCost + * a {@link java.lang.String} object. + * @return a {@link forge.game.spellability.SpellAbility} object. + */ + public static SpellAbility abilityTransmute(final Card sourceCard, String transmuteCost) { + transmuteCost += " Discard<1/CARDNAME>"; + final Cost abCost = new Cost(transmuteCost, true); + class AbilityTransmute extends AbilityActivated { + public AbilityTransmute(final Card ca, final Cost co, final TargetRestrictions t) { + super(ca, co, t); + } + + @Override + public AbilityActivated getCopy() { + AbilityActivated res = new AbilityTransmute(getSourceCard(), getPayCosts(), getTargetRestrictions()); + CardFactory.copySpellAbility(this, res); + res.getRestrictions().setZone(ZoneType.Hand); + return res; + } + + private static final long serialVersionUID = -4960704261761785512L; + + @Override + public boolean canPlayAI(Player aiPlayer) { + return false; + } + + @Override + public boolean canPlay() { + return super.canPlay() && sourceCard.getController().canCastSorcery(); + } + + @Override + public void resolve() { + final List cards = sourceCard.getController().getCardsIn(ZoneType.Library); + final List sameCost = new ArrayList(); + + for (Card c : cards) { + if (c.isSplitCard() && c.getCurState() == CardCharacteristicName.Original) { + if (c.getState(CardCharacteristicName.LeftSplit).getManaCost().getCMC() == sourceCard.getManaCost().getCMC() || + c.getState(CardCharacteristicName.RightSplit).getManaCost().getCMC() == sourceCard.getManaCost().getCMC()) { + sameCost.add(c); + } + } + else if (c.getManaCost().getCMC() == sourceCard.getManaCost().getCMC()) { + sameCost.add(c); + } + } + + if (sameCost.isEmpty()) { + return; + } + + final Card c1 = sourceCard.getController().getController().chooseSingleEntityForEffect(sameCost, this, "Select a card", true); + if (c1 != null) { + // ability.setTargetCard((Card)o); + + sourceCard.getController().discard(sourceCard, this); + sourceCard.getGame().getAction().moveToHand(c1); + + } + sourceCard.getController().shuffle(this); + } + } + final SpellAbility transmute = new AbilityTransmute(sourceCard, abCost, null); + + final StringBuilder sbDesc = new StringBuilder(); + sbDesc.append("Transmute (").append(abCost.toString()); + sbDesc.append("Search your library for a card with the same converted mana cost as this card, reveal it, "); + sbDesc.append("and put it into your hand. Then shuffle your library. Transmute only as a sorcery.)"); + transmute.setDescription(sbDesc.toString()); + + final StringBuilder sbStack = new StringBuilder(); + sbStack.append(sourceCard).append(" Transmute: Search your library "); + sbStack.append("for a card with the same converted mana cost.)"); + transmute.setStackDescription(sbStack.toString()); + + transmute.getRestrictions().setZone(ZoneType.Hand); + return transmute; + } // abilityTransmute() + + /** + *

+ * abilitySuspendStatic. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + * @param suspendCost + * a {@link java.lang.String} object. + * @param timeCounters + * a int. + * @return a {@link forge.game.spellability.SpellAbility} object. + */ + public static SpellAbility abilitySuspendStatic(final Card sourceCard, final String suspendCost, final String timeCounters) { + // be careful with Suspend ability, it will not hit the stack + Cost cost = new Cost(suspendCost, true); + final SpellAbility suspend = new AbilityStatic(sourceCard, cost, null) { + @Override + public boolean canPlay() { + if (!(this.getRestrictions().canPlay(sourceCard, this))) { + return false; + } + + if (sourceCard.isInstant() || sourceCard.hasKeyword("Flash")) { + return true; + } + + return sourceCard.getOwner().canCastSorcery(); + } + + @Override + public boolean canPlayAI(Player aiPlayer) { + return true; + // Suspend currently not functional for the AI, + // seems to be an issue with regaining Priority after Suspension + } + + @Override + public void resolve() { + final Game game = sourceCard.getGame(); + final Card c = game.getAction().exile(sourceCard); + + int counters = AbilityUtils.calculateAmount(c, timeCounters, this); + c.addCounter(CounterType.TIME, counters, true); + + String sb = String.format("%s has suspended %s with %d time counters on it.", this.getActivatingPlayer(), c.getName(), counters); + game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb); + } + }; + final StringBuilder sbDesc = new StringBuilder(); + sbDesc.append("Suspend ").append(timeCounters).append(" - ").append(cost.toSimpleString()); + suspend.setDescription(sbDesc.toString()); + + String svar = "X"; // emulate "References X" here + suspend.setSVar(svar, sourceCard.getSVar(svar)); + + final StringBuilder sbStack = new StringBuilder(); + sbStack.append(sourceCard.getName()).append(" suspending for ").append(timeCounters).append(" turns.)"); + suspend.setStackDescription(sbStack.toString()); + + suspend.getRestrictions().setZone(ZoneType.Hand); + return suspend; + } // abilitySuspendStatic() + + public static void addSuspendUpkeepTrigger(Card card) { + //upkeep trigger + StringBuilder upkeepTrig = new StringBuilder(); + UUID triggerSvar = UUID.randomUUID(); + UUID removeCounterSvar = UUID.randomUUID(); + + upkeepTrig.append("Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Exile | CheckSVar$ "); + upkeepTrig.append(triggerSvar); + upkeepTrig.append(" | SVarCompare$ GE1 | References$ "); + upkeepTrig.append(triggerSvar); + upkeepTrig.append(" | Execute$ "); + upkeepTrig.append(removeCounterSvar); + upkeepTrig.append(" | TriggerDescription$ At the beginning of your upkeep, if this card is suspended, remove a time counter from it"); + + card.setSVar(removeCounterSvar.toString(), "DB$ RemoveCounter | Defined$ Self | CounterType$ TIME | CounterNum$ 1"); + card.setSVar(triggerSvar.toString(),"Count$ValidExile Card.Self+suspended"); + + final Trigger parsedUpkeepTrig = TriggerHandler.parseTrigger(upkeepTrig.toString(), card, true); + card.addTrigger(parsedUpkeepTrig); + } + + public static void addSuspendPlayTrigger(Card card) { + //play trigger + StringBuilder playTrig = new StringBuilder(); + UUID playSvar = UUID.randomUUID(); + + playTrig.append("Mode$ CounterRemoved | TriggerZones$ Exile | ValidCard$ Card.Self | NewCounterAmount$ 0 | Secondary$ True | Execute$ "); + playTrig.append(playSvar.toString()); + playTrig.append(" | TriggerDescription$ When the last time counter is removed from this card, if it's exiled, play it without paying its mana cost if able. "); + playTrig.append("If you can't, it remains exiled. If you cast a creature spell this way, it gains haste until you lose control of the spell or the permanent it becomes."); + + StringBuilder playWithoutCost = new StringBuilder(); + playWithoutCost.append("DB$ Play | Defined$ Self | WithoutManaCost$ True | SuspendCast$ True"); + + final Trigger parsedPlayTrigger = TriggerHandler.parseTrigger(playTrig.toString(), card, true); + card.addTrigger(parsedPlayTrigger); + + card.setSVar(playSvar.toString(),playWithoutCost.toString()); + } + + + /** + *

+ * multiplyCost. + *

+ * + * @param manacost + * a {@link java.lang.String} object. + * @param multiplier + * a int. + * @return a {@link java.lang.String} object. + */ + public static String multiplyCost(final String manacost, final int multiplier) { + if (multiplier == 0) { + return ""; + } + if (multiplier == 1) { + return manacost; + } + + final String[] tokenized = manacost.split("\\s"); + final StringBuilder sb = new StringBuilder(); + + if (Character.isDigit(tokenized[0].charAt(0))) { + // cost starts with "colorless" number cost + int cost = Integer.parseInt(tokenized[0]); + cost = multiplier * cost; + tokenized[0] = "" + cost; + sb.append(tokenized[0]); + } else { + if (tokenized[0].contains("<")) { + final String[] advCostPart = tokenized[0].split("<"); + final String costVariable = advCostPart[1].split(">")[0]; + final String[] advCostPartValid = costVariable.split("\\/", 2); + // multiply the number part of the cost object + int num = Integer.parseInt(advCostPartValid[0]); + num = multiplier * num; + tokenized[0] = advCostPart[0] + "<" + num; + if (advCostPartValid.length > 1) { + tokenized[0] = tokenized[0] + "/" + advCostPartValid[1]; + } + tokenized[0] = tokenized[0] + ">"; + sb.append(tokenized[0]); + } else { + for (int i = 0; i < multiplier; i++) { + // tokenized[0] = tokenized[0] + " " + tokenized[0]; + sb.append((" ")); + sb.append(tokenized[0]); + } + } + } + + for (int i = 1; i < tokenized.length; i++) { + if (tokenized[i].contains("<")) { + final String[] advCostParts = tokenized[i].split("<"); + final String costVariables = advCostParts[1].split(">")[0]; + final String[] advCostPartsValid = costVariables.split("\\/", 2); + // multiply the number part of the cost object + int num = Integer.parseInt(advCostPartsValid[0]); + num = multiplier * num; + tokenized[i] = advCostParts[0] + "<" + num; + if (advCostPartsValid.length > 1) { + tokenized[i] = tokenized[i] + "/" + advCostPartsValid[1]; + } + tokenized[i] = tokenized[i] + ">"; + sb.append((" ")); + sb.append(tokenized[i]); + } else { + for (int j = 0; j < multiplier; j++) { + // tokenized[i] = tokenized[i] + " " + tokenized[i]; + sb.append((" ")); + sb.append(tokenized[i]); + } + } + } + + String result = sb.toString(); + System.out.println("result: " + result); + result = result.trim(); + return result; + } + + /** + *

+ * isTargetStillValid. + *

+ * + * @param ability + * a {@link forge.game.spellability.SpellAbility} object. + * @param target + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public static boolean isTargetStillValid(final SpellAbility ability, final Card target) { + + Zone zone = target.getGame().getZoneOf(target); + if (zone == null) { + return false; // for tokens that disappeared + } + + final Card source = ability.getSourceCard(); + final TargetRestrictions tgt = ability.getTargetRestrictions(); + if (tgt != null) { + // Reconfirm the Validity of a TgtValid, or if the Creature is still + // a Creature + if (tgt.doesTarget() + && !target.isValid(tgt.getValidTgts(), ability.getActivatingPlayer(), ability.getSourceCard())) { + return false; + } + + // Check if the target is in the zone it needs to be in to be targeted + if (!tgt.getZone().contains(zone.getZoneType())) { + return false; + } + } else { + // If an Aura's target is removed before it resolves, the Aura + // fizzles + if (source.isAura() && !target.isInZone(ZoneType.Battlefield)) { + return false; + } + } + + // Make sure it's still targetable as well + return target.canBeTargetedBy(ability); + } + + // does "target" have protection from "card"? + /** + *

+ * hasProtectionFrom. + *

+ * + * @param card + * a {@link forge.game.card.Card} object. + * @param target + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public static boolean hasProtectionFrom(final Card card, final Card target) { + if (target == null) { + return false; + } + + return target.hasProtectionFrom(card); + } + + /** + *

+ * isCounterable. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public static boolean isCounterable(final Card c) { + if (c.hasKeyword("CARDNAME can't be countered.") || !c.getCanCounter()) { + return false; + } + + return true; + } + + /** + *

+ * isCounterableBy. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param sa + * the sa + * @return a boolean. + */ + public static boolean isCounterableBy(final Card c, final SpellAbility sa) { + if (!isCounterable(c)) { + return false; + } + // Autumn's Veil + if (c.hasKeyword("CARDNAME can't be countered by blue or black spells.") && sa.isSpell() + && (sa.getSourceCard().isBlack() || sa.getSourceCard().isBlue())) { + return false; + } + return true; + } + + /** + *

+ * countOccurrences. + *

+ * + * @param arg1 + * a {@link java.lang.String} object. + * @param arg2 + * a {@link java.lang.String} object. + * @return a int. + */ + public static int countOccurrences(final String arg1, final String arg2) { + + int count = 0; + int index = 0; + while ((index = arg1.indexOf(arg2, index)) != -1) { + ++index; + ++count; + } + return count; + } + + /** + *

+ * parseMath. + *

+ * + * @param l + * an array of {@link java.lang.String} objects. + * @return an array of {@link java.lang.String} objects. + */ + public static String extractOperators(final String expression) { + String[] l = expression.split("/"); + return l.length > 1 ? l[1] : null; + } + + /** + *

+ * Parse player targeted X variables. + *

+ * + * @param players + * a {@link java.util.ArrayList} object. + * @param s + * a {@link java.lang.String} object. + * @param source + * a {@link forge.game.card.Card} object. + * @return a int. + */ + public static int objectXCount(final List objects, final String s, final Card source) { + if (objects.isEmpty()) { + return 0; + } + + int n = s.startsWith("Amount") ? objects.size() : 0; + return doXMath(n, extractOperators(s), source); + } + + /** + *

+ * Parse player targeted X variables. + *

+ * + * @param players + * a {@link java.util.ArrayList} object. + * @param s + * a {@link java.lang.String} object. + * @param source + * a {@link forge.game.card.Card} object. + * @return a int. + */ + public static int playerXCount(final List players, final String s, final Card source) { + if (players.size() == 0) { + return 0; + } + + final String[] l = s.split("/"); + final String m = extractOperators(s); + + int n = 0; + + // methods for getting the highest/lowest playerXCount from a range of players + if (l[0].startsWith("Highest")) { + for (final Player player : players) { + final int current = playerXProperty(player, s.replace("Highest", ""), source); + if (current > n) { + n = current; + } + } + + return doXMath(n, m, source); + } else if (l[0].startsWith("Lowest")) { + n = 99999; // if no players have fewer than 99999 valids, the game is frozen anyway + for (final Player player : players) { + final int current = playerXProperty(player, s.replace("Lowest", ""), source); + if (current < n) { + n = current; + } + } + return doXMath(n, m, source); + } + + + final String[] sq; + sq = l[0].split("\\."); + + // the number of players passed in + if (sq[0].equals("Amount")) { + return doXMath(players.size(), m, source); + } + if (sq[0].contains("DamageThisTurn")) { + int totDmg = 0; + for (Player p : players) { + totDmg += p.getAssignedDamage(); + } + return doXMath(totDmg, m, source); + } + + if(players.size() > 0) + return playerXProperty(players.get(0), s, source); + + return doXMath(n, m, source); + } + + public static int playerXProperty(Player player, String s, Card source) { + final String[] l = s.split("/"); + final String m = extractOperators(s); + + final Game game = player.getGame(); + + // count valid cards in any specified zone/s + if (l[0].startsWith("Valid") && !l[0].contains("Valid ")) { + String[] lparts = l[0].split(" ", 2); + final List vZone = ZoneType.listValueOf(lparts[0].split("Valid")[1]); + String restrictions = l[0].replace(lparts[0] + " ", ""); + final String[] rest = restrictions.split(","); + List cards = game.getCardsIn(vZone); + cards = CardLists.getValidCards(cards, rest, player, source); + return doXMath(cards.size(), m, source); + } + // count valid cards on the battlefield + if (l[0].startsWith("Valid ")) { + final String restrictions = l[0].substring(6); + final String[] rest = restrictions.split(","); + List cardsonbattlefield = game.getCardsIn(ZoneType.Battlefield); + cardsonbattlefield = CardLists.getValidCards(cardsonbattlefield, rest, player, source); + return doXMath(cardsonbattlefield.size(), m, source); + } + + final String[] sq = l[0].split("\\."); + final String value = sq[0]; + + if (value.contains("CardsInHand")) { + return doXMath(player.getCardsIn(ZoneType.Hand).size(), m, source); + } + + if (value.contains("NumPowerSurgeLands")) { + return doXMath(player.getNumPowerSurgeLands(), m, source); + } + + if (value.contains("DomainPlayer")) { + int n = 0; + final List someCards = new ArrayList(); + someCards.addAll(player.getCardsIn(ZoneType.Battlefield)); + final List basic = MagicColor.Constant.BASIC_LANDS; + + for (int i = 0; i < basic.size(); i++) { + if (!CardLists.getType(someCards, basic.get(i)).isEmpty()) { + n++; + } + } + return doXMath(n, m, source); + } + + if (value.contains("CardsInLibrary")) { + return doXMath(player.getCardsIn(ZoneType.Library).size(), m, source); + } + + if (value.contains("CardsInGraveyard")) { + return doXMath(player.getCardsIn(ZoneType.Graveyard).size(), m, source); + } + if (value.contains("LandsInGraveyard")) { + return doXMath(CardLists.getType(player.getCardsIn(ZoneType.Graveyard), "Land").size(), m, source); + } + + if (value.contains("CreaturesInPlay")) { + return doXMath(player.getCreaturesInPlay().size(), m, source); + } + + if (value.contains("CardsInPlay")) { + return doXMath(player.getCardsIn(ZoneType.Battlefield).size(), m, source); + } + + if (value.contains("LifeTotal")) { + return doXMath(player.getLife(), m, source); + } + + if (value.contains("LifeLostThisTurn")) { + return doXMath(player.getLifeLostThisTurn(), m, source); + } + + if (value.contains("LifeGainedThisTurn")) { + return doXMath(player.getLifeGainedThisTurn(), m, source); + } + + if (value.contains("PoisonCounters")) { + return doXMath(player.getPoisonCounters(), m, source); + } + + if (value.contains("TopOfLibraryCMC")) { + return doXMath(Aggregates.sum(player.getCardsIn(ZoneType.Library, 1), CardPredicates.Accessors.fnGetCmc), m, source); + } + + if (value.contains("LandsPlayed")) { + return doXMath(player.getNumLandsPlayed(), m, source); + } + + if (value.contains("CardsDrawn")) { + return doXMath(player.getNumDrawnThisTurn(), m, source); + } + + if (value.contains("CardsDiscardedThisTurn")) { + return doXMath(player.getNumDiscardedThisTurn(), m, source); + } + + if (value.contains("AttackersDeclared")) { + return doXMath(player.getAttackersDeclaredThisTurn(), m, source); + } + + if (value.equals("DamageDoneToPlayerBy")) { + return doXMath(source.getDamageDoneToPlayerBy(player.getName()), m, source); + } + + if (value.contains("DamageToOppsThisTurn")) { + int oppDmg = 0; + for (Player opp : player.getOpponents()) { + oppDmg += opp.getAssignedDamage(); + } + return doXMath(oppDmg, m, source); + } + + return doXMath(0, m, source); + } + + /** + *

+ * Parse non-mana X variables. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param expression + * a {@link java.lang.String} object. + * @return a int. + */ + public static int xCount(final Card c, final String expression) { + if (StringUtils.isBlank(expression)) return 0; + if (StringUtils.isNumeric(expression)) return Integer.parseInt(expression); + + final Player cc = c.getController(); + final Player ccOpp = cc.getOpponent(); + final Game game = c.getGame(); + final Player activePlayer = game.getPhaseHandler().getPlayerTurn(); + + + final String[] l = expression.split("/"); + final String m = extractOperators(expression); + + // accept straight numbers + if (l[0].startsWith("Number$")) { + final String number = l[0].substring(7); + if (number.equals("ChosenNumber")) { + return doXMath(c.getChosenNumber(), m, c); + } else { + return doXMath(Integer.parseInt(number), m, c); + } + } + + if (l[0].startsWith("Count$")) { + l[0] = l[0].substring(6); + } + + if (l[0].startsWith("SVar$")) { + return doXMath(xCount(c, c.getSVar(l[0].substring(5))), m, c); + } + + if (l[0].startsWith("Controller$")) + return playerXProperty(cc, l[0].substring(11), c); + + + // Manapool + if (l[0].startsWith("ManaPool")) { + final String color = l[0].split(":")[1]; + if (color.equals("All")) { + return cc.getManaPool().totalMana(); + } else { + return cc.getManaPool().getAmountOfColor(MagicColor.fromName(color)); + } + } + + // count valid cards in any specified zone/s + if (l[0].startsWith("Valid")) { + String[] lparts = l[0].split(" ", 2); + final String[] rest = lparts[1].split(","); + + final List cardsInZones = lparts[0].length() > 5 + ? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(5))) + : game.getCardsIn(ZoneType.Battlefield); + + List cards = CardLists.getValidCards(cardsInZones, rest, cc, c); + return doXMath(cards.size(), m, c); + } + + + if (l[0].startsWith("ImprintedCardPower") && !c.getImprinted().isEmpty()) return c.getImprinted().get(0).getNetAttack(); + if (l[0].startsWith("ImprintedCardToughness") && !c.getImprinted().isEmpty()) return c.getImprinted().get(0).getNetDefense(); + if (l[0].startsWith("ImprintedCardManaCost") && !c.getImprinted().isEmpty()) return c.getImprinted().get(0).getCMC(); + + if (l[0].startsWith("GreatestPower_")) { + final String restriction = l[0].substring(14); + final String[] rest = restriction.split(","); + List list = CardLists.getValidCards(cc.getGame().getCardsIn(ZoneType.Battlefield), rest, cc, c); + int highest = 0; + for (final Card crd : list) { + if (crd.getNetAttack() > highest) { + highest = crd.getNetAttack(); + } + } + return highest; + } + + if (l[0].startsWith("HighestCMC_")) { + final String restriction = l[0].substring(11); + final String[] rest = restriction.split(","); + List list = cc.getGame().getCardsInGame(); + list = CardLists.getValidCards(list, rest, cc, c); + int highest = 0; + for (final Card crd : list) { + if (crd.isSplitCard()) { + if (crd.getCMC(Card.SplitCMCMode.LeftSplitCMC) > highest) { + highest = crd.getCMC(Card.SplitCMCMode.LeftSplitCMC); + } + if (crd.getCMC(Card.SplitCMCMode.RightSplitCMC) > highest) { + highest = crd.getCMC(Card.SplitCMCMode.RightSplitCMC); + } + } else { + if (crd.getCMC() > highest) { + highest = crd.getCMC(); + } + } + } + return highest; + } + + if (l[0].startsWith("DifferentCardNames_")) { + final List crdname = new ArrayList(); + final String restriction = l[0].substring(19); + final String[] rest = restriction.split(","); + List list = cc.getGame().getCardsInGame(); + list = CardLists.getValidCards(list, rest, cc, c); + for (final Card card : list) { + if (!crdname.contains(card.getName())) { + crdname.add(card.getName()); + } + } + return doXMath(crdname.size(), m, c); + } + + if (l[0].startsWith("RememberedSize")) { + return doXMath(c.getRemembered().size(), m, c); + } + + // Count$CountersAdded + if (l[0].startsWith("CountersAdded")) { + final String[] components = l[0].split(" ", 3); + final CounterType counterType = CounterType.valueOf(components[1]); + String restrictions = components[2]; + final String[] rest = restrictions.split(","); + List candidates = game.getCardsInGame(); + candidates = CardLists.getValidCards(candidates, rest, cc, c); + + int added = 0; + for (final Card counterSource : candidates) { + added += c.getCountersAddedBy(counterSource, counterType); + } + return doXMath(added, m, c); + } + + if (l[0].startsWith("CommanderCastFromCommandZone")) { + // Read SVar CommanderCostRaise from Commander effect + Card commeff = CardLists.filter(cc.getCardsIn(ZoneType.Command), + CardPredicates.nameEquals("Commander effect")).get(0); + return doXMath(xCount(commeff, commeff.getSVar("CommanderCostRaise")), "DivideEvenlyDown.2", c); + } + + if (l[0].startsWith("MostProminentCreatureType")) { + String restriction = l[0].split(" ")[1]; + List list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, cc, c); + return doXMath(getMostProminentCreatureTypeSize(list), m, c); + } + + if (l[0].startsWith("SecondMostProminentColor")) { + String restriction = l[0].split(" ")[1]; + List list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, cc, c); + int[] colorSize = SortColorsFromList(list); + return doXMath(colorSize[colorSize.length - 2], m, c); + } + + if (l[0].startsWith("RolledThisTurn")) { + return game.getPhaseHandler().getPlanarDiceRolledthisTurn(); + } + + final String[] sq; + sq = l[0].split("\\."); + + if (sq[0].contains("xPaid")) return doXMath(c.getXManaCostPaid(), m, c); + + + if (sq[0].equals("YouDrewThisTurn")) return doXMath(c.getController().getNumDrawnThisTurn(), m, c); + if (sq[0].equals("OppDrewThisTurn")) return doXMath(c.getController().getOpponent().getNumDrawnThisTurn(), m, c); + + + if (sq[0].equals("StormCount")) return doXMath(game.getStack().getCardsCastThisTurn().size() - 1, m, c); + if (sq[0].equals("DamageDoneThisTurn")) return doXMath(c.getDamageDoneThisTurn(), m, c); + if (sq[0].equals("BloodthirstAmount")) return doXMath(c.getController().getBloodthirstAmount(), m, c); + if (sq[0].equals("RegeneratedThisTurn")) return doXMath(c.getRegeneratedThisTurn(), m, c); + + // TriggeringObjects + if (sq[0].startsWith("Triggered")) return doXMath(xCount((Card) c.getTriggeringObject("Card"), sq[0].substring(9)), m, c); + + if (sq[0].contains("YourStartingLife")) return doXMath(cc.getStartingLife(), m, c); + //if (sq[0].contains("OppStartingLife")) return doXMath(oppController.getStartingLife(), m, c); // found no cards using it + + + if (sq[0].contains("YourLifeTotal")) return doXMath(cc.getLife(), m, c); + if (sq[0].contains("OppLifeTotal")) return doXMath(ccOpp.getLife(), m, c); + + // Count$TargetedLifeTotal (targeted player's life total) + if (sq[0].contains("TargetedLifeTotal")) { + for (final SpellAbility sa : c.getCharacteristics().getSpellAbility()) { + final SpellAbility saTargeting = sa.getSATargetingPlayer(); + if (saTargeting != null) { + for (final Player tgtP : saTargeting.getTargets().getTargetPlayers()) { + return doXMath(tgtP.getLife(), m, c); + } + } + } + } + + if (sq[0].contains("LifeYouLostThisTurn")) return doXMath(cc.getLifeLostThisTurn(), m, c); + if (sq[0].contains("LifeYouGainedThisTurn")) return doXMath(cc.getLifeGainedThisTurn(), m, c); + if (sq[0].contains("LifeOppsLostThisTurn")) { + int lost = 0; + for (Player opp : cc.getOpponents()) { + lost += opp.getLifeLostThisTurn(); + } + return doXMath(lost, m, c); + } + + if (sq[0].equals("TotalDamageDoneByThisTurn")) return doXMath(c.getTotalDamageDoneBy(), m, c); + if (sq[0].equals("TotalDamageReceivedThisTurn")) return doXMath(c.getTotalDamageRecievedThisTurn(), m, c); + + if (sq[0].contains("YourPoisonCounters")) return doXMath(cc.getPoisonCounters(), m, c); + if (sq[0].contains("OppPoisonCounters")) return doXMath(ccOpp.getPoisonCounters(), m, c); + + if (sq[0].contains("OppDamageThisTurn")) return doXMath(ccOpp.getAssignedDamage(), m, c); + if (sq[0].contains("YourDamageThisTurn")) return doXMath(cc.getAssignedDamage(), m, c); + + // Count$YourTypeDamageThisTurn Type + if (sq[0].contains("YourTypeDamageThisTurn")) return doXMath(cc.getAssignedDamage(sq[0].split(" ")[1]), m, c); + if (sq[0].contains("YourDamageSourcesThisTurn")) { + Iterable allSrc = cc.getAssignedDamageSources(); + String restriction = sq[0].split(" ")[1]; + List filtered = CardLists.getValidCards(allSrc, restriction, cc, c); + return doXMath(filtered.size(), m, c); + } + + if (sq[0].contains("YourLandsPlayed")) return doXMath(cc.getNumLandsPlayed(), m, c); + if (sq[0].contains("OppLandsPlayed")) return doXMath(ccOpp.getNumLandsPlayed(), m, c); + + // Count$TopOfLibraryCMC + if (sq[0].contains("TopOfLibraryCMC")) { + final List library = cc.getCardsIn(ZoneType.Library); + return doXMath(library.isEmpty() ? 0 : library.get(0).getCMC(), m, c); + } + + // Count$EnchantedControllerCreatures + if (sq[0].contains("EnchantedControllerCreatures")) { + List enchantedControllerInPlay = new ArrayList(); + if (c.getEnchantingCard() != null) { + enchantedControllerInPlay = c.getEnchantingCard().getController().getCardsIn(ZoneType.Battlefield); + enchantedControllerInPlay = CardLists.filter(enchantedControllerInPlay, CardPredicates.Presets.CREATURES); + } + return enchantedControllerInPlay.size(); + } + + // Count$MonstrosityMagnitude + if (sq[0].contains("MonstrosityMagnitude")) { + return doXMath(c.getMonstrosityNum(), m, c); + } + + // Count$Chroma. + if (sq[0].contains("Chroma") || sq[0].equals("Devotion")) { + ZoneType sourceZone = sq[0].contains("ChromaInGrave") ? ZoneType.Graveyard : ZoneType.Battlefield; + String colorAbb = sq[1]; + if (colorAbb.contains("Chosen")) { + colorAbb = MagicColor.toShortString(c.getChosenColor().get(0)); + } + final List cards; + if (sq[0].contains("ChromaSource")) { // Runs Chroma for passed in Source card + cards = Lists.newArrayList(c); + } else { + cards = cc.getCardsIn(sourceZone); + } + + int colorOcurrencices = 0; + byte colorCode = MagicColor.fromName(colorAbb); + for(Card c0 : cards) { + for(ManaCostShard sh : c0.getManaCost()){ + if (sh.canBePaidWithManaOfColor(colorCode)) + colorOcurrencices++; + } + } + return doXMath(colorOcurrencices, m, c); + } + if (sq[0].contains("DevotionDual")) { + int colorOcurrencices = 0; + byte color1 = MagicColor.fromName(sq[1]); + byte color2 = MagicColor.fromName(sq[2]); + for(Card c0 : cc.getCardsIn(ZoneType.Battlefield)) { + for (ManaCostShard sh : c0.getManaCost()) { + if (sh.canBePaidWithManaOfColor(color1) || sh.canBePaidWithManaOfColor(color2)) { + colorOcurrencices++; + } + } + } + return doXMath(colorOcurrencices, m, c); + } + + if (sq[0].contains("Hellbent")) return doXMath(Integer.parseInt(sq[cc.hasHellbent() ? 1 : 2]), m, c); + if (sq[0].contains("Metalcraft")) return doXMath(Integer.parseInt(sq[cc.hasMetalcraft() ? 1 : 2]), m, c); + if (sq[0].contains("FatefulHour")) return doXMath(Integer.parseInt(sq[cc.getLife() <= 5 ? 1 : 2]), m, c); + + if (sq[0].contains("Landfall")) return doXMath(Integer.parseInt(sq[cc.hasLandfall() ? 1 : 2]), m, c); + if (sq[0].contains("Threshold")) return doXMath(Integer.parseInt(sq[cc.hasThreshold() ? 1 : 2]), m, c); + if (sq[0].startsWith("Kicked")) return doXMath(Integer.parseInt(sq[c.getKickerMagnitude() > 0 ? 1 : 2]), m, c); + if (sq[0].startsWith("AltCost")) return doXMath(Integer.parseInt(sq[c.isOptionalCostPaid(OptionalCost.AltCost) ? 1 : 2]), m, c); + + // Count$wasCastFrom.. + if (sq[0].startsWith("wasCastFrom")) { + boolean zonesMatch = c.getCastFrom() == ZoneType.smartValueOf(sq[0].substring(11)); + return doXMath(Integer.parseInt(sq[zonesMatch ? 1 : 2]), m, c); + } + + if (sq[0].startsWith("Devoured")) { + final String validDevoured = l[0].split(" ")[1]; + List cl = CardLists.getValidCards(c.getDevoured(), validDevoured.split(","), cc, c); + return doXMath(cl.size(), m, c); + } + + if (sq[0].contains("CardPower")) return doXMath(c.getNetAttack(), m, c); + if (sq[0].contains("CardToughness")) return doXMath(c.getNetDefense(), m, c); + if (sq[0].contains("CardSumPT")) return doXMath((c.getNetAttack() + c.getNetDefense()), m, c); + + // Count$SumPower_valid + if (sq[0].contains("SumPower")) { + final String[] restrictions = l[0].split("_"); + final String[] rest = restrictions[1].split(","); + List filteredCards = CardLists.getValidCards(cc.getGame().getCardsIn(ZoneType.Battlefield), rest, cc, c); + return doXMath(Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetNetAttack), m, c); + } + // Count$CardManaCost + if (sq[0].contains("CardManaCost")) { + Card ce = sq[0].contains("Equipped") && c.isEquipping() ? c.getEquipping().get(0) : c; + return doXMath(ce.getCMC(), m, c); + } + // Count$SumCMC_valid + if (sq[0].contains("SumCMC")) { + final String[] restrictions = l[0].split("_"); + final String[] rest = restrictions[1].split(","); + List cardsonbattlefield = game.getCardsIn(ZoneType.Battlefield); + List filteredCards = CardLists.getValidCards(cardsonbattlefield, rest, cc, c); + return Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetCmc); + } + + if (sq[0].contains("CardNumColors")) return doXMath(CardUtil.getColors(c).countColors(), m, c); + if (sq[0].contains("ChosenNumber")) return doXMath(c.getChosenNumber(), m, c); + if (sq[0].contains("CardCounters")) { + // CardCounters.ALL to be used for Kinsbaile Borderguard and anything that cares about all counters + int count = 0; + if (sq[1].equals("ALL")) { + for(Integer i : c.getCounters().values()) { + if (i != null && i > 0) { + count += i; + } + } + } else { + count = c.getCounters(CounterType.getType(sq[1])); + } + return doXMath(count, m, c); + } + + // Count$TotalCounters._ + if (sq[0].contains("TotalCounters")) { + final String[] restrictions = l[0].split("_"); + final CounterType cType = CounterType.getType(restrictions[1]); + final String[] validFilter = restrictions[2].split(","); + List validCards = game.getCardsIn(ZoneType.Battlefield); + validCards = CardLists.getValidCards(validCards, validFilter, cc, c); + int cCount = 0; + for (final Card card : validCards) { + cCount += card.getCounters(cType); + } + return doXMath(cCount, m, c); + } + + if (sq[0].contains("CardTypes")) return doXMath(getCardTypesFromList(game.getCardsIn(ZoneType.smartValueOf(sq[1]))), m, c); + + if (sq[0].contains("BushidoPoint")) return doXMath(c.getKeywordMagnitude("Bushido"), m, c); + if (sq[0].contains("TimesKicked")) return doXMath(c.getKickerMagnitude(), m, c); + if (sq[0].contains("NumCounters")) return doXMath(c.getCounters(CounterType.getType(sq[1])), m, c); + + + // Count$IfMainPhase.. // 7/10 + if (sq[0].contains("IfMainPhase")) { + final PhaseHandler cPhase = cc.getGame().getPhaseHandler(); + final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.getPlayerTurn().equals(cc); + return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), m, c); + } + + // Count$ThisTurnEntered [from ] + if (sq[0].contains("ThisTurnEntered")) { + final String[] workingCopy = l[0].split("_"); + + ZoneType destination = ZoneType.smartValueOf(workingCopy[1]); + final boolean hasFrom = workingCopy[2].equals("from"); + ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null; + String validFilter = workingCopy[hasFrom ? 4 : 2] ; + + final List res = CardUtil.getThisTurnEntered(destination, origin, validFilter, c); + return doXMath(res.size(), m, c); + } + + // Count$LastTurnEntered [from ] + if (sq[0].contains("LastTurnEntered")) { + final String[] workingCopy = l[0].split("_"); + + ZoneType destination = ZoneType.smartValueOf(workingCopy[1]); + final boolean hasFrom = workingCopy[2].equals("from"); + ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null; + String validFilter = workingCopy[hasFrom ? 4 : 2] ; + + final List res = CardUtil.getLastTurnEntered(destination, origin, validFilter, c); + return doXMath(res.size(), m, c); + } + + // Count$AttackersDeclared + if (sq[0].contains("AttackersDeclared")) { + return doXMath(cc.getAttackersDeclaredThisTurn(), m, c); + } + + // Count$ThisTurnCast + // Count$LastTurnCast + if (sq[0].contains("ThisTurnCast") || sq[0].contains("LastTurnCast")) { + + final String[] workingCopy = l[0].split("_"); + final String validFilter = workingCopy[1]; + + List res; + + if (workingCopy[0].contains("This")) { + res = CardUtil.getThisTurnCast(validFilter, c); + } else { + res = CardUtil.getLastTurnCast(validFilter, c); + } + + final int ret = doXMath(res.size(), m, c); + return ret; + } + + // Count$Morbid.. + if (sq[0].startsWith("Morbid")) { + final List res = CardUtil.getThisTurnEntered(ZoneType.Graveyard, ZoneType.Battlefield, "Creature", c); + if (res.size() > 0) { + return doXMath(Integer.parseInt(sq[1]), m, c); + } else { + return doXMath(Integer.parseInt(sq[2]), m, c); + } + } + + if (sq[0].equals("YourTurns")) { + return doXMath(cc.getTurn(), m, c); + } + + if (sq[0].equals("TotalTurns")) { + // Sorry for the Singleton use, replace this once this function has game passed into it + return doXMath(game.getPhaseHandler().getTurn(), m, c); + } + + //Count$Random.. + if (sq[0].equals("Random")) { + int min = StringUtils.isNumeric(sq[1]) ? Integer.parseInt(sq[1]) : xCount(c, c.getSVar(sq[1])); + int max = StringUtils.isNumeric(sq[2]) ? Integer.parseInt(sq[2]) : xCount(c, c.getSVar(sq[2])); + + return forge.util.MyRandom.getRandom().nextInt(1+max-min) + min; + } + + + // Count$Domain + if (sq[0].startsWith("Domain")) { + int n = 0; + Player neededPlayer = sq[0].equals("DomainActivePlayer") ? activePlayer : cc; + List someCards = CardLists.filter(neededPlayer.getCardsIn(ZoneType.Battlefield), Presets.LANDS); + for (String basic : MagicColor.Constant.BASIC_LANDS) { + if (!CardLists.getType(someCards, basic).isEmpty()) { + n++; + } + } + return doXMath(n, m, c); + } + + // Count$ColoredCreatures *a DOMAIN for creatures* + if (sq[0].contains("ColoredCreatures")) { + int mask = 0; + List someCards = CardLists.filter(cc.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); + for (final Card crd : someCards) { + mask |= CardUtil.getColors(crd).getColor(); + } + return doXMath(ColorSet.fromMask(mask).countColors(), m, c); + } + + // Count$CardMulticolor.. + if (sq[0].contains("CardMulticolor")) { + final boolean isMulti = CardUtil.getColors(c).isMulticolor(); + return doXMath(Integer.parseInt(sq[isMulti ? 1 : 2]), m, c); + } + + + // Complex counting methods + List someCards = getCardListForXCount(c, cc, ccOpp, sq); + + // 1/10 - Count$MaxCMCYouCtrl + if (sq[0].contains("MaxCMC")) { + int mmc = Aggregates.max(someCards, CardPredicates.Accessors.fnGetCmc); + return doXMath(mmc, m, c); + } + + return doXMath(someCards.size(), m, c); + } + + private static List getCardListForXCount(final Card c, final Player cc, final Player ccOpp, final String[] sq) { + List someCards = new ArrayList(); + final Game game = c.getGame(); + + // Generic Zone-based counting + // Count$QualityAndZones.Subquality + + // build a list of cards in each possible specified zone + + // if a card was ever written to count two different zones, + // make sure they don't get added twice. + boolean mf = false, my = false, mh = false; + boolean of = false, oy = false, oh = false; + + if (sq[0].contains("YouCtrl") && !mf) { + someCards.addAll(cc.getCardsIn(ZoneType.Battlefield)); + mf = true; + } + + if (sq[0].contains("InYourYard") && !my) { + someCards.addAll(cc.getCardsIn(ZoneType.Graveyard)); + my = true; + } + + if (sq[0].contains("InYourLibrary") && !my) { + someCards.addAll(cc.getCardsIn(ZoneType.Library)); + my = true; + } + + if (sq[0].contains("InYourHand") && !mh) { + someCards.addAll(cc.getCardsIn(ZoneType.Hand)); + mh = true; + } + + if (sq[0].contains("InYourSideboard") && !mh) { + someCards.addAll(cc.getCardsIn(ZoneType.Sideboard)); + mh = true; + } + + if (sq[0].contains("OppCtrl")) { + if (!of) { + someCards.addAll(ccOpp.getCardsIn(ZoneType.Battlefield)); + of = true; + } + } + + if (sq[0].contains("InOppYard")) { + if (!oy) { + someCards.addAll(ccOpp.getCardsIn(ZoneType.Graveyard)); + oy = true; + } + } + + if (sq[0].contains("InOppHand")) { + if (!oh) { + someCards.addAll(ccOpp.getCardsIn(ZoneType.Hand)); + oh = true; + } + } + + if (sq[0].contains("InChosenHand")) { + if (!oh) { + if (c.getChosenPlayer() != null) { + someCards.addAll(c.getChosenPlayer().getCardsIn(ZoneType.Hand)); + } + oh = true; + } + } + + if (sq[0].contains("InChosenYard")) { + if (!oh) { + if (c.getChosenPlayer() != null) { + someCards.addAll(c.getChosenPlayer().getCardsIn(ZoneType.Graveyard)); + } + oh = true; + } + } + + if (sq[0].contains("OnBattlefield")) { + if (!mf) { + someCards.addAll(cc.getCardsIn(ZoneType.Battlefield)); + } + if (!of) { + someCards.addAll(ccOpp.getCardsIn(ZoneType.Battlefield)); + } + } + + if (sq[0].contains("InAllYards")) { + if (!my) { + someCards.addAll(cc.getCardsIn(ZoneType.Graveyard)); + } + if (!oy) { + someCards.addAll(ccOpp.getCardsIn(ZoneType.Graveyard)); + } + } + + if (sq[0].contains("SpellsOnStack")) { + someCards.addAll(game.getCardsIn(ZoneType.Stack)); + } + + if (sq[0].contains("InAllHands")) { + if (!mh) { + someCards.addAll(cc.getCardsIn(ZoneType.Hand)); + } + if (!oh) { + someCards.addAll(ccOpp.getCardsIn(ZoneType.Hand)); + } + } + + // Count$InTargetedHand (targeted player's cards in hand) + if (sq[0].contains("InTargetedHand")) { + for (final SpellAbility sa : c.getCharacteristics().getSpellAbility()) { + final SpellAbility saTargeting = sa.getSATargetingPlayer(); + if (saTargeting != null) { + for (final Player tgtP : saTargeting.getTargets().getTargetPlayers()) { + someCards.addAll(tgtP.getCardsIn(ZoneType.Hand)); + } + } + } + } + + // Count$InTargetedHand (targeted player's cards in hand) + if (sq[0].contains("InEnchantedHand")) { + GameEntity o = c.getEnchanting(); + Player controller = null; + if (o instanceof Card) { + controller = ((Card) o).getController(); + } + else { + controller = (Player) o; + } + if (controller != null) { + someCards.addAll(controller.getCardsIn(ZoneType.Hand)); + } + } + if (sq[0].contains("InEnchantedYard")) { + GameEntity o = c.getEnchanting(); + Player controller = null; + if (o instanceof Card) { + controller = ((Card) o).getController(); + } + else { + controller = (Player) o; + } + if (controller != null) { + someCards.addAll(controller.getCardsIn(ZoneType.Graveyard)); + } + } + // filter lists based on the specified quality + + // "Clerics you control" - Count$TypeYouCtrl.Cleric + if (sq[0].contains("Type")) { + someCards = CardLists.filter(someCards, CardPredicates.isType(sq[1])); + } + + // "Named in all graveyards" - Count$NamedAllYards. + + if (sq[0].contains("Named")) { + if (sq[1].equals("CARDNAME")) { + sq[1] = c.getName(); + } + someCards = CardLists.filter(someCards, CardPredicates.nameEquals(sq[1])); + } + + // Refined qualities + + // "Untapped Lands" - Count$UntappedTypeYouCtrl.Land + // if (sq[0].contains("Untapped")) { someCards = CardLists.filter(someCards, Presets.UNTAPPED); } + + // if (sq[0].contains("Tapped")) { someCards = CardLists.filter(someCards, Presets.TAPPED); } + +// String sq0 = sq[0].toLowerCase(); +// for(String color : MagicColor.Constant.ONLY_COLORS) { +// if( sq0.contains(color) ) +// someCards = someCards.filter(CardListFilter.WHITE); +// } + // "White Creatures" - Count$WhiteTypeYouCtrl.Creature + // if (sq[0].contains("White")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.WHITE)); + // if (sq[0].contains("Blue")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.BLUE)); + // if (sq[0].contains("Black")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.BLACK)); + // if (sq[0].contains("Red")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.RED)); + // if (sq[0].contains("Green")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.GREEN)); + + if (sq[0].contains("Multicolor")) { + someCards = CardLists.filter(someCards, new Predicate() { + @Override + public boolean apply(final Card c) { + return CardUtil.getColors(c).isMulticolor(); + } + }); + } + + if (sq[0].contains("Monocolor")) { + someCards = CardLists.filter(someCards, new Predicate() { + @Override + public boolean apply(final Card c) { + return CardUtil.getColors(c).isMonoColor(); + } + }); + } + return someCards; + } + + public static int doXMath(final int num, final String operators, final Card c) { + if (operators == null || operators.equals("none")) { + return num; + } + + final String[] s = operators.split("\\."); + int secondaryNum = 0; + + try { + if (s.length == 2) { + secondaryNum = Integer.parseInt(s[1]); + } + } catch (final Exception e) { + secondaryNum = xCount(c, c.getSVar(s[1])); + } + + if (s[0].contains("Plus")) { + return num + secondaryNum; + } else if (s[0].contains("NMinus")) { + return secondaryNum - num; + } else if (s[0].contains("Minus")) { + return num - secondaryNum; + } else if (s[0].contains("Twice")) { + return num * 2; + } else if (s[0].contains("Thrice")) { + return num * 3; + } else if (s[0].contains("HalfUp")) { + return (int) (Math.ceil(num / 2.0)); + } else if (s[0].contains("HalfDown")) { + return (int) (Math.floor(num / 2.0)); + } else if (s[0].contains("ThirdUp")) { + return (int) (Math.ceil(num / 3.0)); + } else if (s[0].contains("ThirdDown")) { + return (int) (Math.floor(num / 3.0)); + } else if (s[0].contains("Negative")) { + return num * -1; + } else if (s[0].contains("Times")) { + return num * secondaryNum; + } else if (s[0].contains("DivideEvenlyDown")) { + if (secondaryNum == 0) { + return 0; + } else { + return num / secondaryNum; + } + } else if (s[0].contains("Mod")) { + return num % secondaryNum; + } else if (s[0].contains("Abs")) { + return Math.abs(num); + } else if (s[0].contains("LimitMax")) { + if (num < secondaryNum) { + return num; + } else { + return secondaryNum; + } + } else if (s[0].contains("LimitMin")) { + if (num > secondaryNum) { + return num; + } else { + return secondaryNum; + } + + } else { + return num; + } + } + + /** + *

+ * handlePaid. + *

+ * + * @param paidList + * a {@link forge.CardList} object. + * @param string + * a {@link java.lang.String} object. + * @param source + * a {@link forge.game.card.Card} object. + * @return a int. + */ + public static int handlePaid(final List paidList, final String string, final Card source) { + if (paidList == null) { + if (string.contains(".")) { + final String[] splitString = string.split("\\.", 2); + return doXMath(0, splitString[1], source); + } else { + return 0; + } + } + if (string.startsWith("Amount")) { + if (string.contains(".")) { + final String[] splitString = string.split("\\.", 2); + return doXMath(paidList.size(), splitString[1], source); + } else { + return paidList.size(); + } + + } + if (string.startsWith("Valid")) { + + final String[] splitString = string.split("/", 2); + String valid = splitString[0].substring(6); + final List list = CardLists.getValidCards(paidList, valid, source.getController(), source); + return doXMath(list.size(), splitString.length > 1 ? splitString[1] : null, source); + } + + String filteredString = string; + List filteredList = new ArrayList(paidList); + final String[] filter = filteredString.split("_"); + + if (string.startsWith("FilterControlledBy")) { + final String pString = filter[0].substring(18); + List controllers = new ArrayList(AbilityUtils.getDefinedPlayers(source, pString, null)); + filteredList = CardLists.filterControlledBy(filteredList, controllers); + filteredString = filteredString.replace(pString, ""); + filteredString = filteredString.replace("FilterControlledBy_", ""); + } + + int tot = 0; + for (final Card c : filteredList) { + tot += xCount(c, filteredString); + } + + return tot; + } + + /** + *

+ * isMostProminentColor. + *

+ * + * @param list + * a {@link forge.CardList} object. + * @return a boolean. + */ + public static byte getMostProminentColors(final List list) { + int cntColors = MagicColor.WUBRG.length; + final Integer[] map = new Integer[cntColors]; + for(int i = 0; i < cntColors; i++) { + map[i] = 0; + } + + for (final Card crd : list) { + ColorSet color = CardUtil.getColors(crd); + for(int i = 0; i < cntColors; i++) { + if( color.hasAnyColor(MagicColor.WUBRG[i])) + map[i]++; + } + } // for + + byte mask = 0; + int nMax = -1; + for(int i = 0; i < cntColors; i++) { + if ( map[i] > nMax ) + mask = MagicColor.WUBRG[i]; + else if ( map[i] == nMax ) + mask |= MagicColor.WUBRG[i]; + else + continue; + nMax = map[i]; + } + return mask; + } + + /** + *

+ * SortColorsFromList. + *

+ * + * @param list + * a {@link forge.CardList} object. + * @return a List. + */ + public static int[] SortColorsFromList(final List list) { + int cntColors = MagicColor.WUBRG.length; + final int[] map = new int[cntColors]; + for(int i = 0; i < cntColors; i++) { + map[i] = 0; + } + + for (final Card crd : list) { + ColorSet color = CardUtil.getColors(crd); + for(int i = 0; i < cntColors; i++) { + if( color.hasAnyColor(MagicColor.WUBRG[i])) + map[i]++; + } + } // for + Arrays.sort(map); + return map; + } + + /** + *

+ * getMostProminentColorsFromList. + *

+ * + * @param list + * a {@link forge.CardList} object. + * @return a boolean. + */ + public static byte getMostProminentColorsFromList(final List list, final List restrictedToColors) { + List colorRestrictions = new ArrayList(); + for (final String col : restrictedToColors) { + colorRestrictions.add(MagicColor.fromName(col)); + } + int cntColors = colorRestrictions.size(); + final Integer[] map = new Integer[cntColors]; + for(int i = 0; i < cntColors; i++) { + map[i] = 0; + } + + for (final Card crd : list) { + ColorSet color = CardUtil.getColors(crd); + for (int i = 0; i < cntColors; i++) { + if (color.hasAnyColor(colorRestrictions.get(i))) { + map[i]++; + } + } + } + + byte mask = 0; + int nMax = -1; + for(int i = 0; i < cntColors; i++) { + if ( map[i] > nMax ) + mask = colorRestrictions.get(i); + else if ( map[i] == nMax ) + mask |= colorRestrictions.get(i); + else + continue; + nMax = map[i]; + } + return mask; + } + + /** + *

+ * getMostProminentCreatureType. + *

+ * + * @param list + * a {@link forge.CardList} object. + * @return an int. + */ + public static int getMostProminentCreatureTypeSize(final List list) { + + if (list.isEmpty()) { + return 0; + } + int allCreatureType = 0; + + final Map map = new HashMap(); + for (final Card c : list) { + // Remove Duplicated types + final Set types = new HashSet(c.getType()); + if (types.contains("AllCreatureTypes")) { + allCreatureType++; + continue; + } + for (final String var : types) { + if (CardType.isACreatureType(var)) { + if (!map.containsKey(var)) { + map.put(var, 1); + } else { + map.put(var, map.get(var) + 1); + } + } + } + } + + int max = 0; + for (final Entry entry : map.entrySet()) { + if (max < entry.getValue()) { + max = entry.getValue(); + } + } + + return max + allCreatureType; + } + + /** + *

+ * sharedKeywords. + *

+ * + * @param kw + * a {@link forge.CardList} object. + * @return a List. + */ + public static List sharedKeywords(final String[] kw, final String[] restrictions, + final List zones, final Card host) { + final List filteredkw = new ArrayList(); + final Player p = host.getController(); + List cardlist = new ArrayList(p.getGame().getCardsIn(zones)); + final List landkw = new ArrayList(); + final List protectionkw = new ArrayList(); + final List allkw = new ArrayList(); + + cardlist = CardLists.getValidCards(cardlist, restrictions, p, host); + for (Card c : cardlist) { + for (String k : c.getKeyword()) { + if (k.endsWith("walk")) { + if (!landkw.contains(k)) { + landkw.add(k); + } + } else if (k.startsWith("Protection")) { + if (!protectionkw.contains(k)) { + protectionkw.add(k); + } + } + if (!allkw.contains(k)) { + allkw.add(k); + } + } + } + for (String keyword : kw) { + if (keyword.equals("Protection")) { + filteredkw.addAll(protectionkw); + } else if (keyword.equals("Landwalk")) { + filteredkw.addAll(landkw); + } else if (allkw.contains(keyword)) { + filteredkw.add(keyword); + } + } + return filteredkw; + } + + /** + *

+ * getCardTypesFromList. + *

+ * + * @param list + * a {@link forge.CardList} object. + * @return a int. + */ + public static int getCardTypesFromList(final List list) { + int count = 0; + for (Card c1 : list) { + if (c1.isCreature()) { + count++; + break; + } + } + for (Card c1 : list) { + if (c1.isSorcery()) { + count++; + break; + } + } + for (Card c1 : list) { + if (c1.isInstant()) { + count++; + break; + } + } + for (Card c1 : list) { + if (c1.isArtifact()) { + count++; + break; + } + } + for (Card c1 : list) { + if (c1.isEnchantment()) { + count++; + break; + } + } + for (Card c1 : list) { + if (c1.isLand()) { + count++; + break; + } + } + for (Card c1 : list) { + if (c1.isPlaneswalker()) { + count++; + break; + } + } + for (Card c1 : list) { + if (c1.isTribal()) { + count++; + break; + } + } + return count; + } + + /** + *

+ * getBushidoEffects. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link java.util.ArrayList} object. + */ + public static ArrayList getBushidoEffects(final Card c) { + final ArrayList list = new ArrayList(); + for (final String kw : c.getKeyword()) { + if (kw.contains("Bushido")) { + final String[] parse = kw.split(" "); + final String s = parse[1]; + final int magnitude = Integer.parseInt(s); + + String description = String.format("Bushido %d (When this blocks or becomes blocked, it gets +%d/+%d until end of turn).", magnitude, magnitude, magnitude); + String regularPart = String.format("AB$ Pump | Cost$ 0 | Defined$ CardUID_%d | NumAtt$ +%d | NumDef$ +%d | StackDescription$ %s", c.getUniqueNumber(), magnitude, magnitude, description); + + SpellAbility ability = AbilityFactory.getAbility( regularPart, c); + ability.setDescription(ability.getStackDescription()); + ability.setTrigger(true); // can be copied by Strionic Resonator + list.add(ability); + } + } + return list; + } + + /** + *

+ * getNeededXDamage. + *

+ * + * @param ability + * a {@link forge.game.spellability.SpellAbility} object. + * @return a int. + */ + public static int getNeededXDamage(final SpellAbility ability) { + // when targeting a creature, make sure the AI won't overkill on X + // damage + final Card target = ability.getTargetCard(); + int neededDamage = -1; + + if ((target != null)) { + neededDamage = target.getNetDefense() - target.getDamage(); + } + + return neededDamage; + } + + public static void correctAbilityChainSourceCard(final SpellAbility sa, final Card card) { + + sa.setSourceCard(card); + + if (sa.getSubAbility() != null) { + correctAbilityChainSourceCard(sa.getSubAbility(), card); + } + } + + /** + * Adds the ability factory abilities. + * + * @param card + * the card + */ + public static final void addAbilityFactoryAbilities(final Card card) { + // ************************************************** + // AbilityFactory cards + for (String rawAbility : card.getUnparsedAbilities()) { + card.addSpellAbility(AbilityFactory.getAbility(rawAbility, card)); + } + } + /* + public static final void addCommanderAbilities(final Card cmd) { + ReplacementEffect re = ReplacementHandler.parseReplacement( + "Event$ Moved | Destination$ Graveyard,Exile | ValidCard$ Card.Self | Secondary$ True | Optional$ True | OptionalDecider$ You | ReplaceWith$ CommanderMoveReplacement | " + + "Description$ If a commander would be put into its owner's graveyard or exile from anywhere, that player may put it into the command zone instead.", + cmd, true); + cmd.addReplacementEffect(re); + if(StringUtils.isBlank(cmd.getSVar("CommanderCostRaise"))) // why condition check is needed? + cmd.setSVar("CommanderCostRaise", "Number$0"); + + String cmdManaCost = cmd.getManaCost().toString(); + cmd.setSVar("CommanderMoveReplacement", "DB$ ChangeZone | Origin$ Battlefield,Graveyard,Exile,Library | Destination$ Command | Defined$ ReplacedCard"); + cmd.setSVar("DBCommanderIncCast", "DB$ StoreSVar | SVar$ CommanderCostRaise | Type$ CountSVar | Expression$ CommanderCostRaise/Plus.2"); + SpellAbility sa = AbilityFactory.getAbility("SP$ PermanentCreature | SorcerySpeed$ True | ActivationZone$ Command | SubAbility$ DBCommanderIncCast | Cost$ " + cmdManaCost, cmd); + cmd.addSpellAbility(sa); + + cmd.addIntrinsicAbility("SP$ PermanentCreature | SorcerySpeed$ True | ActivationZone$ Command | SubAbility$ DBCommanderIncCast | Cost$ " + cmdManaCost); + cmd.addStaticAbility("Mode$ RaiseCost | Amount$ CommanderCostRaise | Type$ Spell | ValidCard$ Card.Self+wasCastFromCommand | EffectZone$ All | AffectedZone$ Stack"); + } + */ + public static final String getCommanderInfo(final Player originPlayer ) { + StringBuilder sb = new StringBuilder(); + for(Player p : originPlayer.getGame().getPlayers()) { + if(p.equals(originPlayer)) + continue; + + Map map = p.getCommanderDamage(); + if(map.containsKey(originPlayer.getCommander())) { + sb.append("Commander Damage to " + p.getName() + ": "+ map.get(originPlayer.getCommander()) + "\r\n"); + } + } + + return sb.toString(); + } + + /** + *

+ * postFactoryKeywords. + *

+ * + * @param card + * a {@link forge.game.card.Card} object. + */ + public static void setupKeywordedAbilities(final Card card) { + // this function should handle any keywords that need to be added after + // a spell goes through the factory + // Cards with Cycling abilities + // -1 means keyword "Cycling" not found + + if (hasKeyword(card, "Multikicker") != -1) { + final int n = hasKeyword(card, "Multikicker"); + if (n != -1) { + final String parse = card.getKeyword().get(n).toString(); + final String[] k = parse.split("kicker "); + + final SpellAbility sa = card.getFirstSpellAbility(); + sa.setMultiKickerManaCost(new ManaCost(new ManaCostParser(k[1]))); + } + } + + if(hasKeyword(card, "Fuse") != -1) { + card.getState(CardCharacteristicName.Original).getSpellAbility().add(AbilityFactory.buildFusedAbility(card)); + } + + final int evokePos = hasKeyword(card, "Evoke"); + if (evokePos != -1) { + card.addSpellAbility(makeEvokeSpell(card, card.getKeyword().get(evokePos))); + } + final int monstrousPos = hasKeyword(card, "Monstrosity"); + if (monstrousPos != -1) { + final String parse = card.getKeyword().get(monstrousPos).toString(); + final String[] k = parse.split(":"); + final String magnitude = k[0].substring(12); + final String manacost = k[1]; + card.removeIntrinsicKeyword(parse); + + String ref = "X".equals(magnitude) ? " | References$ X" : ""; + String counters = StringUtils.isNumeric(magnitude) + ? Lang.nounWithNumeral(Integer.parseInt(magnitude), "+1/+1 counter"): "X +1/+1 counters"; + String effect = "AB$ PutCounter | Cost$ " + manacost + " | ConditionPresent$ " + + "Card.Self+IsNotMonstrous | Monstrosity$ True | CounterNum$ " + + magnitude + " | CounterType$ P1P1 | SpellDescription$ Monstrosity " + + magnitude + " (If this creature isn't monstrous, put " + + counters + " on it and it becomes monstrous.) | StackDescription$ SpellDescription" + ref; + + card.addSpellAbility(AbilityFactory.getAbility(effect, card)); + // add ability to instrinic strings so copies/clones create the ability also + card.getUnparsedAbilities().add(effect); + } + + final int iLvlUp = hasKeyword(card, "Level up"); + final int iLvlMax = hasKeyword(card, "maxLevel"); + + if (iLvlUp != -1 && iLvlMax != -1) { + final String strLevelCost = card.getKeyword().get(iLvlUp); + final String strMaxLevel = card.getKeyword().get(iLvlMax); + card.removeIntrinsicKeyword(strLevelCost); + card.removeIntrinsicKeyword(strMaxLevel); + final String[] k = strLevelCost.split(":"); + final String manacost = k[1]; + + final String[] l = strMaxLevel.split(":"); + final int maxLevel = Integer.parseInt(l[1]); + + String effect = "AB$ PutCounter | Cost$ " + manacost + " | " + + "SorcerySpeed$ True | LevelUp$ True | CounterNum$ 1" + + " | CounterType$ LEVEL | PrecostDesc$ Level Up | MaxLevel$ " + + maxLevel + " | SpellDescription$ (Put a level counter on" + + " this permanent. Activate this ability only any time you" + + " could cast a sorcery.)"; + + card.addSpellAbility(AbilityFactory.getAbility(effect, card)); + // add ability to instrinic strings so copies/clones create the ability also + card.getUnparsedAbilities().add(effect); + } // level up + + if (hasKeyword(card, "Cycling") != -1) { + final int n = hasKeyword(card, "Cycling"); + if (n != -1) { + final String parse = card.getKeyword().get(n).toString(); + card.removeIntrinsicKeyword(parse); + + final String[] k = parse.split(":"); + final String manacost = k[1]; + + card.addSpellAbility(abilityCycle(card, manacost)); + } + } // Cycling + + while (hasKeyword(card, "TypeCycling") != -1) { + final int n = hasKeyword(card, "TypeCycling"); + if (n != -1) { + final String parse = card.getKeyword().get(n).toString(); + card.removeIntrinsicKeyword(parse); + + final String[] k = parse.split(":"); + final String type = k[1]; + final String manacost = k[2]; + + card.addSpellAbility(abilityTypecycle(card, manacost, type)); + } + } // TypeCycling + + if (hasKeyword(card, "Transmute") != -1) { + final int n = hasKeyword(card, "Transmute"); + if (n != -1) { + final String parse = card.getKeyword().get(n); + card.removeIntrinsicKeyword(parse); + final String manacost = parse.split(":")[1]; + + card.addSpellAbility(abilityTransmute(card, manacost)); + } + } // transmute + + int shiftPos = hasKeyword(card, "Soulshift"); + while (shiftPos != -1) { + final int n = shiftPos; + final String parse = card.getKeyword().get(n); + final String[] k = parse.split(" "); + final int manacost = Integer.parseInt(k[1]); + + final String actualTrigger = "Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard" + + "| OptionalDecider$ You | ValidCard$ Card.Self | Execute$ SoulshiftAbility" + + "| TriggerController$ TriggeredCardController | TriggerDescription$ " + parse + + " (When this creature dies, you may return target Spirit card with converted mana cost " + + manacost + " or less from your graveyard to your hand.)"; + final String abString = "DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand" + + "| ValidTgts$ Spirit.YouOwn+cmcLE" + manacost; + final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, true); + card.addTrigger(parsedTrigger); + card.setSVar("SoulshiftAbility", abString); + shiftPos = hasKeyword(card, "Soulshift", n + 1); + } // Soulshift + + final int championPos = hasKeyword(card, "Champion"); + if (championPos != -1) { + String parse = card.getKeyword().get(championPos); + card.removeIntrinsicKeyword(parse); + + final String[] k = parse.split(":"); + final String[] valid = k[1].split(","); + String desc = k.length > 2 ? k[2] : k[1]; + + StringBuilder changeType = new StringBuilder(); + for (String v : valid) { + if (changeType.length() != 0) { + changeType.append(","); + } + changeType.append(v).append(".YouCtrl+Other"); + } + + StringBuilder trig = new StringBuilder(); + trig.append("Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | "); + trig.append("Execute$ ChampionAbility | TriggerDescription$ Champion a(n) "); + trig.append(desc).append(" (When this enters the battlefield, sacrifice it unless you exile another "); + trig.append(desc).append(" you control. When this leaves the battlefield, that card returns to the battlefield.)"); + + StringBuilder trigReturn = new StringBuilder(); + trigReturn.append("Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | "); + trigReturn.append("Execute$ ChampionReturn | Secondary$ True | TriggerDescription$ When this leaves the battlefield, that card returns to the battlefield."); + + StringBuilder ab = new StringBuilder(); + ab.append("DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | Champion$ True | "); + ab.append("Hidden$ True | Optional$ True | SubAbility$ DBSacrifice | ChangeType$ ").append(changeType); + + StringBuilder subAb = new StringBuilder(); + subAb.append("DB$ Sacrifice | Defined$ Card.Self | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"); + + String returnChampion = "DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield"; + final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig.toString(), card, true); + final Trigger parsedTrigReturn = TriggerHandler.parseTrigger(trigReturn.toString(), card, true); + card.addTrigger(parsedTrigger); + card.addTrigger(parsedTrigReturn); + card.setSVar("ChampionAbility", ab.toString()); + card.setSVar("ChampionReturn", returnChampion); + card.setSVar("DBSacrifice", subAb.toString()); + } + + if (card.hasKeyword("If CARDNAME would be put into a graveyard " + + "from anywhere, reveal CARDNAME and shuffle it into its owner's library instead.")) { + + String replacement = "Event$ Moved | Destination$ Graveyard | ValidCard$ Card.Self | ReplaceWith$ GraveyardToLibrary"; + String ab = "DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Library | Defined$ ReplacedCard | Reveal$ True | Shuffle$ True"; + + card.addReplacementEffect(ReplacementHandler.parseReplacement(replacement, card, true)); + card.setSVar("GraveyardToLibrary", ab); + } + + final int echoPos = hasKeyword(card, "Echo"); + if (echoPos != -1) { + // card.removeIntrinsicKeyword(parse); + final String[] k = card.getKeyword().get(echoPos).split(":"); + final String manacost = k[1]; + + card.setEchoCost(manacost); + + final Command intoPlay = new Command() { + + private static final long serialVersionUID = -7913835645603984242L; + + @Override + public void run() { + card.addExtrinsicKeyword("(Echo unpaid)"); + } + }; + card.addComesIntoPlayCommand(intoPlay); + } // echo + + if (hasKeyword(card, "Suspend") != -1) { + // Suspend:: + final int n = hasKeyword(card, "Suspend"); + if (n != -1) { + final String parse = card.getKeyword().get(n); + card.removeIntrinsicKeyword(parse); + card.setSuspend(true); + final String[] k = parse.split(":"); + + final String timeCounters = k[1]; + final String cost = k[2]; + card.addSpellAbility(abilitySuspendStatic(card, cost, timeCounters)); + addSuspendUpkeepTrigger(card); + addSuspendPlayTrigger(card); + } + } // Suspend + + if (hasKeyword(card, "Fading") != -1) { + final int n = hasKeyword(card, "Fading"); + if (n != -1) { + final String[] k = card.getKeyword().get(n).split(":"); + + card.addIntrinsicKeyword("etbCounter:FADE:" + k[1] + ":no Condition:no desc"); + + String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield " + + " | Execute$ TrigUpkeepFading | Secondary$ True | TriggerDescription$ At the beginning of " + + "your upkeep, remove a fade counter from CARDNAME. If you can't, sacrifice CARDNAME."; + + card.setSVar("TrigUpkeepFading", "AB$ RemoveCounter | Cost$ 0 | Defined$ Self | CounterType$ FADE" + + " | CounterNum$ 1 | RememberRemoved$ True | SubAbility$ DBUpkeepFadingSac"); + card.setSVar("DBUpkeepFadingSac","DB$ Sacrifice | SacValid$ Self | ConditionCheckSVar$ FadingCheckSVar" + + " | ConditionSVarCompare$ EQ0 | References$ FadingCheckSVar | SubAbility$ FadingCleanup"); + card.setSVar("FadingCleanup","DB$ Cleanup | ClearRemembered$ True"); + card.setSVar("FadingCheckSVar","Count$RememberedSize"); + final Trigger parsedUpkeepTrig = TriggerHandler.parseTrigger(upkeepTrig, card, true); + card.addTrigger(parsedUpkeepTrig); + } + } // Fading + + if (hasKeyword(card, "Vanishing") != -1) { + final int n = hasKeyword(card, "Vanishing"); + if (n != -1) { + final String[] k = card.getKeyword().get(n).split(":"); + // etbcounter + card.addIntrinsicKeyword("etbCounter:TIME:" + k[1] + ":no Condition:no desc"); + // Remove Time counter trigger + String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | " + + "TriggerZones$ Battlefield | IsPresent$ Card.Self+counters_GE1_TIME" + + " | Execute$ TrigUpkeepVanishing | TriggerDescription$ At the " + + "beginning of your upkeep, if CARDNAME has a time counter on it, " + + "remove a time counter from it. | Secondary$ True"; + card.setSVar("TrigUpkeepVanishing", "AB$ RemoveCounter | Cost$ 0 | Defined$ Self" + + " | CounterType$ TIME | CounterNum$ 1"); + final Trigger parsedUpkeepTrig = TriggerHandler.parseTrigger(upkeepTrig, card, true); + card.addTrigger(parsedUpkeepTrig); + // sacrifice trigger + String sacTrig = "Mode$ CounterRemoved | TriggerZones$ Battlefield | ValidCard$" + + " Card.Self | NewCounterAmount$ 0 | Secondary$ True | CounterType$ TIME |" + + " Execute$ TrigVanishingSac | TriggerDescription$ When the last time " + + "counter is removed from CARDNAME, sacrifice it."; + card.setSVar("TrigVanishingSac", "AB$ Sacrifice | Cost$ 0 | SacValid$ Self"); + + final Trigger parsedSacTrigger = TriggerHandler.parseTrigger(sacTrig, card, true); + card.addTrigger(parsedSacTrigger); + } + } // Vanishing + + // AddCost + if (card.hasSVar("FullCost")) { + final SpellAbility sa1 = card.getFirstSpellAbility(); + if (sa1 != null && sa1.isSpell()) { + sa1.setPayCosts(new Cost(card.getSVar("FullCost"), sa1.isAbility())); + } + } + + // AltCost + String altCost = card.getSVar("AltCost"); + if (StringUtils.isNotBlank(altCost)) { + final SpellAbility sa1 = card.getFirstSpellAbility(); + if (sa1 != null && sa1.isSpell()) { + card.addSpellAbility(makeAltCostAbility(card, altCost, sa1)); + } + } + if (card.hasKeyword("Delve")) { + card.getSpellAbilities().get(0).setDelve(true); + } + + if (card.hasStartOfKeyword("Haunt")) { + setupHauntSpell(card); + } + + if (card.hasKeyword("Provoke")) { + final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | " + + "OptionalDecider$ You | Execute$ ProvokeAbility | Secondary$ True | TriggerDescription$ " + + "When this attacks, you may have target creature defending player " + + "controls untap and block it if able."; + final String abString = "DB$ MustBlock | ValidTgts$ Creature.DefenderCtrl | " + + "TgtPrompt$ Select target creature defending player controls | SubAbility$ DBUntap"; + final String dbString = "DB$ Untap | Defined$ Targeted"; + final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, true); + card.addTrigger(parsedTrigger); + card.setSVar("ProvokeAbility", abString); + card.setSVar("DBUntap", dbString); + } + + if (card.hasKeyword("Living Weapon")) { + card.removeIntrinsicKeyword("Living Weapon"); + + final StringBuilder sbTrig = new StringBuilder(); + sbTrig.append("Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | "); + sbTrig.append("ValidCard$ Card.Self | Execute$ TrigGerm | TriggerDescription$ "); + sbTrig.append("Living Weapon (When this Equipment enters the battlefield, "); + sbTrig.append("put a 0/0 black Germ creature token onto the battlefield, then attach this to it.)"); + + final StringBuilder sbGerm = new StringBuilder(); + sbGerm.append("DB$ Token | TokenAmount$ 1 | TokenName$ Germ | TokenTypes$ Creature,Germ | RememberTokens$ True | "); + sbGerm.append("TokenOwner$ You | TokenColors$ Black | TokenPower$ 0 | TokenToughness$ 0 | TokenImage$ B 0 0 Germ | SubAbility$ DBGermAttach"); + + final StringBuilder sbAttach = new StringBuilder(); + sbAttach.append("DB$ Attach | Defined$ Remembered | SubAbility$ DBGermClear"); + + final StringBuilder sbClear = new StringBuilder(); + sbClear.append("DB$ Cleanup | ClearRemembered$ True"); + + card.setSVar("TrigGerm", sbGerm.toString()); + card.setSVar("DBGermAttach", sbAttach.toString()); + card.setSVar("DBGermClear", sbClear.toString()); + + final Trigger etbTrigger = TriggerHandler.parseTrigger(sbTrig.toString(), card, true); + card.addTrigger(etbTrigger); + } + + if (card.hasKeyword("Epic")) { + makeEpic(card); + } + + if (card.hasKeyword("Soulbond")) { + // Setup ETB trigger for card with Soulbond keyword + final String actualTriggerSelf = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | " + + "ValidCard$ Card.Self | Execute$ TrigBondOther | OptionalDecider$ You | " + + "IsPresent$ Creature.Other+YouCtrl+NotPaired | Secondary$ True | " + + "TriggerDescription$ When CARDNAME enters the battlefield, " + + "you may pair CARDNAME with another unpaired creature you control"; + final String abStringSelf = "AB$ Bond | Cost$ 0 | Defined$ Self | ValidCards$ Creature.Other+YouCtrl+NotPaired"; + final Trigger parsedTriggerSelf = TriggerHandler.parseTrigger(actualTriggerSelf, card, true); + card.addTrigger(parsedTriggerSelf); + card.setSVar("TrigBondOther", abStringSelf); + // Setup ETB trigger for other creatures you control + final String actualTriggerOther = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | " + + "ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | OptionalDecider$ You | " + + "Execute$ TrigBondSelf | IsPresent$ Creature.Self+NotPaired | Secondary$ True | " + + " TriggerDescription$ When another unpaired creature you control enters the battlefield, " + + "you may pair it with CARDNAME"; + final String abStringOther = "AB$ Bond | Cost$ 0 | Defined$ TriggeredCard | ValidCards$ Creature.Self+NotPaired"; + final Trigger parsedTriggerOther = TriggerHandler.parseTrigger(actualTriggerOther, card, true); + card.addTrigger(parsedTriggerOther); + card.setSVar("TrigBondSelf", abStringOther); + } + + if (card.hasKeyword("Extort")) { + final String extortTrigger = "Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | " + + "TriggerZones$ Battlefield | Execute$ ExtortOpps | Secondary$ True" + + " | TriggerDescription$ Extort (Whenever you cast a spell, you may pay W/B. If you do, " + + "each opponent loses 1 life and you gain that much life.)"; + final String abString = "AB$ LoseLife | Cost$ WB | Defined$ Player.Opponent | " + + "LifeAmount$ 1 | SubAbility$ ExtortGainLife"; + final String dbString = "DB$ GainLife | Defined$ You | LifeAmount$ AFLifeLost | References$ AFLifeLost"; + final Trigger parsedTrigger = TriggerHandler.parseTrigger(extortTrigger, card, true); + card.addTrigger(parsedTrigger); + card.setSVar("ExtortOpps", abString); + card.setSVar("ExtortGainLife", dbString); + card.setSVar("AFLifeLost", "Number$0"); + } + + if (card.hasKeyword("Evolve")) { + final String evolveTrigger = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | " + + " ValidCard$ Creature.YouCtrl+Other | EvolveCondition$ True | " + + "TriggerZones$ Battlefield | Execute$ EvolveAddCounter | Secondary$ True | " + + "TriggerDescription$ Evolve (Whenever a creature enters the battlefield under your " + + "control, if that creature has greater power or toughness than this creature, put a " + + "+1/+1 counter on this creature.)"; + final String abString = "AB$ PutCounter | Cost$ 0 | Defined$ Self | CounterType$ P1P1 | " + + "CounterNum$ 1 | Evolve$ True"; + final Trigger parsedTrigger = TriggerHandler.parseTrigger(evolveTrigger, card, true); + card.addTrigger(parsedTrigger); + card.setSVar("EvolveAddCounter", abString); + } + + if (card.hasStartOfKeyword("Dredge")) { + final int dredgeAmount = card.getKeywordMagnitude("Dredge"); + + final String actualRep = "Event$ Draw | ActiveZones$ Graveyard | ValidPlayer$ You | " + + "ReplaceWith$ DredgeCards | Secondary$ True | Optional$ True | CheckSVar$ " + + "DredgeCheckLib | SVarCompare$ GE" + dredgeAmount + " | References$ " + + "DredgeCheckLib | AICheckDredge$ True | Description$ " + card.getName() + + " - Dredge " + dredgeAmount; + final String abString = "DB$ Mill | Defined$ You | NumCards$ " + dredgeAmount + " | " + + "SubAbility$ DredgeMoveToPlay"; + final String moveToPlay = "DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | " + + "Defined$ Self"; + final String checkSVar = "Count$ValidLibrary Card.YouOwn"; + card.setSVar("DredgeCards", abString); + card.setSVar("DredgeMoveToPlay", moveToPlay); + card.setSVar("DredgeCheckLib", checkSVar); + card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true)); + } + + if (card.hasStartOfKeyword("Tribute")) { + final int tributeAmount = card.getKeywordMagnitude("Tribute"); + + final String actualRep = "Event$ Moved | Destination$ Battlefield | ValidCard$ Card.Self |" + + " ReplaceWith$ TributeAddCounter | Secondary$ True | Description$ Tribute " + + tributeAmount + " (As this creature enters the battlefield, an opponent of your" + + " choice may place " + tributeAmount + " +1/+1 counter on it.)"; + final String abString = "DB$ PutCounter | Defined$ ReplacedCard | Tribute$ True | " + + "CounterType$ P1P1 | CounterNum$ " + tributeAmount + + " | SubAbility$ TributeMoveToPlay"; + final String moveToPlay = "DB$ ChangeZone | Origin$ All | Destination$ Battlefield | " + + "Defined$ ReplacedCard | Hidden$ True"; + card.setSVar("TributeAddCounter", abString); + card.setSVar("TributeMoveToPlay", moveToPlay); + card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true)); + } + + if (card.hasStartOfKeyword("Amplify")) { + // find position of Amplify keyword + final int ampPos = card.getKeywordPosition("Amplify"); + final String[] ampString = card.getKeyword().get(ampPos).split(":"); + final String amplifyMagnitude = ampString[1]; + final String suffix = !amplifyMagnitude.equals("1") ? "s" : ""; + final String ampTypes = ampString[2]; + String[] refinedTypes = ampTypes.split(","); + final StringBuilder types = new StringBuilder(); + for (int i = 0; i < refinedTypes.length; i++) { + types.append("Card.").append(refinedTypes[i]).append("+YouCtrl"); + if (i + 1 != refinedTypes.length) { + types.append(","); + } + } + // Setup ETB replacement effects + final String actualRep = "Event$ Moved | Destination$ Battlefield | ValidCard$ Card.Self |" + + " ReplaceWith$ AmplifyReveal | Secondary$ True | Description$ As this creature " + + "enters the battlefield, put " + amplifyMagnitude + " +1/+1 counter" + suffix + + " on it for each " + ampTypes.replace(",", " and/or ") + + " card you reveal in your hand.)"; + final String abString = "DB$ Reveal | AnyNumber$ True | RevealValid$ " + + types.toString() + " | RememberRevealed$ True | SubAbility$ AmplifyMoveToPlay"; + final String moveToPlay = "DB$ ChangeZone | Origin$ All | Destination$ Battlefield | " + + "Defined$ ReplacedCard | Hidden$ True | SubAbility$ Amplify"; + final String dbString = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | " + + "CounterNum$ AmpMagnitude | References$ Revealed,AmpMagnitude | SubAbility$" + + " DBCleanup"; + card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true)); + card.setSVar("AmplifyReveal", abString); + card.setSVar("AmplifyMoveToPlay", moveToPlay); + card.setSVar("Amplify", dbString); + card.setSVar("DBCleanup", "DB$ Cleanup | ClearRemembered$ True"); + card.setSVar("AmpMagnitude", "SVar$Revealed/Times." + amplifyMagnitude); + card.setSVar("Revealed", "Remembered$Amount"); + } + + if (card.hasStartOfKeyword("Equip")) { + // find position of Equip keyword + final int equipPos = card.getKeywordPosition("Equip"); + // Check for additional params such as preferred AI targets + final String equipString = card.getKeyword().get(equipPos).substring(5); + final String[] equipExtras = equipString.contains("|") ? equipString.split("\\|", 2) : null; + // Get cost string + String equipCost = ""; + if (equipExtras != null) { + equipCost = equipExtras[0].trim(); + } else { + equipCost = equipString.trim(); + } + // Create attach ability string + final StringBuilder abilityStr = new StringBuilder(); + abilityStr.append("AB$ Attach | Cost$ "); + abilityStr.append(equipCost); + abilityStr.append(" | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control "); + abilityStr.append("| SorcerySpeed$ True | Equip$ True | AILogic$ Pump | IsPresent$ Card.Self+nonCreature "); + if (equipExtras != null) { + abilityStr.append("| ").append(equipExtras[1]).append(" "); + } + abilityStr.append("| PrecostDesc$ Equip "); + Cost cost = new Cost(equipCost, true); + if (!cost.isOnlyManaCost()) { //Something other than a mana cost + abilityStr.append("- "); + } + abilityStr.append("| CostDesc$ " + cost.toSimpleString() + " "); + abilityStr.append("| SpellDescription$ (" + cost.toSimpleString() + ": Attach to target creature you control. Equip only as a sorcery.)"); + // instantiate attach ability + final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); + card.addSpellAbility(sa); + // add ability to instrinic strings so copies/clones create the ability also + card.getUnparsedAbilities().add(abilityStr.toString()); + } + + if (card.hasStartOfKeyword("Fortify")) { + final int equipPos = card.getKeywordPosition("Fortify"); + final String equipString = card.getKeyword().get(equipPos).substring(7); + final String[] equipExtras = equipString.contains("|") ? equipString.split("\\|", 2) : null; + // Get cost string + String equipCost = ""; + if (equipExtras != null) { + equipCost = equipExtras[0].trim(); + } else { + equipCost = equipString.trim(); + } + // Create attach ability string + final StringBuilder abilityStr = new StringBuilder(); + abilityStr.append("AB$ Attach | Cost$ "); + abilityStr.append(equipCost); + abilityStr.append(" | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select target land you control "); + abilityStr.append("| SorcerySpeed$ True | AILogic$ Pump | IsPresent$ Card.Self+nonCreature "); + if (equipExtras != null) { + abilityStr.append("| ").append(equipExtras[1]).append(" "); + } + abilityStr.append("| PrecostDesc$ Fortify "); + Cost cost = new Cost(equipCost, true); + if (!cost.isOnlyManaCost()) { //Something other than a mana cost + abilityStr.append("- "); + } + abilityStr.append("| CostDesc$ " + cost.toSimpleString() + " "); + abilityStr.append("| SpellDescription$ (" + cost.toSimpleString() + ": Attach to target land you control. Fortify only as a sorcery.)"); + + // instantiate attach ability + final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); + card.addSpellAbility(sa); + // add ability to instrinic strings so copies/clones create the ability also + card.getUnparsedAbilities().add(abilityStr.toString()); + } + + if (card.hasStartOfKeyword("Bestow")) { + final int bestowPos = card.getKeywordPosition("Bestow"); + final String cost = card.getKeyword().get(bestowPos).split(":")[1]; + card.removeIntrinsicKeyword(card.getKeyword().get(bestowPos)); + + final StringBuilder sbAttach = new StringBuilder(); + sbAttach.append("SP$ Attach | Cost$ "); + sbAttach.append(cost); + sbAttach.append(" | AILogic$ Pump | Bestow$ True | ValidTgts$ Creature"); + final SpellAbility bestow = AbilityFactory.getAbility(sbAttach.toString(), card); + + bestow.setDescription("Bestow " + ManaCostParser.parse(cost) + " (If you cast this" + + " card for its bestow cost, it's an Aura spell with enchant creature. It" + + " becomes a creature again if it's not attached to a creature.)"); + bestow.setStackDescription("Bestow - " + card.getName()); + bestow.setBasicSpell(false); + card.addSpellAbility(bestow); + card.getUnparsedAbilities().add(sbAttach.toString()); + + } + + setupEtbKeywords(card); + } + + /** + * TODO: Write javadoc for this method. + * @param card + */ + private static void setupEtbKeywords(final Card card) { + for (String kw : card.getKeyword()) { + + if (kw.startsWith("ETBReplacement")) { + String[] splitkw = kw.split(":"); + ReplacementLayer layer = ReplacementLayer.smartValueOf(splitkw[1]); + SpellAbility repAb = AbilityFactory.getAbility(card.getSVar(splitkw[2]), card); + String desc = repAb.getDescription(); + setupETBReplacementAbility(repAb); + + final String valid = splitkw.length >= 6 ? splitkw[5] : "Card.Self"; + + StringBuilder repEffsb = new StringBuilder(); + repEffsb.append("Event$ Moved | ValidCard$ ").append(valid); + repEffsb.append(" | Destination$ Battlefield | Description$ ").append(desc); + if (splitkw.length >= 4) { + if (splitkw[3].contains("Optional")) { + repEffsb.append(" | Optional$ True"); + } + } + if (splitkw.length >= 5) { + if (!splitkw[4].isEmpty()) { + repEffsb.append(" | ActiveZones$ " + splitkw[4]); + } + } + + ReplacementEffect re = ReplacementHandler.parseReplacement(repEffsb.toString(), card, true); + re.setLayer(layer); + re.setOverridingAbility(repAb); + + card.addReplacementEffect(re); + } else if (kw.startsWith("etbCounter")) { + String parse = kw; + card.removeIntrinsicKeyword(parse); + + String[] splitkw = parse.split(":"); + + String desc = "CARDNAME enters the battlefield with " + splitkw[2] + " " + + CounterType.valueOf(splitkw[1]).getName() + " counters on it."; + String extraparams = ""; + String amount = splitkw[2]; + if (splitkw.length > 3) { + if (!splitkw[3].equals("no Condition")) { + extraparams = splitkw[3]; + } + } + if (splitkw.length > 4) { + desc = !splitkw[4].equals("no desc") ? splitkw[4] : ""; + } + String abStr = "AB$ ChangeZone | Cost$ 0 | Hidden$ True | Origin$ All | Destination$ Battlefield" + + "| Defined$ ReplacedCard | SubAbility$ ETBCounterDBSVar"; + String dbStr = "DB$ PutCounter | Defined$ Self | CounterType$ " + splitkw[1] + " | CounterNum$ " + amount; + try { + Integer.parseInt(amount); + } + catch (NumberFormatException ignored) { + dbStr += " | References$ " + amount; + } + card.setSVar("ETBCounterSVar", abStr); + card.setSVar("ETBCounterDBSVar", dbStr); + + String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield " + + "| ReplaceWith$ ETBCounterSVar | Description$ " + desc + (!extraparams.equals("") ? " | " + extraparams : ""); + + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true); + re.setLayer(ReplacementLayer.Other); + + card.addReplacementEffect(re); + } else if (kw.equals("CARDNAME enters the battlefield tapped.")) { + String parse = kw; + card.removeIntrinsicKeyword(parse); + + String abStr = "AB$ Tap | Cost$ 0 | Defined$ Self | ETB$ True | SubAbility$ MoveETB"; + String dbStr = "DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Battlefield" + + "| Defined$ ReplacedCard"; + + card.setSVar("ETBTappedSVar", abStr); + card.setSVar("MoveETB", dbStr); + + String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield " + + "| ReplaceWith$ ETBTappedSVar | Description$ CARDNAME enters the battlefield tapped."; + + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true); + re.setLayer(ReplacementLayer.Other); + + card.addReplacementEffect(re); + } + } + } + + /** + * TODO: Write javadoc for this method. + * @param card + * @return + */ + private static void makeEpic(final Card card) { + + // Add the Epic effect as a subAbility + String dbStr = "DB$ Effect | Triggers$ EpicTrigger | SVars$ EpicCopy | StaticAbilities$ EpicCantBeCast | Duration$ Permanent | Unique$ True"; + + final AbilitySub newSA = (AbilitySub) AbilityFactory.getAbility(dbStr.toString(), card); + + card.setSVar("EpicCantBeCast", "Mode$ CantBeCast | ValidCard$ Card | Caster$ You | EffectZone$ Command | Description$ For the rest of the game, you can't cast spells."); + card.setSVar("EpicTrigger", "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ EpicCopy | TriggerDescription$ " + + "At the beginning of each of your upkeeps, copy " + card.toString() + " except for its epic ability."); + card.setSVar("EpicCopy", "DB$ CopySpellAbility | Defined$ EffectSource"); + + final SpellAbility origSA = card.getSpellAbilities().get(0); + + SpellAbility child = origSA; + while (child.getSubAbility() != null) { + child = child.getSubAbility(); + } + child.setSubAbility(newSA); + newSA.setParent(child); + } + + /** + * TODO: Write javadoc for this method. + * @param card + */ + private static void setupHauntSpell(final Card card) { + final int hauntPos = card.getKeywordPosition("Haunt"); + final String[] splitKeyword = card.getKeyword().get(hauntPos).split(":"); + final String hauntSVarName = splitKeyword[1]; + final String abilityDescription = splitKeyword[2]; + final String hauntAbilityDescription = abilityDescription.substring(0, 1).toLowerCase() + + abilityDescription.substring(1); + String hauntDescription; + if (card.isCreature()) { + final StringBuilder sb = new StringBuilder(); + sb.append("When ").append(card.getName()); + sb.append(" enters the battlefield or the creature it haunts dies, "); + sb.append(hauntAbilityDescription); + hauntDescription = sb.toString(); + } else { + final StringBuilder sb = new StringBuilder(); + sb.append("When the creature ").append(card.getName()); + sb.append(" haunts dies, ").append(hauntAbilityDescription); + hauntDescription = sb.toString(); + } + + card.getKeyword().remove(hauntPos); + + // First, create trigger that runs when the haunter goes to the + // graveyard + final StringBuilder sbHaunter = new StringBuilder(); + sbHaunter.append("Mode$ ChangesZone | Origin$ Battlefield | "); + sbHaunter.append("Destination$ Graveyard | ValidCard$ Card.Self | "); + sbHaunter.append("Static$ True | Secondary$ True | TriggerDescription$ Blank"); + + final Trigger haunterDies = TriggerHandler.parseTrigger(sbHaunter.toString(), card, true); + + final Ability haunterDiesWork = new Ability(card, ManaCost.ZERO) { + @Override + public void resolve() { + this.getTargets().getFirstTargetedCard().addHauntedBy(card); + card.getGame().getAction().exile(card); + } + }; + haunterDiesWork.setDescription(hauntDescription); + haunterDiesWork.setTargetRestrictions(new TargetRestrictions(null, new String[]{"Creature"}, "1", "1")); // not null to make stack preserve targets set + + final Ability haunterDiesSetup = new Ability(card, ManaCost.ZERO) { + @Override + public void resolve() { + final Game game = card.getGame(); + this.setActivatingPlayer(card.getController()); + haunterDiesWork.setActivatingPlayer(card.getController()); + List allCreatures = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); + final List creats = CardLists.getTargetableCards(allCreatures, haunterDiesWork); + if (creats.isEmpty()) { + return; + } + + final Card toHaunt = card.getController().getController().chooseSingleEntityForEffect(creats, new SpellAbility.EmptySa(ApiType.InternalHaunt, card), "Choose target creature to haunt."); + haunterDiesWork.setTargetCard(toHaunt); + haunterDiesWork.setActivatingPlayer(card.getController()); + game.getStack().add(haunterDiesWork); + } + }; + + haunterDies.setOverridingAbility(haunterDiesSetup); + + // Second, create the trigger that runs when the haunted creature dies + final StringBuilder sbDies = new StringBuilder(); + sbDies.append("Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | "); + sbDies.append("ValidCard$ Creature.HauntedBy | Execute$ ").append(hauntSVarName); + sbDies.append(" | TriggerDescription$ ").append(hauntDescription); + + final Trigger hauntedDies = forge.game.trigger.TriggerHandler.parseTrigger(sbDies.toString(), card, true); + + // Third, create the trigger that runs when the haunting creature + // enters the battlefield + final StringBuilder sbETB = new StringBuilder(); + sbETB.append("Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ "); + sbETB.append(hauntSVarName).append(" | Secondary$ True | TriggerDescription$ "); + sbETB.append(hauntDescription); + + final Trigger haunterETB = forge.game.trigger.TriggerHandler.parseTrigger(sbETB.toString(), card, true); + + // Fourth, create a trigger that removes the haunting status if the + // haunter leaves the exile + final StringBuilder sbUnExiled = new StringBuilder(); + sbUnExiled.append("Mode$ ChangesZone | Origin$ Exile | Destination$ Any | "); + sbUnExiled.append("ValidCard$ Card.Self | Static$ True | Secondary$ True | "); + sbUnExiled.append("TriggerDescription$ Blank"); + + final Trigger haunterUnExiled = forge.game.trigger.TriggerHandler.parseTrigger(sbUnExiled.toString(), card, + true); + + final Ability haunterUnExiledWork = new Ability(card, ManaCost.ZERO) { + @Override + public void resolve() { + if (card.getHaunting() != null) { + card.getHaunting().removeHauntedBy(card); + card.setHaunting(null); + } + } + }; + + haunterUnExiled.setOverridingAbility(haunterUnExiledWork); + + // Fifth, add all triggers and abilities to the card. + if (card.isCreature()) { + card.addTrigger(haunterETB); + card.addTrigger(haunterDies); + } else { + final String abString = card.getSVar(hauntSVarName).replace("AB$", "SP$") + .replace("Cost$ 0", "Cost$ " + card.getManaCost()) + + " | SpellDescription$ " + abilityDescription; + + final SpellAbility sa = AbilityFactory.getAbility(abString, card); + card.addSpellAbility(sa); + } + + card.addTrigger(hauntedDies); + card.addTrigger(haunterUnExiled); + } + + /** + * TODO: Write javadoc for this method. + * @param card + * @param abilities + * @return + */ + private static SpellAbility makeAltCostAbility(final Card card, final String altCost, final SpellAbility sa) { + final Map params = AbilityFactory.getMapParams(altCost); + + final SpellAbility altCostSA = sa.copy(); + final Cost abCost = new Cost(params.get("Cost"), altCostSA.isAbility()); + altCostSA.setPayCosts(abCost); + altCostSA.setBasicSpell(false); + altCostSA.addOptionalCost(OptionalCost.AltCost); + + final SpellAbilityRestriction restriction = new SpellAbilityRestriction(); + restriction.setRestrictions(params); + if (!params.containsKey("ActivationZone")) { + restriction.setZone(ZoneType.Hand); + } + altCostSA.setRestrictions(restriction); + + final String costDescription = params.containsKey("Description") ? params.get("Description") + : String.format("You may %s rather than pay %s's mana cost.", abCost.toStringAlt(), card.getName()); + + altCostSA.setDescription(costDescription); + return altCostSA; + } + + /** + * TODO: Write javadoc for this method. + * @param card + * @param evokeKeyword + * @return + */ + private static SpellAbility makeEvokeSpell(final Card card, final String evokeKeyword) { + final String[] k = evokeKeyword.split(":"); + final Cost evokedCost = new Cost(k[1], false); + + final SpellAbility evokedSpell = new Spell(card, evokedCost) { + private static final long serialVersionUID = -1598664196463358630L; + + @Override + public void resolve() { + final Game game = card.getGame(); + card.setEvoked(true); + game.getAction().moveToPlay(card); + } + + @Override + public boolean canPlayAI(Player aiPlayer) { + final Game game = card.getGame(); + if (!SpellPermanent.checkETBEffects(card, aiPlayer)) { + return false; + } + // Wait for Main2 if possible + if (game.getPhaseHandler().is(PhaseType.MAIN1) + && game.getPhaseHandler().isPlayerTurn(aiPlayer) + && aiPlayer.getManaPool().totalMana() <= 0 + && !ComputerUtil.castPermanentInMain1(aiPlayer, this)) { + return false; + } + + return super.canPlayAI(aiPlayer); + } + }; + card.removeIntrinsicKeyword(evokeKeyword); + final StringBuilder desc = new StringBuilder(); + desc.append("Evoke ").append(evokedCost.toSimpleString()); + desc.append(" (You may cast this spell for its evoke cost. "); + desc.append("If you do, when it enters the battlefield, sacrifice it.)"); + + evokedSpell.setDescription(desc.toString()); + + final StringBuilder sb = new StringBuilder(); + sb.append(card.getName()).append(" (Evoked)"); + evokedSpell.setStackDescription(sb.toString()); + evokedSpell.setBasicSpell(false); + return evokedSpell; + } + + private static final Map emptyMap = new TreeMap(); + public static void setupETBReplacementAbility(SpellAbility sa) { + sa.appendSubAbility(new AbilitySub(ApiType.InternalEtbReplacement, sa.getSourceCard(), null, emptyMap)); + // ETBReplacementMove(sa.getSourceCard(), null)); + } + + /** + *

+ * hasKeyword. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param k + * a {@link java.lang.String} object. + * @return a int. + */ + public static final int hasKeyword(final Card c, final String k) { + return hasKeyword(c, k, 0); + } + + /** + *

+ * hasKeyword. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param k + * a {@link java.lang.String} object. + * @param startPos + * a int. + * @return a int. + */ + private static final int hasKeyword(final Card c, final String k, final int startPos) { + final List a = c.getKeyword(); + for (int i = startPos; i < a.size(); i++) { + if (a.get(i).startsWith(k)) { + return i; + } + } + + return -1; + } + + /** + *

+ * parseKeywords. + *

+ * Pulling out the parsing of keywords so it can be used by the token + * generator + * + * @param card + * a {@link forge.game.card.Card} object. + * @param cardName + * a {@link java.lang.String} object. + * + */ + public static final void parseKeywords(final Card card, final String cardName) { + if (card.hasKeyword("CARDNAME enters the battlefield tapped unless you control two or fewer other lands.")) { + card.addComesIntoPlayCommand(new Command() { + private static final long serialVersionUID = 6436821515525468682L; + + @Override + public void run() { + final List lands = card.getController().getLandsInPlay(); + lands.remove(card); + if (!(lands.size() <= 2)) { + // it enters the battlefield this way, and should not + // fire triggers + card.setTapped(true); + } + } + }); + } + if (hasKeyword(card, "CARDNAME enters the battlefield tapped unless you control a") != -1) { + final int n = hasKeyword(card, + "CARDNAME enters the battlefield tapped unless you control a"); + final String parse = card.getKeyword().get(n).toString(); + + String splitString; + if (parse.contains(" or a ")) { + splitString = " or a "; + } else { + splitString = " or an "; + } + + final String[] types = parse.substring(60, parse.length() - 1).split(splitString); + + card.addComesIntoPlayCommand(new Command() { + private static final long serialVersionUID = 403635232455049834L; + + @Override + public void run() { + final List clICtrl = card.getOwner().getCardsIn(ZoneType.Battlefield); + + boolean fnd = false; + + for (int i = 0; i < clICtrl.size(); i++) { + final Card c = clICtrl.get(i); + for (final String type : types) { + if (c.isType(type.trim())) { + fnd = true; + } + } + } + + if (!fnd) { + // it enters the battlefield this way, and should not + // fire triggers + card.setTapped(true); + } + } + }); + } + if (hasKeyword(card, "Sunburst") != -1) { + final Command sunburstCIP = new Command() { + private static final long serialVersionUID = 1489845860231758299L; + + @Override + public void run() { + if (card.isCreature()) { + card.addCounter(CounterType.P1P1, card.getSunburstValue(), true); + } else { + card.addCounter(CounterType.CHARGE, card.getSunburstValue(), true); + } + + } + }; + + final Command sunburstLP = new Command() { + private static final long serialVersionUID = -7564420917490677427L; + + @Override + public void run() { + card.setSunburstValue(0); + } + }; + + card.addComesIntoPlayCommand(sunburstCIP); + card.addLeavesPlayCommand(sunburstLP); + } + + // Enforce the "World rule" + if (card.isType("World")) { + final Command intoPlay = new Command() { + private static final long serialVersionUID = 6536398032388958127L; + + @Override + public void run() { + final Game game = card.getGame(); + final List cardsInPlay = CardLists.getType(game.getCardsIn(ZoneType.Battlefield), "World"); + cardsInPlay.remove(card); + for (int i = 0; i < cardsInPlay.size(); i++) { + game.getAction().sacrificeDestroy(cardsInPlay.get(i)); + } + } // execute() + }; // Command + card.addComesIntoPlayCommand(intoPlay); + } + + if (hasKeyword(card, "Morph") != -1) { + final int n = hasKeyword(card, "Morph"); + if (n != -1) { + + final String parse = card.getKeyword().get(n).toString(); + Map sVars = card.getSVars(); + + final String[] k = parse.split(":"); + final Cost cost = new Cost(k[1], true); + + card.addSpellAbility(abilityMorphDown(card)); + + card.turnFaceDown(); + + card.addSpellAbility(abilityMorphUp(card, cost)); + card.setSVars(sVars); // for Warbreak Trumpeter. + + card.setState(CardCharacteristicName.Original); + } + } // Morph + + if (hasKeyword(card, "Unearth") != -1) { + final int n = hasKeyword(card, "Unearth"); + if (n != -1) { + final String parse = card.getKeyword().get(n).toString(); + // card.removeIntrinsicKeyword(parse); + + final String[] k = parse.split(":"); + + final String manacost = k[1]; + + card.addSpellAbility(abilityUnearth(card, manacost)); + } + } // unearth + + if (hasKeyword(card, "Madness") != -1) { + final int n = hasKeyword(card, "Madness"); + if (n != -1) { + // Set Madness Replacement effects + String repeffstr = "Event$ Discard | ActiveZones$ Hand | ValidCard$ Card.Self | " + + "ReplaceWith$ DiscardMadness | Secondary$ True | Description$ If you would" + + " discard this card, you discard it, but may exile it instead of putting it" + + " into your graveyard"; + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true); + card.addReplacementEffect(re); + String sVarMadness = "DB$ Discard | Defined$ ReplacedPlayer" + + " | Mode$ Defined | DefinedCards$ ReplacedCard | Madness$ True"; + card.setSVar("DiscardMadness", sVarMadness); + + // Set Madness Triggers + final String parse = card.getKeyword().get(n).toString(); + // card.removeIntrinsicKeyword(parse); + final String[] k = parse.split(":"); + String trigStr = "Mode$ Discarded | ValidCard$ Card.Self | IsMadness$ True | " + + "Execute$ TrigPlayMadness | Secondary$ True | TriggerDescription$ " + + "Play Madness - " + card.getName(); + final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, true); + card.addTrigger(myTrigger); + String playMadness = "AB$ Play | Cost$ 0 | Defined$ Self | PlayMadness$ " + k[1] + + " | Optional$ True | SubAbility$ DBWasNotPlayMadness | RememberPlayed$ True"; + String moveToYard = "DB$ ChangeZone | Defined$ Self | Origin$ Exile | " + + "Destination$ Graveyard | ConditionDefined$ Remembered | ConditionPresent$" + + " Card | ConditionCompare$ EQ0 | SubAbility$ DBMadnessCleanup"; + String cleanUp = "DB$ Cleanup | ClearRemembered$ True"; + card.setSVar("TrigPlayMadness", playMadness); + card.setSVar("DBWasNotPlayMadness", moveToYard); + card.setSVar("DBMadnessCleanup", cleanUp); + } + } // madness + + if (hasKeyword(card, "Miracle") != -1) { + final int n = hasKeyword(card, "Miracle"); + if (n != -1) { + final String parse = card.getKeyword().get(n).toString(); + // card.removeIntrinsicKeyword(parse); + + final String[] k = parse.split(":"); + card.setMiracleCost(new Cost(k[1], false)); + } + } // miracle + + if (hasKeyword(card, "Devour") != -1) { + final int n = hasKeyword(card, "Devour"); + if (n != -1) { + + final String parse = card.getKeyword().get(n).toString(); + // card.removeIntrinsicKeyword(parse); + + final String[] k = parse.split(":"); + final String magnitude = k[1]; + + String abStr = "AB$ ChangeZone | Cost$ 0 | Hidden$ True | Origin$ All | " + + "Destination$ Battlefield | Defined$ ReplacedCard | SubAbility$ DevourSac"; + String dbStr = "DB$ Sacrifice | Defined$ You | Amount$ DevourSacX | " + + "References$ DevourSacX | SacValid$ Creature.Other | SacMessage$ creature (Devour " + + magnitude + ") | RememberSacrificed$ True | Optional$ True | " + + "Devour$ True | SubAbility$ DevourCounters"; + String counterStr = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ DevourX" + + " | References$ DevourX,DevourSize | SubAbility$ DevourCleanup"; + + card.setSVar("DevourETB", abStr); + card.setSVar("DevourSac", dbStr); + card.setSVar("DevourSacX", "Count$Valid Creature.YouCtrl+Other"); + card.setSVar("DevourCounters", counterStr); + card.setSVar("DevourX", "SVar$DevourSize/Times." + magnitude); + card.setSVar("DevourSize", "Count$RememberedSize"); + card.setSVar("DevourCleanup", "DB$ Cleanup | ClearRemembered$ True"); + + String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$ DevourETB"; + + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true); + re.setLayer(ReplacementLayer.Other); + card.addReplacementEffect(re); + } + } // Devour + + if (hasKeyword(card, "Modular") != -1) { + final int n = hasKeyword(card, "Modular"); + if (n != -1) { + final String parse = card.getKeyword().get(n).toString(); + card.getKeyword().remove(parse); + + final int m = Integer.parseInt(parse.substring(8)); + + card.addIntrinsicKeyword("etbCounter:P1P1:" + m + ":no Condition: " + + "Modular " + m + " (This enters the battlefield with " + m + " +1/+1 counters on it. When it's put into a graveyard, " + + "you may put its +1/+1 counters on target artifact creature.)"); + + final String abStr = "AB$ PutCounter | Cost$ 0 | References$ ModularX | ValidTgts$ Artifact.Creature | " + + "TgtPrompt$ Select target artifact creature | CounterType$ P1P1 | CounterNum$ ModularX"; + card.setSVar("ModularTrig", abStr); + card.setSVar("ModularX", "TriggeredCard$CardCounters.P1P1"); + + String trigStr = "Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Graveyard" + + " | OptionalDecider$ TriggeredCardController | TriggerController$ TriggeredCardController | Execute$ ModularTrig | " + + "Secondary$ True | TriggerDescription$ When CARDNAME is put into a graveyard from the battlefield, " + + "you may put a +1/+1 counter on target artifact creature for each +1/+1 counter on CARDNAME"; + final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, true); + card.addTrigger(myTrigger); + } + } // Modular + + /* + * WARNING: must keep this keyword processing before etbCounter keyword + * processing. + */ + final int graft = hasKeyword(card, "Graft"); + if (graft != -1) { + final String parse = card.getKeyword().get(graft).toString(); + + final int m = Integer.parseInt(parse.substring(6)); + final String abStr = "AB$ MoveCounter | Cost$ 0 | Source$ Self | " + + "Defined$ TriggeredCard | CounterType$ P1P1 | CounterNum$ 1"; + card.setSVar("GraftTrig", abStr); + + String trigStr = "Mode$ ChangesZone | ValidCard$ Creature.Other | " + + "Origin$ Any | Destination$ Battlefield" + + " | TriggerZones$ Battlefield | OptionalDecider$ You | " + + "IsPresent$ Card.Self+counters_GE1_P1P1 | " + + "Execute$ GraftTrig | TriggerDescription$ " + + "Whenever another creature enters the battlefield, you " + + "may move a +1/+1 counter from this creature onto it."; + final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, true); + card.addTrigger(myTrigger); + + card.addIntrinsicKeyword("etbCounter:P1P1:" + m); + } + + final int bloodthirst = hasKeyword(card, "Bloodthirst"); + if (bloodthirst != -1) { + final String numCounters = card.getKeyword().get(bloodthirst).split(" ")[1]; + String desc = "Bloodthirst " + + numCounters + " (If an opponent was dealt damage this turn, this creature enters the battlefield with " + + numCounters + " +1/+1 counters on it.)"; + if (numCounters.equals("X")) { + desc = "Bloodthirst X (This creature enters the battlefield with X +1/+1 counters on it, " + + "where X is the damage dealt to your opponents this turn.)"; + card.setSVar("X", "Count$BloodthirstAmount"); + } + + card.addIntrinsicKeyword("etbCounter:P1P1:" + numCounters + ":Bloodthirst$ True:" + desc); + } // bloodthirst + + final int storm = card.getKeywordAmount("Storm"); + for (int i = 0; i < storm; i++) { + final StringBuilder trigScript = new StringBuilder( + "Mode$ SpellCast | ValidCard$ Card.Self | Execute$ Storm " + + "| TriggerDescription$ Storm (When you cast this spell, " + + "copy it for each spell cast before it this turn.)"); + + card.setSVar("Storm", "AB$ CopySpellAbility | Cost$ 0 | Defined$ TriggeredSpellAbility | Amount$ StormCount | References$ StormCount"); + card.setSVar("StormCount", "Count$StormCount"); + final Trigger stormTrigger = TriggerHandler.parseTrigger(trigScript.toString(), card, true); + + card.addTrigger(stormTrigger); + } // Storm + final int cascade = card.getKeywordAmount("Cascade"); + for (int i = 0; i < cascade; i++) { + final StringBuilder trigScript = new StringBuilder( + "Mode$ SpellCast | ValidCard$ Card.Self | Execute$ TrigCascade | Secondary$ " + + "True | TriggerDescription$ Cascade - CARDNAME"); + + final String abString = "AB$ DigUntil | Cost$ 0 | Defined$ You | Amount$ 1 | Valid$ " + + "Card.nonLand+cmcLTCascadeX | FoundDestination$ Exile | RevealedDestination$" + + " Exile | References$ CascadeX | ImprintRevealed$ True | RememberFound$ True" + + " | SubAbility$ CascadeCast"; + final String dbCascadeCast = "DB$ Play | Defined$ Remembered | WithoutManaCost$ True | " + + "Optional$ True | SubAbility$ CascadeMoveToLib"; + final String dbMoveToLib = "DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered,Card.IsImprinted" + + " | Origin$ Exile | Destination$ Library | RandomOrder$ True | LibraryPosition$ -1" + + " | SubAbility$ CascadeCleanup"; + card.setSVar("TrigCascade", abString); + card.setSVar("CascadeCast", dbCascadeCast); + card.setSVar("CascadeMoveToLib", dbMoveToLib); + card.setSVar("CascadeX", "Count$CardManaCost"); + card.setSVar("CascadeCleanup", "DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True"); + final Trigger cascadeTrigger = TriggerHandler.parseTrigger(trigScript.toString(), card, true); + + card.addTrigger(cascadeTrigger); + } // Cascade + + if (hasKeyword(card, "Recover") != -1) { + final String recoverCost = card.getKeyword().get(card.getKeywordPosition("Recover")).split(":")[1]; + final String abStr = "AB$ ChangeZone | Cost$ 0 | Defined$ Self" + + " | Origin$ Graveyard | Destination$ Hand | UnlessCost$ " + + recoverCost + " | UnlessPayer$ You | UnlessSwitched$ True" + + " | UnlessResolveSubs$ WhenNotPaid | SubAbility$ RecoverExile"; + card.setSVar("RecoverTrig", abStr); + card.setSVar("RecoverExile", "DB$ ChangeZone | Defined$ Self" + + " | Origin$ Graveyard | Destination$ Exile"); + String trigObject = card.isCreature() ? "Creature.Other+YouOwn" : "Creature.YouOwn"; + String trigArticle = card.isCreature() ? "another" : "a"; + String trigStr = "Mode$ ChangesZone | ValidCard$ " + trigObject + + " | Origin$ Battlefield | Destination$ Graveyard | " + + "TriggerZones$ Graveyard | Execute$ RecoverTrig | " + + "TriggerDescription$ When " + trigArticle + " creature is " + + "put into your graveyard from the battlefield, you " + + "may pay " + recoverCost + ". If you do, return " + + "CARDNAME from your graveyard to your hand. Otherwise," + + " exile CARDNAME. | Secondary$ True"; + final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, true); + card.addTrigger(myTrigger); + } // Recover + + int ripplePos = hasKeyword(card, "Ripple"); + while (ripplePos != -1) { + final int n = ripplePos; + final String parse = card.getKeyword().get(n); + final String[] k = parse.split(":"); + final int num = Integer.parseInt(k[1]); + UUID triggerSvar = UUID.randomUUID(); + + final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | " + + "Execute$ " + triggerSvar + " | Secondary$ True | TriggerDescription$" + + " Ripple " + num + " - CARDNAME | OptionalDecider$ You"; + final String abString = "AB$ Dig | Cost$ 0 | NoMove$ True | DigNum$ " + num + + " | Reveal$ True | RememberRevealed$ True | SubAbility$ DBCastRipple"; + final String dbCast = "DB$ Play | Valid$ Card.IsRemembered+sameName | " + + "ValidZone$ Library | WithoutManaCost$ True | Optional$ True | " + + "Amount$ All | SubAbility$ RippleMoveToBottom"; + + card.setSVar(triggerSvar.toString(), abString); + card.setSVar("DBCastRipple", dbCast); + card.setSVar("RippleMoveToBottom", "DB$ ChangeZoneAll | ChangeType$ " + + "Card.IsRemembered | Origin$ Library | Destination$ Library | " + + "LibraryPosition$ -1 | SubAbility$ RippleCleanup"); + card.setSVar("RippleCleanup", "DB$ Cleanup | ClearRemembered$ True"); + + final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, true); + card.addTrigger(parsedTrigger); + + ripplePos = hasKeyword(card, "Ripple", n + 1); + } // Ripple + } + + + public final static void refreshTotemArmor(Card c) { + boolean hasKw = c.hasKeyword("Totem armor"); + + List res = c.getReplacementEffects(); + for ( int ix = 0; ix < res.size(); ix++ ) { + ReplacementEffect re = res.get(ix); + if( re.getMapParams().containsKey("TotemArmor") ) { + if(hasKw) return; // has re and kw - nothing to do here + res.remove(ix--); + } + } + + if( hasKw ) { + ReplacementEffect re = ReplacementHandler.parseReplacement("Event$ Destroy | ActiveZones$ Battlefield | ValidCard$ Card.EnchantedBy | ReplaceWith$ RegenTA | Secondary$ True | TotemArmor$ True | Description$ Totem armor - " + c, c, true); + c.getSVars().put("RegenTA", "AB$ DealDamage | Cost$ 0 | Defined$ ReplacedCard | Remove$ All | SubAbility$ DestroyMe"); + c.getSVars().put("DestroyMe", "DB$ Destroy | Defined$ Self"); + c.getReplacementEffects().add(re); + } + } + +} // end class CardFactoryUtil diff --git a/forge-game/src/main/java/forge/game/card/CardKeywords.java b/forge-gui/src/main/java/forge/game/card/CardKeywords.java similarity index 93% rename from forge-game/src/main/java/forge/game/card/CardKeywords.java rename to forge-gui/src/main/java/forge/game/card/CardKeywords.java index 8ecd2eacd85..17fb70219ba 100644 --- a/forge-game/src/main/java/forge/game/card/CardKeywords.java +++ b/forge-gui/src/main/java/forge/game/card/CardKeywords.java @@ -1,85 +1,85 @@ -/* - * 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 . - */ -package forge.game.card; - -import java.util.List; -import java.util.ArrayList; - -/** - *

- * Card_Keywords class. - *

- * - * @author Forge - * @version $Id: CardKeywords.java 23786 2013-11-24 06:59:42Z Max mtg $ - */ -public class CardKeywords { - // takes care of individual card types - private List keywords = new ArrayList(); - private List removeKeywords = new ArrayList(); - private boolean removeAllKeywords = false; - - /** - * - * Card_Keywords. - * - * @param keywordList - * an ArrayList - * @param removeKeywordList - * a ArrayList - * @param removeAll - * a boolean - * @param stamp - * a long - */ - CardKeywords(final List keywordList, final List removeKeywordList, final boolean removeAll) { - this.keywords = keywordList; - this.removeKeywords = removeKeywordList; - this.removeAllKeywords = removeAll; - } - - /** - * - * getKeywords. - * - * @return ArrayList - */ - public final List getKeywords() { - return this.keywords; - } - - /** - * - * getRemoveKeywords. - * - * @return ArrayList - */ - public final List getRemoveKeywords() { - return this.removeKeywords; - } - - /** - * - * isRemoveAllKeywords. - * - * @return boolean - */ - public final boolean isRemoveAllKeywords() { - return this.removeAllKeywords; - } -} +/* + * 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 . + */ +package forge.game.card; + +import java.util.List; +import java.util.ArrayList; + +/** + *

+ * Card_Keywords class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class CardKeywords { + // takes care of individual card types + private List keywords = new ArrayList(); + private List removeKeywords = new ArrayList(); + private boolean removeAllKeywords = false; + + /** + * + * Card_Keywords. + * + * @param keywordList + * an ArrayList + * @param removeKeywordList + * a ArrayList + * @param removeAll + * a boolean + * @param stamp + * a long + */ + CardKeywords(final List keywordList, final List removeKeywordList, final boolean removeAll) { + this.keywords = keywordList; + this.removeKeywords = removeKeywordList; + this.removeAllKeywords = removeAll; + } + + /** + * + * getKeywords. + * + * @return ArrayList + */ + public final List getKeywords() { + return this.keywords; + } + + /** + * + * getRemoveKeywords. + * + * @return ArrayList + */ + public final List getRemoveKeywords() { + return this.removeKeywords; + } + + /** + * + * isRemoveAllKeywords. + * + * @return boolean + */ + public final boolean isRemoveAllKeywords() { + return this.removeAllKeywords; + } +} diff --git a/forge-game/src/main/java/forge/game/card/CardLists.java b/forge-gui/src/main/java/forge/game/card/CardLists.java similarity index 96% rename from forge-game/src/main/java/forge/game/card/CardLists.java rename to forge-gui/src/main/java/forge/game/card/CardLists.java index 2ab7833a1e6..ed1e82d3b19 100644 --- a/forge-game/src/main/java/forge/game/card/CardLists.java +++ b/forge-gui/src/main/java/forge/game/card/CardLists.java @@ -1,298 +1,298 @@ -/* - * 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 . - */ -package forge.game.card; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - -import forge.ai.ComputerUtilCard; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; -import forge.util.MyRandom; - - -/** - *

- * CardListUtil class. - *

- * - * @author Forge - * @version $Id: CardLists.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class CardLists { - /** - *

- * filterToughness. - *

- * - * @param in - * a {@link forge.CardList} object. - * @param atLeastToughness - * a int. - * @return a {@link forge.CardList} object. - */ - public static List filterToughness(final List in, final int atLeastToughness) { - return CardLists.filter(in, new Predicate() { - @Override - public boolean apply(Card c) { - return c.getNetDefense() <= atLeastToughness; - } - }); - } - - public static final Comparator ToughnessComparator = new Comparator() { - @Override - public int compare(final Card a, final Card b) { - return a.getNetDefense() - b.getNetDefense(); - } - }; - public static final Comparator PowerComparator = new Comparator() { - @Override - public int compare(final Card a, final Card b) { - return a.getNetCombatDamage() - b.getNetCombatDamage(); - } - }; - public static final Comparator CmcComparatorInv = new Comparator() { - @Override - public int compare(final Card a, final Card b) { - return b.getCMC() - a.getCMC(); - } - }; - - public static final Comparator TextLenComparator = new Comparator() { - @Override - public int compare(final Card a, final Card b) { - final int aLen = a.getText().length(); - final int bLen = b.getText().length(); - return aLen - bLen; - } - }; - - public static final List emptyList = ImmutableList.of(); - - /** - *

- * Sorts a List by "best" using the EvaluateCreature function. - * the best creatures will be first in the list. - *

- * - * @param list - * a {@link forge.CardList} object. - */ - public static void sortByEvaluateCreature(final List list) { - Collections.sort(list, ComputerUtilCard.EvaluateCreatureComparator); - } // sortByEvaluateCreature() - - /** - *

- * Sorts a List from highest converted mana cost to lowest. - *

- * - * @param list - * a {@link forge.CardList} object. - */ - public static void sortByCmcDesc(final List list) { - Collections.sort(list, CmcComparatorInv); - } // sortCMC - - /** - *

- * sortAttackLowFirst. - *

- * - * @param list - * a {@link forge.CardList} object. - */ - public static void sortByPowerAsc(final List list) { - Collections.sort(list, PowerComparator); - } // sortAttackLowFirst() - - // the higher the attack the better - /** - *

- * sortAttack. - *

- * - * @param list - * a {@link forge.CardList} object. - */ - public static void sortByPowerDesc(final List list) { - Collections.sort(list, Collections.reverseOrder(PowerComparator)); - } // sortAttack() - - - /** - * - * Given a List c, return a List that contains a random amount of cards from c. - * - * @param c - * CardList - * @param amount - * int - * @return CardList - */ - public static List getRandomSubList(final List c, final int amount) { - if (c.size() < amount) { - return null; - } - - final List cs = Lists.newArrayList(c); - - final List subList = new ArrayList(); - while (subList.size() < amount) { - CardLists.shuffle(cs); - subList.add(cs.remove(0)); - } - return subList; - } - - /** - * TODO: Write javadoc for this method. - * @param cardList - */ - public static void shuffle(List list) { - // reseed Random each time we want to Shuffle - // MyRandom.random = MyRandom.random; - Collections.shuffle(list, MyRandom.getRandom()); - Collections.shuffle(list, MyRandom.getRandom()); - Collections.shuffle(list, MyRandom.getRandom()); - } - - public static List filterControlledBy(Iterable cardList, Player player) { - return CardLists.filter(cardList, CardPredicates.isController(player)); - } - - public static List filterControlledBy(Iterable cardList, List player) { - return CardLists.filter(cardList, CardPredicates.isControlledByAnyOf(player)); - } - - public static List getValidCards(Iterable cardList, String[] restrictions, Player sourceController, Card source) { - return CardLists.filter(cardList, CardPredicates.restriction(restrictions, sourceController, source)); - } - - public static List getValidCards(Iterable cardList, String restriction, Player sourceController, Card source) { - return CardLists.filter(cardList, CardPredicates.restriction(restriction.split(","), sourceController, source)); - } - - public static List getTargetableCards(Iterable cardList, SpellAbility source) { - return CardLists.filter(cardList, CardPredicates.isTargetableBy(source)); - } - - public static List getKeyword(Iterable cardList, String keyword) { - return CardLists.filter(cardList, CardPredicates.hasKeyword(keyword)); - } - - public static List getNotKeyword(Iterable cardList, String keyword) { - return CardLists.filter(cardList, Predicates.not(CardPredicates.hasKeyword(keyword))); - } - - // cardType is like "Land" or "Goblin", returns a new ArrayList that is a - // subset of current CardList - public static List getNotType(Iterable cardList, String cardType) { - return CardLists.filter(cardList, Predicates.not(CardPredicates.isType(cardType))); - } - - public static List getType(Iterable cardList, String cardType) { - return CardLists.filter(cardList, CardPredicates.isType(cardType)); - } - - /** - * Create a new list of cards by applying a filter to this one. - * - * @param filt - * determines which cards are present in the resulting list - * - * @return a subset of this List whose items meet the filtering - * criteria; may be empty, but never null. - */ - public static List filter(Iterable cardList, Predicate filt) { - return Lists.newArrayList(Iterables.filter(cardList, filt)); - } - - public static List filter(Iterable cardList, Predicate f1, Predicate f2) { - return Lists.newArrayList(Iterables.filter(Iterables.filter(cardList, f1), f2)); - } - - /** - * Given a List cardList, return a List that are tied for having the highest CMC. - * - * @param cardList the Card List to be filtered. - * @return the list of Cards sharing the highest CMC. - */ - public static List getCardsWithHighestCMC(Iterable cardList) { - final List tiedForHighest = new ArrayList(); - int highest = 0; - for (final Card crd : cardList) { - int curCmc = crd.isSplitCard() ? Math.max(crd.getCMC(Card.SplitCMCMode.LeftSplitCMC), crd.getCMC(Card.SplitCMCMode.RightSplitCMC)) : crd.getCMC(); - - if (curCmc > highest) { - highest = curCmc; - tiedForHighest.clear(); - } - if (curCmc >= highest) { - tiedForHighest.add(crd); - } - } - return tiedForHighest; - } - - /** - * Given a List cardList, return a List that are tied for having the lowest CMC. - * - * @param cardList the Card List to be filtered. - * @return the list of Cards sharing the lowest CMC. - */ - public static List getCardsWithLowestCMC(Iterable cardList) { - final List tiedForLowest = new ArrayList(); - int lowest = 25; - for (final Card crd : cardList) { - int curCmc = crd.isSplitCard() ? Math.min(crd.getCMC(Card.SplitCMCMode.LeftSplitCMC), crd.getCMC(Card.SplitCMCMode.RightSplitCMC)) : crd.getCMC(); - - if (curCmc < lowest) { - lowest = curCmc; - tiedForLowest.clear(); - } - if (curCmc <= lowest) { - tiedForLowest.add(crd); - } - } - return tiedForLowest; - } - - /** - * Given a List cardList, return a int TotalPower. - * - * @param cardList the Card List to be filtered. - * @return the total power. - */ - public static int getTotalPower(Iterable cardList) { - int total = 0; - for (final Card crd : cardList) { - total += crd.getNetAttack(); - } - return total; - } -} +/* + * 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 . + */ +package forge.game.card; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import forge.ai.ComputerUtilCard; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.util.MyRandom; + + +/** + *

+ * CardListUtil class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class CardLists { + /** + *

+ * filterToughness. + *

+ * + * @param in + * a {@link forge.CardList} object. + * @param atLeastToughness + * a int. + * @return a {@link forge.CardList} object. + */ + public static List filterToughness(final List in, final int atLeastToughness) { + return CardLists.filter(in, new Predicate() { + @Override + public boolean apply(Card c) { + return c.getNetDefense() <= atLeastToughness; + } + }); + } + + public static final Comparator ToughnessComparator = new Comparator() { + @Override + public int compare(final Card a, final Card b) { + return a.getNetDefense() - b.getNetDefense(); + } + }; + public static final Comparator PowerComparator = new Comparator() { + @Override + public int compare(final Card a, final Card b) { + return a.getNetCombatDamage() - b.getNetCombatDamage(); + } + }; + public static final Comparator CmcComparatorInv = new Comparator() { + @Override + public int compare(final Card a, final Card b) { + return b.getCMC() - a.getCMC(); + } + }; + + public static final Comparator TextLenComparator = new Comparator() { + @Override + public int compare(final Card a, final Card b) { + final int aLen = a.getText().length(); + final int bLen = b.getText().length(); + return aLen - bLen; + } + }; + + public static final List emptyList = ImmutableList.of(); + + /** + *

+ * Sorts a List by "best" using the EvaluateCreature function. + * the best creatures will be first in the list. + *

+ * + * @param list + * a {@link forge.CardList} object. + */ + public static void sortByEvaluateCreature(final List list) { + Collections.sort(list, ComputerUtilCard.EvaluateCreatureComparator); + } // sortByEvaluateCreature() + + /** + *

+ * Sorts a List from highest converted mana cost to lowest. + *

+ * + * @param list + * a {@link forge.CardList} object. + */ + public static void sortByCmcDesc(final List list) { + Collections.sort(list, CmcComparatorInv); + } // sortCMC + + /** + *

+ * sortAttackLowFirst. + *

+ * + * @param list + * a {@link forge.CardList} object. + */ + public static void sortByPowerAsc(final List list) { + Collections.sort(list, PowerComparator); + } // sortAttackLowFirst() + + // the higher the attack the better + /** + *

+ * sortAttack. + *

+ * + * @param list + * a {@link forge.CardList} object. + */ + public static void sortByPowerDesc(final List list) { + Collections.sort(list, Collections.reverseOrder(PowerComparator)); + } // sortAttack() + + + /** + * + * Given a List c, return a List that contains a random amount of cards from c. + * + * @param c + * CardList + * @param amount + * int + * @return CardList + */ + public static List getRandomSubList(final List c, final int amount) { + if (c.size() < amount) { + return null; + } + + final List cs = Lists.newArrayList(c); + + final List subList = new ArrayList(); + while (subList.size() < amount) { + CardLists.shuffle(cs); + subList.add(cs.remove(0)); + } + return subList; + } + + /** + * TODO: Write javadoc for this method. + * @param cardList + */ + public static void shuffle(List list) { + // reseed Random each time we want to Shuffle + // MyRandom.random = MyRandom.random; + Collections.shuffle(list, MyRandom.getRandom()); + Collections.shuffle(list, MyRandom.getRandom()); + Collections.shuffle(list, MyRandom.getRandom()); + } + + public static List filterControlledBy(Iterable cardList, Player player) { + return CardLists.filter(cardList, CardPredicates.isController(player)); + } + + public static List filterControlledBy(Iterable cardList, List player) { + return CardLists.filter(cardList, CardPredicates.isControlledByAnyOf(player)); + } + + public static List getValidCards(Iterable cardList, String[] restrictions, Player sourceController, Card source) { + return CardLists.filter(cardList, CardPredicates.restriction(restrictions, sourceController, source)); + } + + public static List getValidCards(Iterable cardList, String restriction, Player sourceController, Card source) { + return CardLists.filter(cardList, CardPredicates.restriction(restriction.split(","), sourceController, source)); + } + + public static List getTargetableCards(Iterable cardList, SpellAbility source) { + return CardLists.filter(cardList, CardPredicates.isTargetableBy(source)); + } + + public static List getKeyword(Iterable cardList, String keyword) { + return CardLists.filter(cardList, CardPredicates.hasKeyword(keyword)); + } + + public static List getNotKeyword(Iterable cardList, String keyword) { + return CardLists.filter(cardList, Predicates.not(CardPredicates.hasKeyword(keyword))); + } + + // cardType is like "Land" or "Goblin", returns a new ArrayList that is a + // subset of current CardList + public static List getNotType(Iterable cardList, String cardType) { + return CardLists.filter(cardList, Predicates.not(CardPredicates.isType(cardType))); + } + + public static List getType(Iterable cardList, String cardType) { + return CardLists.filter(cardList, CardPredicates.isType(cardType)); + } + + /** + * Create a new list of cards by applying a filter to this one. + * + * @param filt + * determines which cards are present in the resulting list + * + * @return a subset of this List whose items meet the filtering + * criteria; may be empty, but never null. + */ + public static List filter(Iterable cardList, Predicate filt) { + return Lists.newArrayList(Iterables.filter(cardList, filt)); + } + + public static List filter(Iterable cardList, Predicate f1, Predicate f2) { + return Lists.newArrayList(Iterables.filter(Iterables.filter(cardList, f1), f2)); + } + + /** + * Given a List cardList, return a List that are tied for having the highest CMC. + * + * @param cardList the Card List to be filtered. + * @return the list of Cards sharing the highest CMC. + */ + public static List getCardsWithHighestCMC(Iterable cardList) { + final List tiedForHighest = new ArrayList(); + int highest = 0; + for (final Card crd : cardList) { + int curCmc = crd.isSplitCard() ? Math.max(crd.getCMC(Card.SplitCMCMode.LeftSplitCMC), crd.getCMC(Card.SplitCMCMode.RightSplitCMC)) : crd.getCMC(); + + if (curCmc > highest) { + highest = curCmc; + tiedForHighest.clear(); + } + if (curCmc >= highest) { + tiedForHighest.add(crd); + } + } + return tiedForHighest; + } + + /** + * Given a List cardList, return a List that are tied for having the lowest CMC. + * + * @param cardList the Card List to be filtered. + * @return the list of Cards sharing the lowest CMC. + */ + public static List getCardsWithLowestCMC(Iterable cardList) { + final List tiedForLowest = new ArrayList(); + int lowest = 25; + for (final Card crd : cardList) { + int curCmc = crd.isSplitCard() ? Math.min(crd.getCMC(Card.SplitCMCMode.LeftSplitCMC), crd.getCMC(Card.SplitCMCMode.RightSplitCMC)) : crd.getCMC(); + + if (curCmc < lowest) { + lowest = curCmc; + tiedForLowest.clear(); + } + if (curCmc <= lowest) { + tiedForLowest.add(crd); + } + } + return tiedForLowest; + } + + /** + * Given a List cardList, return a int TotalPower. + * + * @param cardList the Card List to be filtered. + * @return the total power. + */ + public static int getTotalPower(Iterable cardList) { + int total = 0; + for (final Card crd : cardList) { + total += crd.getNetAttack(); + } + return total; + } +} diff --git a/forge-game/src/main/java/forge/game/card/CardPowerToughness.java b/forge-gui/src/main/java/forge/game/card/CardPowerToughness.java similarity index 92% rename from forge-game/src/main/java/forge/game/card/CardPowerToughness.java rename to forge-gui/src/main/java/forge/game/card/CardPowerToughness.java index 3c4d34ef6d6..090727c43c4 100644 --- a/forge-game/src/main/java/forge/game/card/CardPowerToughness.java +++ b/forge-gui/src/main/java/forge/game/card/CardPowerToughness.java @@ -1,99 +1,99 @@ -/* - * 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 . - */ -package forge.game.card; - -/** - *

- * CardPowerToughness class. - *

- * - * @author Forge - * @version $Id: CardPowerToughness.java 23786 2013-11-24 06:59:42Z Max mtg $ - */ -public class CardPowerToughness { - - private final int power; - private final int toughness; - private long timeStamp = 0; - - /** - *

- * getTimestamp. - *

- * - * @return a long. - */ - public final long getTimestamp() { - return this.timeStamp; - } - - /** - *

- * Constructor for Card_PT. - *

- * - * @param newPower - * a int. - * @param newToughness - * a int. - * @param stamp - * a long. - */ - CardPowerToughness(final int newPower, final int newToughness, final long stamp) { - this.power = newPower; - this.toughness = newToughness; - this.timeStamp = stamp; - } - - /** - * - * Get Power. - * - * @return int - */ - public final int getPower() { - return this.power; - } - - /** - * - * Get Toughness. - * - * @return int - */ - public final int getToughness() { - return this.toughness; - } - - /** - *

- * equals. - *

- * - * @param newPower - * a int. - * @param newToughness - * a int. - * @param stamp - * a long. - * @return a boolean. - */ - public final boolean equals(final int newPower, final int newToughness, final long stamp) { - return (this.timeStamp == stamp) && (this.power == newPower) && (this.toughness == newToughness); - } -} +/* + * 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 . + */ +package forge.game.card; + +/** + *

+ * CardPowerToughness class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class CardPowerToughness { + + private final int power; + private final int toughness; + private long timeStamp = 0; + + /** + *

+ * getTimestamp. + *

+ * + * @return a long. + */ + public final long getTimestamp() { + return this.timeStamp; + } + + /** + *

+ * Constructor for Card_PT. + *

+ * + * @param newPower + * a int. + * @param newToughness + * a int. + * @param stamp + * a long. + */ + CardPowerToughness(final int newPower, final int newToughness, final long stamp) { + this.power = newPower; + this.toughness = newToughness; + this.timeStamp = stamp; + } + + /** + * + * Get Power. + * + * @return int + */ + public final int getPower() { + return this.power; + } + + /** + * + * Get Toughness. + * + * @return int + */ + public final int getToughness() { + return this.toughness; + } + + /** + *

+ * equals. + *

+ * + * @param newPower + * a int. + * @param newToughness + * a int. + * @param stamp + * a long. + * @return a boolean. + */ + public final boolean equals(final int newPower, final int newToughness, final long stamp) { + return (this.timeStamp == stamp) && (this.power == newPower) && (this.toughness == newToughness); + } +} diff --git a/forge-game/src/main/java/forge/game/card/CardPredicates.java b/forge-gui/src/main/java/forge/game/card/CardPredicates.java similarity index 96% rename from forge-game/src/main/java/forge/game/card/CardPredicates.java rename to forge-gui/src/main/java/forge/game/card/CardPredicates.java index 068d8f5e50b..e745e13e3dd 100644 --- a/forge-game/src/main/java/forge/game/card/CardPredicates.java +++ b/forge-gui/src/main/java/forge/game/card/CardPredicates.java @@ -1,384 +1,384 @@ -/* - * 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 . - */ -package forge.game.card; - -import java.util.List; - -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; - -import forge.game.combat.CombatUtil; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; -import forge.util.PredicateString; - - -/** - *

- * Predicate interface. - *

- * - * @author Forge - * @version $Id: CardPredicates.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public final class CardPredicates { - - public static final Predicate isController(final Player p) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return c.getController().equals(p); - } - }; - } - public static final Predicate isControlledByAnyOf(final List pList) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return pList.contains((Player) c.getController()); - } - }; - } - public static final Predicate isOwner(final Player p) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return c.getOwner().equals(p); - } - }; - } - - public static final Predicate isType(final String cardType) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return c.isType(cardType); - } - }; - } - - public static final Predicate hasKeyword(final String keyword) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return c.hasKeyword(keyword); - } - }; - } - - public static final Predicate containsKeyword(final String keyword) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return Iterables.any(c.getKeyword(), PredicateString.contains(keyword)); - } - }; - } - - public static final Predicate isTargetableBy(final SpellAbility source) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return source.canTarget(c); - } - }; - } - - public static final Predicate nameEquals(final String name) { - return new Predicate() { - @Override - public boolean apply(Card c) { - return c.getName().equals(name); - } - }; - } - - public static final Predicate sharesColorWith(final Card color) { - return new Predicate() { - @Override - public boolean apply(Card c) { - return c.sharesColorWith(color); - } - }; - } - - public static final Predicate possibleBlockers(final Card attacker) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return c.isCreature() && CombatUtil.canBlock(attacker, c); - } - }; - } - - public static final Predicate possibleBlockerForAtLeastOne(final Iterable attackers) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return c.isCreature() && CombatUtil.canBlockAtLeastOne(c, attackers); - } - }; - } - - public static final Predicate isProtectedFrom(final Card source) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return c.hasProtectionFrom(source); - } - }; - } - - public static final Predicate restriction(final String[] restrictions, final Player sourceController, final Card source) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return (c != null) && c.isValid(restrictions, sourceController, source); - } - }; - } - - public static final Predicate canBeSacrificedBy(final SpellAbility sa) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return c.canBeSacrificedBy(sa); - } - }; - }; - - public static final Predicate isColor(final byte color) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return CardUtil.getColors(c).hasAnyColor(color); - } - }; - } // getColor() - - public static final Predicate hasCMC(final int cmc) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return c.getCMC() == cmc; - } - }; - } - - public static class Presets { - - /** - * a Predicate to get all cards that are tapped. - */ - public static final Predicate TAPPED = new Predicate() { - @Override - public boolean apply(Card c) { - return c.isTapped(); - } - }; - - public static final Predicate FACE_DOWN = new Predicate() { - @Override - public boolean apply(Card c) { - return c.isFaceDown(); - } - }; - - /** - * a Predicate to get all cards that are untapped. - */ - public static final Predicate UNTAPPED = new Predicate() { - @Override - public boolean apply(Card c) { - return c.isUntapped(); - } - }; - /** - * a Predicate to get all creatures. - */ - public static final Predicate CREATURES = new Predicate() { - @Override - public boolean apply(Card c) { - return c.isCreature(); - } - }; - - /** - * a Predicate to get all enchantments. - */ - public static final Predicate ENCHANTMENTS = new Predicate() { - @Override - public boolean apply(Card c) { - return c.isEnchantment(); - } - }; - /** - * a Predicate to get all equipment. - */ - public static final Predicate EQUIPMENT = new Predicate() { - @Override - public boolean apply(Card c) { - return c.isEquipment(); - } - }; - /** - * a Predicate to get all fortification. - */ - public static final Predicate Fortification = new Predicate() { - @Override - public boolean apply(Card c) { - return c.isFortification(); - } - }; - /** - * a Predicate to get all unenchanted cards in a list. - */ - public static final Predicate UNENCHANTED = new Predicate() { - @Override - public boolean apply(Card c) { - return !c.isEnchanted(); - } - }; - /** - * a Predicate to get all enchanted cards in a list. - */ - public static final Predicate ENCHANTED = new Predicate() { - @Override - public boolean apply(Card c) { - return c.isEnchanted(); - } - }; - /** - * a Predicate to get all nontoken cards. - */ - public static final Predicate NON_TOKEN = new Predicate() { - @Override - public boolean apply(Card c) { - return !c.isToken(); - } - }; - /** - * a Predicate to get all token cards. - */ - public static final Predicate TOKEN = new Predicate() { - @Override - public boolean apply(Card c) { - return c.isToken(); - } - }; - /** - * a Predicate to get all basicLands. - */ - public static final Predicate BASIC_LANDS = new Predicate() { - @Override - public boolean apply(Card c) { - // the isBasicLand() check here may be sufficient... - return c.isLand() && c.isBasicLand(); - } - }; - /** - * a Predicate to get all artifacts. - */ - public static final Predicate ARTIFACTS = new Predicate() { - @Override - public boolean apply(Card c) { - return c.isArtifact(); - } - }; - /** - * a Predicate to get all nonartifacts. - */ - public static final Predicate NON_ARTIFACTS = new Predicate() { - @Override - public boolean apply(Card c) { - return !c.isArtifact(); - } - }; - /** - * a Predicate to get all lands. - */ - public static final Predicate LANDS = new Predicate() { - @Override - public boolean apply(Card c) { - return c.isLand(); - } - }; - - public static final Predicate hasFirstStrike = new Predicate() { - @Override - public boolean apply(Card c) { - return c.isCreature() && (c.hasFirstStrike() || c.hasDoubleStrike()); - } - }; - public static final Predicate hasSecondStrike = new Predicate() { - @Override - public boolean apply(Card c) { - return c.isCreature() && (!c.hasFirstStrike() || c.hasDoubleStrike()); - } - }; - public static final Predicate SNOW_LANDS = new Predicate() { - @Override - public boolean apply(final Card c) { - return c.isLand() && c.isSnow(); - } - }; - public static final Predicate PLANEWALKERS = new Predicate() { - @Override - public boolean apply(final Card c) { - return c.isPlaneswalker(); - } - }; - public static final Predicate CAN_BE_DESTROYED = new Predicate() { - @Override - public boolean apply(final Card c) { - return c.canBeDestroyed(); - } - }; - } - - public static class Accessors { - public static final Function fnGetDefense = new Function() { - @Override - public Integer apply(Card a) { - return a.getNetDefense(); - } - }; - - public static final Function fnGetNetAttack = new Function() { - @Override - public Integer apply(Card a) { - return a.getNetAttack(); - } - }; - - public static final Function fnGetAttack = new Function() { - @Override - public Integer apply(Card a) { - return a.getNetCombatDamage(); - } - }; - - public static final Function fnGetCmc = new Function() { - @Override - public Integer apply(Card a) { - return a.getCMC(); - } - }; - } -} +/* + * 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 . + */ +package forge.game.card; + +import java.util.List; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; + +import forge.game.combat.CombatUtil; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.util.PredicateString; + + +/** + *

+ * Predicate interface. + *

+ * + * @author Forge + * @version $Id$ + */ +public final class CardPredicates { + + public static final Predicate isController(final Player p) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return c.getController().equals(p); + } + }; + } + public static final Predicate isControlledByAnyOf(final List pList) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return pList.contains((Player) c.getController()); + } + }; + } + public static final Predicate isOwner(final Player p) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return c.getOwner().equals(p); + } + }; + } + + public static final Predicate isType(final String cardType) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return c.isType(cardType); + } + }; + } + + public static final Predicate hasKeyword(final String keyword) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return c.hasKeyword(keyword); + } + }; + } + + public static final Predicate containsKeyword(final String keyword) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return Iterables.any(c.getKeyword(), PredicateString.contains(keyword)); + } + }; + } + + public static final Predicate isTargetableBy(final SpellAbility source) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return source.canTarget(c); + } + }; + } + + public static final Predicate nameEquals(final String name) { + return new Predicate() { + @Override + public boolean apply(Card c) { + return c.getName().equals(name); + } + }; + } + + public static final Predicate sharesColorWith(final Card color) { + return new Predicate() { + @Override + public boolean apply(Card c) { + return c.sharesColorWith(color); + } + }; + } + + public static final Predicate possibleBlockers(final Card attacker) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return c.isCreature() && CombatUtil.canBlock(attacker, c); + } + }; + } + + public static final Predicate possibleBlockerForAtLeastOne(final Iterable attackers) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return c.isCreature() && CombatUtil.canBlockAtLeastOne(c, attackers); + } + }; + } + + public static final Predicate isProtectedFrom(final Card source) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return c.hasProtectionFrom(source); + } + }; + } + + public static final Predicate restriction(final String[] restrictions, final Player sourceController, final Card source) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return (c != null) && c.isValid(restrictions, sourceController, source); + } + }; + } + + public static final Predicate canBeSacrificedBy(final SpellAbility sa) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return c.canBeSacrificedBy(sa); + } + }; + }; + + public static final Predicate isColor(final byte color) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return CardUtil.getColors(c).hasAnyColor(color); + } + }; + } // getColor() + + public static final Predicate hasCMC(final int cmc) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return c.getCMC() == cmc; + } + }; + } + + public static class Presets { + + /** + * a Predicate to get all cards that are tapped. + */ + public static final Predicate TAPPED = new Predicate() { + @Override + public boolean apply(Card c) { + return c.isTapped(); + } + }; + + public static final Predicate FACE_DOWN = new Predicate() { + @Override + public boolean apply(Card c) { + return c.isFaceDown(); + } + }; + + /** + * a Predicate to get all cards that are untapped. + */ + public static final Predicate UNTAPPED = new Predicate() { + @Override + public boolean apply(Card c) { + return c.isUntapped(); + } + }; + /** + * a Predicate to get all creatures. + */ + public static final Predicate CREATURES = new Predicate() { + @Override + public boolean apply(Card c) { + return c.isCreature(); + } + }; + + /** + * a Predicate to get all enchantments. + */ + public static final Predicate ENCHANTMENTS = new Predicate() { + @Override + public boolean apply(Card c) { + return c.isEnchantment(); + } + }; + /** + * a Predicate to get all equipment. + */ + public static final Predicate EQUIPMENT = new Predicate() { + @Override + public boolean apply(Card c) { + return c.isEquipment(); + } + }; + /** + * a Predicate to get all fortification. + */ + public static final Predicate Fortification = new Predicate() { + @Override + public boolean apply(Card c) { + return c.isFortification(); + } + }; + /** + * a Predicate to get all unenchanted cards in a list. + */ + public static final Predicate UNENCHANTED = new Predicate() { + @Override + public boolean apply(Card c) { + return !c.isEnchanted(); + } + }; + /** + * a Predicate to get all enchanted cards in a list. + */ + public static final Predicate ENCHANTED = new Predicate() { + @Override + public boolean apply(Card c) { + return c.isEnchanted(); + } + }; + /** + * a Predicate to get all nontoken cards. + */ + public static final Predicate NON_TOKEN = new Predicate() { + @Override + public boolean apply(Card c) { + return !c.isToken(); + } + }; + /** + * a Predicate to get all token cards. + */ + public static final Predicate TOKEN = new Predicate() { + @Override + public boolean apply(Card c) { + return c.isToken(); + } + }; + /** + * a Predicate to get all basicLands. + */ + public static final Predicate BASIC_LANDS = new Predicate() { + @Override + public boolean apply(Card c) { + // the isBasicLand() check here may be sufficient... + return c.isLand() && c.isBasicLand(); + } + }; + /** + * a Predicate to get all artifacts. + */ + public static final Predicate ARTIFACTS = new Predicate() { + @Override + public boolean apply(Card c) { + return c.isArtifact(); + } + }; + /** + * a Predicate to get all nonartifacts. + */ + public static final Predicate NON_ARTIFACTS = new Predicate() { + @Override + public boolean apply(Card c) { + return !c.isArtifact(); + } + }; + /** + * a Predicate to get all lands. + */ + public static final Predicate LANDS = new Predicate() { + @Override + public boolean apply(Card c) { + return c.isLand(); + } + }; + + public static final Predicate hasFirstStrike = new Predicate() { + @Override + public boolean apply(Card c) { + return c.isCreature() && (c.hasFirstStrike() || c.hasDoubleStrike()); + } + }; + public static final Predicate hasSecondStrike = new Predicate() { + @Override + public boolean apply(Card c) { + return c.isCreature() && (!c.hasFirstStrike() || c.hasDoubleStrike()); + } + }; + public static final Predicate SNOW_LANDS = new Predicate() { + @Override + public boolean apply(final Card c) { + return c.isLand() && c.isSnow(); + } + }; + public static final Predicate PLANEWALKERS = new Predicate() { + @Override + public boolean apply(final Card c) { + return c.isPlaneswalker(); + } + }; + public static final Predicate CAN_BE_DESTROYED = new Predicate() { + @Override + public boolean apply(final Card c) { + return c.canBeDestroyed(); + } + }; + } + + public static class Accessors { + public static final Function fnGetDefense = new Function() { + @Override + public Integer apply(Card a) { + return a.getNetDefense(); + } + }; + + public static final Function fnGetNetAttack = new Function() { + @Override + public Integer apply(Card a) { + return a.getNetAttack(); + } + }; + + public static final Function fnGetAttack = new Function() { + @Override + public Integer apply(Card a) { + return a.getNetCombatDamage(); + } + }; + + public static final Function fnGetCmc = new Function() { + @Override + public Integer apply(Card a) { + return a.getCMC(); + } + }; + } +} diff --git a/forge-game/src/main/java/forge/game/card/CardShields.java b/forge-gui/src/main/java/forge/game/card/CardShields.java similarity index 100% rename from forge-game/src/main/java/forge/game/card/CardShields.java rename to forge-gui/src/main/java/forge/game/card/CardShields.java diff --git a/forge-game/src/main/java/forge/game/card/CardType.java b/forge-gui/src/main/java/forge/game/card/CardType.java similarity index 94% rename from forge-game/src/main/java/forge/game/card/CardType.java rename to forge-gui/src/main/java/forge/game/card/CardType.java index be2de010f53..c4862ee348a 100644 --- a/forge-game/src/main/java/forge/game/card/CardType.java +++ b/forge-gui/src/main/java/forge/game/card/CardType.java @@ -1,126 +1,126 @@ -/* - * 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 . - */ -package forge.game.card; - -import java.util.ArrayList; - -/** - *

- * Card_Color class. - *

- * - * @author Forge - * @version $Id: CardType.java 23786 2013-11-24 06:59:42Z Max mtg $ - */ -public class CardType { - // takes care of individual card types - private ArrayList type = new ArrayList(); - private ArrayList removeType = new ArrayList(); - private final boolean removeSuperTypes; - private final boolean removeCardTypes; - private final boolean removeSubTypes; - private final boolean removeCreatureTypes; - - /** - * Instantiates a new card_ type. - * - * @param types - * an ArrayList - * @param removeTypes - * an ArrayList - * @param removeSuperType - * a boolean - * @param removeCardType - * a boolean - * @param removeSubType - * a boolean - * @param removeCreatureType - * a boolean - * @param stamp - * a long - */ - CardType(final ArrayList types, final ArrayList removeTypes, final boolean removeSuperType, - final boolean removeCardType, final boolean removeSubType, final boolean removeCreatureType) { - this.type = types; - this.removeType = removeTypes; - this.removeSuperTypes = removeSuperType; - this.removeCardTypes = removeCardType; - this.removeSubTypes = removeSubType; - this.removeCreatureTypes = removeCreatureType; - } - - /** - * - * getType. - * - * @return type - */ - public final ArrayList getType() { - return this.type; - } - - /** - * - * getRemoveType. - * - * @return removeType - */ - public final ArrayList getRemoveType() { - return this.removeType; - } - - /** - * - * isRemoveSuperTypes. - * - * @return removeSuperTypes - */ - public final boolean isRemoveSuperTypes() { - return this.removeSuperTypes; - } - - /** - * - * isRemoveCardTypes. - * - * @return removeCardTypes - */ - public final boolean isRemoveCardTypes() { - return this.removeCardTypes; - } - - /** - * - * isRemoveSubTypes. - * - * @return removeSubTypes - */ - public final boolean isRemoveSubTypes() { - return this.removeSubTypes; - } - - /** - * - * isRemoveCreatureTypes. - * - * @return removeCreatureTypes - */ - public final boolean isRemoveCreatureTypes() { - return this.removeCreatureTypes; - } -} +/* + * 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 . + */ +package forge.game.card; + +import java.util.ArrayList; + +/** + *

+ * Card_Color class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class CardType { + // takes care of individual card types + private ArrayList type = new ArrayList(); + private ArrayList removeType = new ArrayList(); + private final boolean removeSuperTypes; + private final boolean removeCardTypes; + private final boolean removeSubTypes; + private final boolean removeCreatureTypes; + + /** + * Instantiates a new card_ type. + * + * @param types + * an ArrayList + * @param removeTypes + * an ArrayList + * @param removeSuperType + * a boolean + * @param removeCardType + * a boolean + * @param removeSubType + * a boolean + * @param removeCreatureType + * a boolean + * @param stamp + * a long + */ + CardType(final ArrayList types, final ArrayList removeTypes, final boolean removeSuperType, + final boolean removeCardType, final boolean removeSubType, final boolean removeCreatureType) { + this.type = types; + this.removeType = removeTypes; + this.removeSuperTypes = removeSuperType; + this.removeCardTypes = removeCardType; + this.removeSubTypes = removeSubType; + this.removeCreatureTypes = removeCreatureType; + } + + /** + * + * getType. + * + * @return type + */ + public final ArrayList getType() { + return this.type; + } + + /** + * + * getRemoveType. + * + * @return removeType + */ + public final ArrayList getRemoveType() { + return this.removeType; + } + + /** + * + * isRemoveSuperTypes. + * + * @return removeSuperTypes + */ + public final boolean isRemoveSuperTypes() { + return this.removeSuperTypes; + } + + /** + * + * isRemoveCardTypes. + * + * @return removeCardTypes + */ + public final boolean isRemoveCardTypes() { + return this.removeCardTypes; + } + + /** + * + * isRemoveSubTypes. + * + * @return removeSubTypes + */ + public final boolean isRemoveSubTypes() { + return this.removeSubTypes; + } + + /** + * + * isRemoveCreatureTypes. + * + * @return removeCreatureTypes + */ + public final boolean isRemoveCreatureTypes() { + return this.removeCreatureTypes; + } +} diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-gui/src/main/java/forge/game/card/CardUtil.java similarity index 87% rename from forge-game/src/main/java/forge/game/card/CardUtil.java rename to forge-gui/src/main/java/forge/game/card/CardUtil.java index efc72d2250e..7740d40652b 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-gui/src/main/java/forge/game/card/CardUtil.java @@ -1,356 +1,404 @@ -/* - * 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 . - */ -package forge.game.card; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import forge.Constant; -import forge.ImageCacheBridge; -import forge.card.CardCharacteristicName; -import forge.card.ColorSet; -import forge.card.MagicColor; -import forge.game.Game; -import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; -import forge.game.player.Player; -import forge.game.spellability.AbilityManaPart; -import forge.game.spellability.AbilitySub; -import forge.game.spellability.SpellAbility; -import forge.game.zone.ZoneType; - -public final class CardUtil { - // disable instantiation - private CardUtil() { } - - public static ColorSet getColors(final Card c) { - return c.determineColor(); - } - - public static boolean isStackingKeyword(final String keyword) { - String kw = new String(keyword); - if (kw.startsWith("HIDDEN")) { - kw = kw.substring(7); - } - - return !kw.startsWith("Protection") && !kw.startsWith("CantBeBlockedBy") - && !Constant.Keywords.NON_STACKING_LIST.contains(kw); - } - - public static String getShortColorsString(final Iterable colors) { - StringBuilder colorDesc = new StringBuilder(); - for (final String col : colors) { - colorDesc.append(MagicColor.toShortString(col) + " "); - } - return colorDesc.toString(); - } - - /** - * getThisTurnEntered. - * - * @param to zone going to - * @param from zone coming from - * @param valid a isValid expression - * @param src a Card object - * @return a List that matches the given criteria - */ - public static List getThisTurnEntered(final ZoneType to, final ZoneType from, final String valid, final Card src) { - List res = new ArrayList(); - final Game game = src.getGame(); - if (to != ZoneType.Stack) { - for (Player p : game.getPlayers()) { - res.addAll(p.getZone(to).getCardsAddedThisTurn(from)); - } - } else { - res.addAll(game.getStackZone().getCardsAddedThisTurn(from)); - } - - res = CardLists.getValidCards(res, valid, src.getController(), src); - - return res; - } - - /** - * getLastTurnEntered. - * - * @param to zone going to - * @param from zone coming from - * @param valid a isValid expression - * @param src a Card object - * @return a List that matches the given criteria - */ - public static List getLastTurnEntered(final ZoneType to, final ZoneType from, final String valid, final Card src) { - List res = new ArrayList(); - final Game game = src.getGame(); - if (to != ZoneType.Stack) { - for (Player p : game.getPlayers()) { - res.addAll(p.getZone(to).getCardsAddedLastTurn(from)); - } - } else { - res.addAll(game.getStackZone().getCardsAddedLastTurn(from)); - } - - res = CardLists.getValidCards(res, valid, src.getController(), src); - - return res; - } - - public static List getThisTurnCast(final String valid, final Card src) { - List res = new ArrayList(); - final Game game = src.getGame(); - res.addAll(game.getStack().getCardsCastThisTurn()); - - res = CardLists.getValidCards(res, valid, src.getController(), src); - - return res; - } - - public static List getLastTurnCast(final String valid, final Card src) { - List res = new ArrayList(); - final Game game = src.getGame(); - res.addAll(game.getStack().getCardsCastLastTurn()); - - res = CardLists.getValidCards(res, valid, src.getController(), src); - - return res; - } - - /** - * @param in a Card to copy. - * @return a copy of C with LastKnownInfo stuff retained. - */ - public static Card getLKICopy(final Card in) { - - final Card newCopy = new Card(in.getUniqueNumber()); - newCopy.setCurSetCode(in.getCurSetCode()); - newCopy.setOwner(in.getOwner()); - newCopy.setController(in.getController(), 0); - newCopy.getCharacteristics().copyFrom(in.getState(in.getCurState())); - if (in.isCloned()) { - newCopy.addAlternateState(CardCharacteristicName.Cloner); - } - newCopy.setType(new ArrayList(in.getType())); - newCopy.setToken(in.isToken()); - newCopy.setTriggers(in.getTriggers(), false); - for (SpellAbility sa : in.getManaAbility()) { - newCopy.addSpellAbility(sa); - sa.setSourceCard(in); - } - - // lock in the current P/T without boni from counters - newCopy.setBaseAttack(in.getCurrentPower() + in.getTempAttackBoost() + in.getSemiPermanentAttackBoost()); - newCopy.setBaseDefense(in.getCurrentToughness() + in.getTempDefenseBoost() + in.getSemiPermanentDefenseBoost()); - - newCopy.setCounters(in.getCounters()); - newCopy.setExtrinsicKeyword(in.getExtrinsicKeyword()); - - // Determine the color for LKI copy, not just getColor - ArrayList currentColor = new ArrayList(); - currentColor.add(new CardColor(in.determineColor().getColor())); - newCopy.setColor(currentColor); - newCopy.setReceivedDamageFromThisTurn(in.getReceivedDamageFromThisTurn()); - newCopy.getDamageHistory().setCreatureGotBlockedThisTurn(in.getDamageHistory().getCreatureGotBlockedThisTurn()); - newCopy.setEnchanting(in.getEnchanting()); - newCopy.setEnchantedBy(new ArrayList (in.getEnchantedBy())); - newCopy.setEquipping(new ArrayList (in.getEquipping())); - newCopy.setEquippedBy(new ArrayList (in.getEquippedBy())); - newCopy.setFortifying(new ArrayList (in.getFortifying())); - newCopy.setFortifiedBy(new ArrayList (in.getFortifiedBy())); - newCopy.setClones(in.getClones()); - newCopy.setHaunting(in.getHaunting()); - for (final Card haunter : in.getHauntedBy()) { - newCopy.addHauntedBy(haunter); - } - for (final Object o : in.getRemembered()) { - newCopy.addRemembered(o); - } - for (final Card o : in.getImprinted()) { - newCopy.addImprinted(o); - } - - return newCopy; - } - - public static List getRadiance(final Card source, final Card origin, final String[] valid) { - final List res = new ArrayList(); - - final Game game = source.getGame(); - ColorSet cs = CardUtil.getColors(origin); - for (byte color : MagicColor.WUBRG) { - if(!cs.hasAnyColor(color)) - continue; - - for(final Card c : game.getColoredCardsInPlay(MagicColor.toLongString(color))) { - if (!res.contains(c) && c.isValid(valid, source.getController(), source) && !c.equals(origin)) { - res.add(c); - } - } - } - return res; - } - - public static CardCharacteristics getFaceDownCharacteristic() { - final ArrayList types = new ArrayList(); - types.add("Creature"); - - final CardCharacteristics ret = new CardCharacteristics(); - ret.setBaseAttack(2); - ret.setBaseDefense(2); - - ret.setName(""); - ret.setType(types); - - ret.setImageKey(ImageCacheBridge.instance.getMorphImage()); - - return ret; - } - - // a nice entry point with minimum parameters - public static Set getReflectableManaColors(final SpellAbility sa) { - return getReflectableManaColors(sa, sa, new HashSet(), new ArrayList()); - } - - private static Set getReflectableManaColors(final SpellAbility abMana, final SpellAbility sa, - Set colors, final List parents) { - // Here's the problem with reflectable Mana. If more than one is out, - // they need to Reflect each other, - // so we basically need to have a recursive list that send the parents - // so we don't infinite recurse. - final Card card = abMana.getSourceCard(); - - if (abMana.getApi() != ApiType.ManaReflected) { - return colors; - } - - if (!parents.contains(card)) { - parents.add(card); - } - - final String colorOrType = sa.getParam("ColorOrType"); - // currently Color or Type, Type is colors + colorless - final String validCard = sa.getParam("Valid"); - final String reflectProperty = sa.getParam("ReflectProperty"); - // Produce (Reflecting Pool) or Is (Meteor Crater) - - int maxChoices = 5; // Color is the default colorOrType - if (colorOrType.equals("Type")) { - maxChoices++; - } - - List cards = null; - - // Reuse AF_Defined in a slightly different way - if (validCard.startsWith("Defined.")) { - cards = AbilityUtils.getDefinedCards(card, validCard.replace("Defined.", ""), abMana); - } else { - final Game game = sa.getActivatingPlayer().getGame(); - cards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validCard, abMana.getActivatingPlayer(), card); - } - - // remove anything cards that is already in parents - for (final Card p : parents) { - if (cards.contains(p)) { - cards.remove(p); - } - } - - if ((cards.size() == 0) && !reflectProperty.equals("Produced")) { - return colors; - } - - if (reflectProperty.equals("Is")) { // Meteor Crater - for (final Card card1 : cards) { - // For each card, go through all the colors and if the card is that color, add - for (final String col : MagicColor.Constant.ONLY_COLORS) { - if (card1.isOfColor(col)) { - colors.add(col); - if (colors.size() == maxChoices) { - break; - } - } - } - } - } else if (reflectProperty.equals("Produced")) { - // Why is this name so similar to the one below? - final String producedColors = abMana instanceof AbilitySub ? (String) abMana.getRootAbility().getTriggeringObject("Produced") : (String) abMana.getTriggeringObject("Produced"); - for (final String col : MagicColor.Constant.ONLY_COLORS) { - final String s = MagicColor.toShortString(col); - if (producedColors.contains(s)) { - colors.add(col); - } - } - if (maxChoices == 6 && producedColors.contains("1")) { - colors.add(MagicColor.Constant.COLORLESS); - } - } else if (reflectProperty.equals("Produce")) { - final List abilities = new ArrayList(); - for (final Card c : cards) { - abilities.addAll(c.getManaAbility()); - } - - final List reflectAbilities = new ArrayList(); - - for (final SpellAbility ab : abilities) { - if (maxChoices == colors.size()) { - break; - } - - if (ab.getApi() == ApiType.ManaReflected) { - if (!parents.contains(ab.getSourceCard())) { - // Recursion! Set Activator to controller for appropriate valid comparison - ab.setActivatingPlayer(ab.getSourceCard().getController()); - reflectAbilities.add(ab); - parents.add(ab.getSourceCard()); - } - continue; - } - colors = canProduce(maxChoices, ab.getManaPart(), colors); - if (!parents.contains(ab.getSourceCard())) { - parents.add(ab.getSourceCard()); - } - } - - for (final SpellAbility ab : reflectAbilities) { - if (maxChoices == colors.size()) { - break; - } - - colors = CardUtil.getReflectableManaColors(ab, sa, colors, parents); - } - } - return colors; - } - - public static Set canProduce(final int maxChoices, final AbilityManaPart ab, - final Set colors) { - for (final String col : MagicColor.Constant.ONLY_COLORS) { - final String s = MagicColor.toShortString(col); - if (ab.canProduce(s)) { - colors.add(col); - } - } - - if (maxChoices == 6 && ab.canProduce("1")) { - colors.add(MagicColor.Constant.COLORLESS); - } - - return colors; - } -} +/* + * 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 . + */ +package forge.game.card; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import forge.Constant; +import forge.Singletons; +import forge.card.CardCharacteristicName; +import forge.card.ColorSet; +import forge.card.MagicColor; +import forge.game.Game; +import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; +import forge.game.player.Player; +import forge.game.spellability.AbilityManaPart; +import forge.game.spellability.AbilitySub; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; +import forge.properties.NewConstants; + +public final class CardUtil { + // disable instantiation + private CardUtil() { } + + public static ColorSet getColors(final Card c) { + return c.determineColor(); + } + + public static boolean isStackingKeyword(final String keyword) { + String kw = new String(keyword); + if (kw.startsWith("HIDDEN")) { + kw = kw.substring(7); + } + + return !kw.startsWith("Protection") && !kw.startsWith("CantBeBlockedBy") + && !Constant.Keywords.NON_STACKING_LIST.contains(kw); + } + + public static String getShortColorsString(final Iterable colors) { + StringBuilder colorDesc = new StringBuilder(); + for (final String col : colors) { + colorDesc.append(MagicColor.toShortString(col) + " "); + } + return colorDesc.toString(); + } + + /** + * getThisTurnEntered. + * + * @param to zone going to + * @param from zone coming from + * @param valid a isValid expression + * @param src a Card object + * @return a List that matches the given criteria + */ + public static List getThisTurnEntered(final ZoneType to, final ZoneType from, final String valid, final Card src) { + List res = new ArrayList(); + final Game game = src.getGame(); + if (to != ZoneType.Stack) { + for (Player p : game.getPlayers()) { + res.addAll(p.getZone(to).getCardsAddedThisTurn(from)); + } + } else { + res.addAll(game.getStackZone().getCardsAddedThisTurn(from)); + } + + res = CardLists.getValidCards(res, valid, src.getController(), src); + + return res; + } + + /** + * getLastTurnEntered. + * + * @param to zone going to + * @param from zone coming from + * @param valid a isValid expression + * @param src a Card object + * @return a List that matches the given criteria + */ + public static List getLastTurnEntered(final ZoneType to, final ZoneType from, final String valid, final Card src) { + List res = new ArrayList(); + final Game game = src.getGame(); + if (to != ZoneType.Stack) { + for (Player p : game.getPlayers()) { + res.addAll(p.getZone(to).getCardsAddedLastTurn(from)); + } + } else { + res.addAll(game.getStackZone().getCardsAddedLastTurn(from)); + } + + res = CardLists.getValidCards(res, valid, src.getController(), src); + + return res; + } + + public static List getThisTurnCast(final String valid, final Card src) { + List res = new ArrayList(); + final Game game = src.getGame(); + res.addAll(game.getStack().getCardsCastThisTurn()); + + res = CardLists.getValidCards(res, valid, src.getController(), src); + + return res; + } + + public static List getLastTurnCast(final String valid, final Card src) { + List res = new ArrayList(); + final Game game = src.getGame(); + res.addAll(game.getStack().getCardsCastLastTurn()); + + res = CardLists.getValidCards(res, valid, src.getController(), src); + + return res; + } + + /** + * @param in a Card to copy. + * @return a copy of C with LastKnownInfo stuff retained. + */ + public static Card getLKICopy(final Card in) { + + final Card newCopy = new Card(in.getUniqueNumber()); + newCopy.setCurSetCode(in.getCurSetCode()); + newCopy.setOwner(in.getOwner()); + newCopy.setController(in.getController(), 0); + newCopy.getCharacteristics().copyFrom(in.getState(in.getCurState())); + if (in.isCloned()) { + newCopy.addAlternateState(CardCharacteristicName.Cloner); + } + newCopy.setType(new ArrayList(in.getType())); + newCopy.setToken(in.isToken()); + newCopy.setTriggers(in.getTriggers(), false); + for (SpellAbility sa : in.getManaAbility()) { + newCopy.addSpellAbility(sa); + sa.setSourceCard(in); + } + + // lock in the current P/T without boni from counters + newCopy.setBaseAttack(in.getCurrentPower() + in.getTempAttackBoost() + in.getSemiPermanentAttackBoost()); + newCopy.setBaseDefense(in.getCurrentToughness() + in.getTempDefenseBoost() + in.getSemiPermanentDefenseBoost()); + + newCopy.setCounters(in.getCounters()); + newCopy.setExtrinsicKeyword(in.getExtrinsicKeyword()); + + // Determine the color for LKI copy, not just getColor + ArrayList currentColor = new ArrayList(); + currentColor.add(new CardColor(in.determineColor().getColor())); + newCopy.setColor(currentColor); + newCopy.setReceivedDamageFromThisTurn(in.getReceivedDamageFromThisTurn()); + newCopy.getDamageHistory().setCreatureGotBlockedThisTurn(in.getDamageHistory().getCreatureGotBlockedThisTurn()); + newCopy.setEnchanting(in.getEnchanting()); + newCopy.setEnchantedBy(new ArrayList (in.getEnchantedBy())); + newCopy.setEquipping(new ArrayList (in.getEquipping())); + newCopy.setEquippedBy(new ArrayList (in.getEquippedBy())); + newCopy.setFortifying(new ArrayList (in.getFortifying())); + newCopy.setFortifiedBy(new ArrayList (in.getFortifiedBy())); + newCopy.setClones(in.getClones()); + newCopy.setHaunting(in.getHaunting()); + for (final Card haunter : in.getHauntedBy()) { + newCopy.addHauntedBy(haunter); + } + for (final Object o : in.getRemembered()) { + newCopy.addRemembered(o); + } + for (final Card o : in.getImprinted()) { + newCopy.addImprinted(o); + } + + return newCopy; + } + + public static List getRadiance(final Card source, final Card origin, final String[] valid) { + final List res = new ArrayList(); + + final Game game = source.getGame(); + ColorSet cs = CardUtil.getColors(origin); + for (byte color : MagicColor.WUBRG) { + if(!cs.hasAnyColor(color)) + continue; + + for(final Card c : game.getColoredCardsInPlay(MagicColor.toLongString(color))) { + if (!res.contains(c) && c.isValid(valid, source.getController(), source) && !c.equals(origin)) { + res.add(c); + } + } + } + return res; + } + + public static CardCharacteristics getFaceDownCharacteristic() { + final ArrayList types = new ArrayList(); + types.add("Creature"); + + final CardCharacteristics ret = new CardCharacteristics(); + ret.setBaseAttack(2); + ret.setBaseDefense(2); + + ret.setName(""); + ret.setType(types); + + ret.setImageKey(NewConstants.CACHE_MORPH_IMAGE_FILE); + + return ret; + } + + // a nice entry point with minimum parameters + public static Set getReflectableManaColors(final SpellAbility sa) { + return getReflectableManaColors(sa, sa, new HashSet(), new ArrayList()); + } + + private static Set getReflectableManaColors(final SpellAbility abMana, final SpellAbility sa, + Set colors, final List parents) { + // Here's the problem with reflectable Mana. If more than one is out, + // they need to Reflect each other, + // so we basically need to have a recursive list that send the parents + // so we don't infinite recurse. + final Card card = abMana.getSourceCard(); + + if (abMana.getApi() != ApiType.ManaReflected) { + return colors; + } + + if (!parents.contains(card)) { + parents.add(card); + } + + final String colorOrType = sa.getParam("ColorOrType"); + // currently Color or Type, Type is colors + colorless + final String validCard = sa.getParam("Valid"); + final String reflectProperty = sa.getParam("ReflectProperty"); + // Produce (Reflecting Pool) or Is (Meteor Crater) + + int maxChoices = 5; // Color is the default colorOrType + if (colorOrType.equals("Type")) { + maxChoices++; + } + + List cards = null; + + // Reuse AF_Defined in a slightly different way + if (validCard.startsWith("Defined.")) { + cards = AbilityUtils.getDefinedCards(card, validCard.replace("Defined.", ""), abMana); + } else { + final Game game = sa.getActivatingPlayer().getGame(); + cards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validCard, abMana.getActivatingPlayer(), card); + } + + // remove anything cards that is already in parents + for (final Card p : parents) { + if (cards.contains(p)) { + cards.remove(p); + } + } + + if ((cards.size() == 0) && !reflectProperty.equals("Produced")) { + return colors; + } + + if (reflectProperty.equals("Is")) { // Meteor Crater + for (final Card card1 : cards) { + // For each card, go through all the colors and if the card is that color, add + for (final String col : MagicColor.Constant.ONLY_COLORS) { + if (card1.isOfColor(col)) { + colors.add(col); + if (colors.size() == maxChoices) { + break; + } + } + } + } + } else if (reflectProperty.equals("Produced")) { + // Why is this name so similar to the one below? + final String producedColors = abMana instanceof AbilitySub ? (String) abMana.getRootAbility().getTriggeringObject("Produced") : (String) abMana.getTriggeringObject("Produced"); + for (final String col : MagicColor.Constant.ONLY_COLORS) { + final String s = MagicColor.toShortString(col); + if (producedColors.contains(s)) { + colors.add(col); + } + } + if (maxChoices == 6 && producedColors.contains("1")) { + colors.add(MagicColor.Constant.COLORLESS); + } + } else if (reflectProperty.equals("Produce")) { + final List abilities = new ArrayList(); + for (final Card c : cards) { + abilities.addAll(c.getManaAbility()); + } + + final List reflectAbilities = new ArrayList(); + + for (final SpellAbility ab : abilities) { + if (maxChoices == colors.size()) { + break; + } + + if (ab.getApi() == ApiType.ManaReflected) { + if (!parents.contains(ab.getSourceCard())) { + // Recursion! Set Activator to controller for appropriate valid comparison + ab.setActivatingPlayer(ab.getSourceCard().getController()); + reflectAbilities.add(ab); + parents.add(ab.getSourceCard()); + } + continue; + } + colors = canProduce(maxChoices, ab.getManaPart(), colors); + if (!parents.contains(ab.getSourceCard())) { + parents.add(ab.getSourceCard()); + } + } + + for (final SpellAbility ab : reflectAbilities) { + if (maxChoices == colors.size()) { + break; + } + + colors = CardUtil.getReflectableManaColors(ab, sa, colors, parents); + } + } + return colors; + } + + public static Set canProduce(final int maxChoices, final AbilityManaPart ab, + final Set colors) { + for (final String col : MagicColor.Constant.ONLY_COLORS) { + final String s = MagicColor.toShortString(col); + if (ab.canProduce(s)) { + colors.add(col); + } + } + + if (maxChoices == 6 && ab.canProduce("1")) { + colors.add(MagicColor.Constant.COLORLESS); + } + + return colors; + } + + /** + * Card characteristic state machine. + *

+ * Given a card and a state in terms of {@code CardCharacteristicName} this + * will determine whether there is a valid alternate {@code CardCharacteristicName} + * state for that card. + * + * @param card the {@code Card} + * @param currentState not necessarily {@code card.getCurState()} + * @return the alternate {@code CardCharacteristicName} state or default if not applicable + */ + public static CardCharacteristicName getAlternateState(final Card card, CardCharacteristicName currentState) { + + // Default. Most cards will only ever have an "Original" state represented by a single image. + CardCharacteristicName alternateState = CardCharacteristicName.Original; + + if (card.isDoubleFaced()) { + if (currentState == CardCharacteristicName.Original) { + alternateState = CardCharacteristicName.Transformed; + } + + } else if (card.isFlipCard()) { + if (currentState == CardCharacteristicName.Original) { + alternateState = CardCharacteristicName.Flipped; + } + + } else if (card.isFaceDown()) { + if (currentState == CardCharacteristicName.Original) { + alternateState = CardCharacteristicName.FaceDown; + } else if (isAuthorizedToViewFaceDownCard(card)) { + alternateState = CardCharacteristicName.Original; + } else { + alternateState = currentState; + } + } + + return alternateState; + } + + /** + * Prevents player from identifying opponent's face-down card. + */ + public static boolean isAuthorizedToViewFaceDownCard(Card card) { + return Singletons.getControl().mayShowCard(card); + } + +} diff --git a/forge-game/src/main/java/forge/game/card/CounterType.java b/forge-gui/src/main/java/forge/game/card/CounterType.java similarity index 92% rename from forge-game/src/main/java/forge/game/card/CounterType.java rename to forge-gui/src/main/java/forge/game/card/CounterType.java index 2bf83e47ded..c962d86d7cc 100644 --- a/forge-game/src/main/java/forge/game/card/CounterType.java +++ b/forge-gui/src/main/java/forge/game/card/CounterType.java @@ -1,312 +1,312 @@ -/* - * 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 . - */ - -package forge.game.card; - -/** - * The class Counters. - * - * @author Clemens Koza - * @version V0.0 17.02.2010 - */ -public enum CounterType { - - M1M1("-1/-1"), - P1P1("+1/+1"), - LOYALTY, - - AGE(), - - AIM(), - - ARROW(), - - ARROWHEAD(), - - AWAKENING(), - - BLAZE(), - - BLOOD(), - - BOUNTY(), - - BRIBERY(), - - CARRION(), - - CHARGE(), - - CORPSE(), - - CREDIT(), - - CUBE(), - - CURRENCY(), - - DEATH(), - - DELAY(), - - DEPLETION(), - - DESPAIR(), - - DEVOTION(), - - DIVINITY(), - - DREAM(), - - DOOM(), - - ECHO(), - - ELIXIR(), - - ENERGY(), - - EON(), - - EYEBALL(), - - FADE(), - - FATE(), - - FEATHER(), - - FILIBUSTER(), - - FLAME(), - - FLOOD(), - - FUNGUS(), - - FUSE(), - - GLYPH(), - - GOLD(), - - GROWTH(), - - HATCHLING(), - - HEALING(), - - HOOFPRINT(), - - HOURGLASS(), - - HUNGER(), - - ICE(), - - INFECTION(), - - INTERVENTION(), - - JAVELIN(), - - KI(), - - LEVEL("Level"), - - LORE(), - - LUCK(), - - M0M1("-0/-1"), - - M0M2("-0/-2"), - - M1M0("-1/-0"), - - M2M1("-2/-1"), - - M2M2("-2/-2"), - - MAGNET(), - - MANA(), - - MANIFESTATION(), - - MANNEQUIN(), - - MATRIX(), - - MINE(), - - MINING(), - - MIRE(), - - MUSIC(), - - MUSTER(), - - NET(), - - OMEN(), - - ORE(), - - PAGE(), - - PAIN(), - - PARALYZATION(), - - PETAL(), - - PETRIFICATION(), - - PIN(), - - PLAGUE(), - - PRESSURE(), - - PHYLACTERY, - - POLYP(), - - PUPA(), - - P0P1("+0/+1"), - - P0P2("+0/+2"), - - P1P0("+1/+0"), - - P1P2("+1/+2"), - - P2P0("+2/+0"), - - P2P2("+2/+2"), - - QUEST(), - - RUST(), - - SCREAM(), - - SCROLL(), - - SHELL(), - - SHIELD(), - - SHRED(), - - SLEEP(), - - SLEIGHT(), - - SLIME(), - - SOOT(), - - SPORE(), - - STORAGE(), - - STRIFE(), - - STUDY(), - - THEFT(), - - TIDE(), - - TIME(), - - TOWER("tower"), - - TRAINING(), - - TRAP(), - - TREASURE(), - - VELOCITY(), - - VERSE(), - - VITALITY(), - - WAGE(), - - WINCH(), - - WIND(), - - WISH(); - - private String name; - - /** - *

- * Constructor for Counters. - *

- */ - private CounterType() { - this.name = this.name().substring(0, 1).toUpperCase() + this.name().substring(1).toLowerCase(); - } - - /** - *

- * Constructor for Counters. - *

- * - * @param name - * a {@link java.lang.String} object. - */ - private CounterType(final String nameIn) { - this.name = nameIn; - } - - /** - *

- * Getter for the field name. - *

- * - * @return a {@link java.lang.String} object. - */ - public String getName() { - return this.name; - } - - /** - *

- * getType. - *

- * - * @param name - * a {@link java.lang.String} object. - * @return a {@link forge.game.card.CounterType} object. - */ - public static CounterType getType(final String name) { - final String replacedName = name.replace("/", "").replaceAll("\\+", "p").replaceAll("\\-", "m").toUpperCase(); - return Enum.valueOf(CounterType.class, replacedName); - } -} +/* + * 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 . + */ + +package forge.game.card; + +/** + * The class Counters. + * + * @author Clemens Koza + * @version V0.0 17.02.2010 + */ +public enum CounterType { + + M1M1("-1/-1"), + P1P1("+1/+1"), + LOYALTY, + + AGE(), + + AIM(), + + ARROW(), + + ARROWHEAD(), + + AWAKENING(), + + BLAZE(), + + BLOOD(), + + BOUNTY(), + + BRIBERY(), + + CARRION(), + + CHARGE(), + + CORPSE(), + + CREDIT(), + + CUBE(), + + CURRENCY(), + + DEATH(), + + DELAY(), + + DEPLETION(), + + DESPAIR(), + + DEVOTION(), + + DIVINITY(), + + DREAM(), + + DOOM(), + + ECHO(), + + ELIXIR(), + + ENERGY(), + + EON(), + + EYEBALL(), + + FADE(), + + FATE(), + + FEATHER(), + + FILIBUSTER(), + + FLAME(), + + FLOOD(), + + FUNGUS(), + + FUSE(), + + GLYPH(), + + GOLD(), + + GROWTH(), + + HATCHLING(), + + HEALING(), + + HOOFPRINT(), + + HOURGLASS(), + + HUNGER(), + + ICE(), + + INFECTION(), + + INTERVENTION(), + + JAVELIN(), + + KI(), + + LEVEL("Level"), + + LORE(), + + LUCK(), + + M0M1("-0/-1"), + + M0M2("-0/-2"), + + M1M0("-1/-0"), + + M2M1("-2/-1"), + + M2M2("-2/-2"), + + MAGNET(), + + MANA(), + + MANIFESTATION(), + + MANNEQUIN(), + + MATRIX(), + + MINE(), + + MINING(), + + MIRE(), + + MUSIC(), + + MUSTER(), + + NET(), + + OMEN(), + + ORE(), + + PAGE(), + + PAIN(), + + PARALYZATION(), + + PETAL(), + + PETRIFICATION(), + + PIN(), + + PLAGUE(), + + PRESSURE(), + + PHYLACTERY, + + POLYP(), + + PUPA(), + + P0P1("+0/+1"), + + P0P2("+0/+2"), + + P1P0("+1/+0"), + + P1P2("+1/+2"), + + P2P0("+2/+0"), + + P2P2("+2/+2"), + + QUEST(), + + RUST(), + + SCREAM(), + + SCROLL(), + + SHELL(), + + SHIELD(), + + SHRED(), + + SLEEP(), + + SLEIGHT(), + + SLIME(), + + SOOT(), + + SPORE(), + + STORAGE(), + + STRIFE(), + + STUDY(), + + THEFT(), + + TIDE(), + + TIME(), + + TOWER("tower"), + + TRAINING(), + + TRAP(), + + TREASURE(), + + VELOCITY(), + + VERSE(), + + VITALITY(), + + WAGE(), + + WINCH(), + + WIND(), + + WISH(); + + private String name; + + /** + *

+ * Constructor for Counters. + *

+ */ + private CounterType() { + this.name = this.name().substring(0, 1).toUpperCase() + this.name().substring(1).toLowerCase(); + } + + /** + *

+ * Constructor for Counters. + *

+ * + * @param name + * a {@link java.lang.String} object. + */ + private CounterType(final String nameIn) { + this.name = nameIn; + } + + /** + *

+ * Getter for the field name. + *

+ * + * @return a {@link java.lang.String} object. + */ + public String getName() { + return this.name; + } + + /** + *

+ * getType. + *

+ * + * @param name + * a {@link java.lang.String} object. + * @return a {@link forge.game.card.CounterType} object. + */ + public static CounterType getType(final String name) { + final String replacedName = name.replace("/", "").replaceAll("\\+", "p").replaceAll("\\-", "m").toUpperCase(); + return Enum.valueOf(CounterType.class, replacedName); + } +} diff --git a/forge-gui/src/main/java/forge/game/card/package-info.java b/forge-gui/src/main/java/forge/game/card/package-info.java new file mode 100644 index 00000000000..2e4122db81e --- /dev/null +++ b/forge-gui/src/main/java/forge/game/card/package-info.java @@ -0,0 +1,8 @@ +/** + * + */ +/** + * @author Max + * + */ +package forge.game.card; \ No newline at end of file diff --git a/forge-game/src/main/java/forge/game/combat/AttackingBand.java b/forge-gui/src/main/java/forge/game/combat/AttackingBand.java similarity index 100% rename from forge-game/src/main/java/forge/game/combat/AttackingBand.java rename to forge-gui/src/main/java/forge/game/combat/AttackingBand.java diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-gui/src/main/java/forge/game/combat/Combat.java similarity index 97% rename from forge-game/src/main/java/forge/game/combat/Combat.java rename to forge-gui/src/main/java/forge/game/combat/Combat.java index d2a6b85fe32..f874491411b 100644 --- a/forge-game/src/main/java/forge/game/combat/Combat.java +++ b/forge-gui/src/main/java/forge/game/combat/Combat.java @@ -1,715 +1,715 @@ -/* - * 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 . - */ -package forge.game.combat; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.apache.commons.lang3.tuple.Pair; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Lists; -import com.google.common.collect.Multimap; -import com.google.common.collect.Multimaps; - -import forge.game.GameEntity; -import forge.game.card.Card; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; -import forge.game.player.Player; -import forge.game.trigger.TriggerType; -import forge.game.zone.ZoneType; - -/** - *

- * Combat class. - *

- * - * @author Forge - * @version $Id: Combat.java 24125 2014-01-02 06:51:30Z Agetian $ - */ -public class Combat { - private final Player playerWhoAttacks; - // Defenders, as they are attacked by hostile forces - private final List attackableEntries = new ArrayList(); - - // Keyed by attackable defender (player or planeswalker) - private final Multimap attackedByBands = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); - private final Multimap blockedBands = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); - - private final HashMap defendingDamageMap = new HashMap(); - - private Map> attackersOrderedForDamageAssignment = new HashMap>(); - private Map> blockersOrderedForDamageAssignment = new HashMap>(); - private Map lkiCache = new HashMap(); - - // List holds creatures who have dealt 1st strike damage to disallow them deal damage on regular basis (unless they have double-strike KW) - private List combatantsThatDealtFirstStrikeDamage = Lists.newArrayList(); - - - public Combat(Player attacker) { - playerWhoAttacks = attacker; - - // Create keys for all possible attack targets - for (Player defender : playerWhoAttacks.getOpponents()) { - this.attackableEntries.add(defender); - List planeswalkers = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS); - for (final Card pw : planeswalkers) { - this.attackableEntries.add(pw); - } - } - } - - public final Player getAttackingPlayer() { - return this.playerWhoAttacks; - } - - public final List getDefenders() { - return Lists.newArrayList(attackableEntries); - } - - public final List getDefendersControlledBy(Player who) { - List res = Lists.newArrayList(); - for(GameEntity ge : attackableEntries) { - // if defender is the player himself or his cards - if (ge == who || ge instanceof Card && ((Card) ge).getController() == who) - res.add(ge); - } - return res; - } - - public final List getDefendingPlaneswalkers() { - final List pwDefending = new ArrayList(); - for (final GameEntity o : attackableEntries) { - if (o instanceof Card) { - pwDefending.add((Card) o); - } - } - return pwDefending; - } - - public final List getAttackingBandsOf(GameEntity defender) { - return Lists.newArrayList(attackedByBands.get(defender)); - } - - public final List getAttackersOf(GameEntity defender) { - List result = new ArrayList(); - for(AttackingBand v : attackedByBands.get(defender)) { - result.addAll(v.getAttackers()); - } - return result; - } - - public final void addAttacker(final Card c, GameEntity defender) { - addAttacker(c, defender, null); - } - - public final void addAttacker(final Card c, GameEntity defender, AttackingBand band) { - Collection attackersOfDefender = attackedByBands.get(defender); - if (attackersOfDefender == null) { - System.out.println("Trying to add Attacker " + c + " to missing defender " + defender); - return; - } - - if (band == null || !attackersOfDefender.contains(band)) { - band = new AttackingBand(c, defender); - attackersOfDefender.add(band); - } else { - band.addAttacker(c); - } - } - - public final GameEntity getDefenderByAttacker(final Card c) { - return getDefenderByAttacker(getBandOfAttacker(c)); - } - - public final GameEntity getDefenderByAttacker(final AttackingBand c) { - for(Entry e : attackedByBands.entries()) { - if ( e.getValue() == c ) - return e.getKey(); - } - return null; - } - - public final Player getDefenderPlayerByAttacker(final Card c) { - GameEntity defender = getDefenderByAttacker(c); - - // System.out.println(c.toString() + " attacks " + defender.toString()); - if (defender instanceof Player) { - return (Player) defender; - } - - // maybe attack on a controlled planeswalker? - if (defender instanceof Card) { - return ((Card) defender).getController(); - } - return null; - } - - // takes LKI into consideration, should use it at all times (though a single iteration over multimap seems faster) - public final AttackingBand getBandOfAttacker(final Card c) { - for(AttackingBand ab : attackedByBands.values()) { - if ( ab.contains(c) ) - return ab; - } - CombatLki lki = lkiCache.get(c); - return lki == null ? null : lki.getFirstBand(); - } - - public final List getAttackingBands() { - return Lists.newArrayList(attackedByBands.values()); - } - - public final boolean isAttacking(final Card c) { - AttackingBand ab = getBandOfAttacker(c); - return ab != null; - } - - public boolean isAttacking(Card card, GameEntity defender) { - AttackingBand ab = getBandOfAttacker(card); - for(Entry ee : attackedByBands.entries()) - if ( ee.getValue() == ab ) - return ee.getKey() == defender; - return false; - } - - public final List getAttackers() { - List result = Lists.newArrayList(); - for(AttackingBand ab : attackedByBands.values()) - result.addAll(ab.getAttackers()); - return result; - } - - public final List getBlockers(final Card card) { - // If requesting the ordered blocking list pass true, directly. - AttackingBand band = getBandOfAttacker(card); - Collection blockers = blockedBands.get(band); - return blockers == null ? Lists.newArrayList() : Lists.newArrayList(blockers); - } - - public final boolean isBlocked(final Card attacker) { - AttackingBand band = getBandOfAttacker(attacker); - return band == null ? false : Boolean.TRUE.equals(band.isBlocked()); - - } - - // Some cards in Alpha may UNBLOCK an attacker, so second parameter is not always-true - public final void setBlocked(final Card attacker, boolean value) { - getBandOfAttacker(attacker).setBlocked(value); // called by Curtain of Light, Dazzling Beauty, Trap Runner - } - - public final void addBlocker(final Card attacker, final Card blocker) { - AttackingBand band = getBandOfAttacker(attacker); - blockedBands.put(band, blocker); - } - - // remove blocked from specific attacker - public final void removeBlockAssignment(final Card attacker, final Card blocker) { - AttackingBand band = getBandOfAttacker(attacker); - Collection cc = blockedBands.get(band); - if( cc != null) - cc.remove(blocker); - } - - // remove blocker from everywhere - public final void undoBlockingAssignment(final Card blocker) { - List toRemove = Lists.newArrayList(blocker); - blockedBands.values().removeAll(toRemove); - } - - public final List getAllBlockers() { - List result = new ArrayList(); - for(Card blocker : blockedBands.values()) { - if(!result.contains(blocker)) - result.add(blocker); - } - return result; - } - - public final List getBlockers(final AttackingBand band) { - Collection blockers = blockedBands.get(band); - return blockers == null ? Lists.newArrayList() : Lists.newArrayList(blockers); - } - - - public final List getAttackersBlockedBy(final Card blocker) { - List blocked = new ArrayList(); - for(Entry s : blockedBands.entries()) { - if (s.getValue().equals(blocker)) - blocked.addAll(s.getKey().getAttackers()); - } - return blocked; - } - - public final List getAttackingBandsBlockedBy(Card blocker) { - List bands = Lists.newArrayList(); - for( Entry kv : blockedBands.entries()) { - if (kv.getValue().equals(blocker)) - bands.add(kv.getKey()); - } - return bands; - } - - public Player getDefendingPlayerRelatedTo(final Card source) { - Card attacker = source; - if (source.isAura()) { - attacker = source.getEnchantingCard(); - } else if (source.isEquipment()) { - attacker = source.getEquippingCard(); - } else if (source.isFortification()) { - attacker = source.getFortifyingCard(); - } - - // return the corresponding defender - return getDefenderPlayerByAttacker(attacker); - } - - /** If there are multiple blockers, the Attacker declares the Assignment Order */ - public void orderBlockersForDamageAssignment() { // this method performs controller's role - - List>> blockersNeedManualOrdering = new ArrayList<>(); - - for(AttackingBand band : attackedByBands.values()) - { - if (band.isEmpty()) continue; - - Collection blockers = blockedBands.get(band); - if ( blockers == null || blockers.isEmpty() ) - continue; - - for(Card attacker : band.getAttackers()) { - if ( blockers.size() <= 1 ) - blockersOrderedForDamageAssignment.put(attacker, Lists.newArrayList(blockers)); - else // process it a bit later - blockersNeedManualOrdering.add(Pair.of(attacker, (List)blockers)); // we know there's a list - } - } - - // brought this out of iteration on bands to avoid concurrency problems - for(Pair> pair : blockersNeedManualOrdering){ - // Damage Ordering needs to take cards like Melee into account, is that happening? - List orderedBlockers = playerWhoAttacks.getController().orderBlockers(pair.getLeft(), pair.getRight()); // we know there's a list - blockersOrderedForDamageAssignment.put(pair.getLeft(), orderedBlockers); - } - } - - public void orderAttackersForDamageAssignment() { // this method performs controller's role - // If there are multiple blockers, the Attacker declares the Assignment Order - for (final Card blocker : getAllBlockers()) { - orderAttackersForDamageAssignment(blocker); - } - } - - public void orderAttackersForDamageAssignment(Card blocker) { // this method performs controller's role - List attackers = getAttackersBlockedBy(blocker); - // They need a reverse map here: Blocker => List - - Player blockerCtrl = blocker.getController(); - List orderedAttacker = attackers.size() <= 1 ? attackers : blockerCtrl.getController().orderAttackers(blocker, attackers); - - // Damage Ordering needs to take cards like Melee into account, is that happening? - attackersOrderedForDamageAssignment.put(blocker, orderedAttacker); - } - - // removes references to this attacker from all indices and orders - private void unregisterAttacker(final Card c, AttackingBand ab) { - blockersOrderedForDamageAssignment.remove(c); - - Collection blockers = blockedBands.get(ab); - if ( blockers != null ) { - for (Card b : blockers) { - // Clear removed attacker from assignment order - if (this.attackersOrderedForDamageAssignment.containsKey(b)) { - this.attackersOrderedForDamageAssignment.get(b).remove(c); - } - } - } - return; - } - - // removes references to this defender from all indices and orders - private void unregisterDefender(final Card c, AttackingBand bandBeingBlocked) { - this.attackersOrderedForDamageAssignment.remove(c); - for(Card atk : bandBeingBlocked.getAttackers()) { - if (this.blockersOrderedForDamageAssignment.containsKey(atk)) { - this.blockersOrderedForDamageAssignment.get(atk).remove(c); - } - } - } - - // remove a combatant whose side is unknown - public final void removeFromCombat(final Card c) { - AttackingBand ab = getBandOfAttacker(c); - if (ab != null) { - unregisterAttacker(c, ab); - ab.removeAttacker(c); - } - - // if not found in attackers, look for this card in blockers - for(Entry be : blockedBands.entries()) { - if(be.getValue().equals(c)) { - unregisterDefender(c, be.getKey()); - } - } - - // remove card from map - while(blockedBands.values().remove(c)); - - } // removeFromCombat() - - public final void removeAbsentCombatants() { - // iterate all attackers and remove them - for(Entry ee : attackedByBands.entries()) { - List atk = ee.getValue().getAttackers(); - for(int i = atk.size() - 1; i >= 0; i--) { // might remove items from collection, so no iterators - Card c = atk.get(i); - if ( !c.isInPlay() ) { - unregisterAttacker(c, ee.getValue()); - } - } - } - - Collection toRemove = Lists.newArrayList(); - for(Entry be : blockedBands.entries()) { - if ( !be.getValue().isInPlay() ) { - unregisterDefender(be.getValue(), be.getKey()); - toRemove.add(be.getValue()); - } - } - blockedBands.values().removeAll(toRemove); - - } // verifyCreaturesInPlay() - - - // Call this method right after turn-based action of declare blockers has been performed - public final void fireTriggersForUnblockedAttackers() { - for(AttackingBand ab : attackedByBands.values()) { - Collection blockers = blockedBands.get(ab); - boolean isBlocked = blockers != null && !blockers.isEmpty(); - ab.setBlocked(isBlocked); - - if (!isBlocked ) - for (Card attacker : ab.getAttackers()) { - // Run Unblocked Trigger - final HashMap runParams = new HashMap(); - runParams.put("Attacker", attacker); - runParams.put("Defender",this.getDefenderByAttacker(attacker)); - attacker.getGame().getTriggerHandler().runTrigger(TriggerType.AttackerUnblocked, runParams, false); - } - } - } - - private final boolean assignBlockersDamage(boolean firstStrikeDamage) { - // Assign damage by Blockers - final List blockers = this.getAllBlockers(); - boolean assignedDamage = false; - - for (final Card blocker : blockers) { - if (!dealDamageThisPhase(blocker, firstStrikeDamage)) { - continue; - } - - if (firstStrikeDamage) { - this.combatantsThatDealtFirstStrikeDamage.add(blocker); - } - - List attackers = this.attackersOrderedForDamageAssignment.get(blocker); - - final int damage = blocker.getNetCombatDamage(); - - if (!attackers.isEmpty()) { - Player attackingPlayer = this.getAttackingPlayer(); - Player assigningPlayer = blocker.getController(); - - if (AttackingBand.isValidBand(attackers, true)) - assigningPlayer = attackingPlayer; - - assignedDamage = true; - Map map = assigningPlayer.getController().assignCombatDamage(blocker, attackers, damage, null, assigningPlayer != blocker.getController()); - for (Entry dt : map.entrySet()) { - dt.getKey().addAssignedDamage(dt.getValue(), blocker); - } - } - } - - return assignedDamage; - } - - private final boolean assignAttackersDamage(boolean firstStrikeDamage) { - // Assign damage by Attackers - this.defendingDamageMap.clear(); // this should really happen in deal damage - List orderedBlockers = null; - final List attackers = this.getAttackers(); - boolean assignedDamage = false; - for (final Card attacker : attackers) { - if (!dealDamageThisPhase(attacker, firstStrikeDamage)) { - continue; - } - - if (firstStrikeDamage) { - this.combatantsThatDealtFirstStrikeDamage.add(attacker); - } - - // If potential damage is 0, continue along - final int damageDealt = attacker.getNetCombatDamage(); - if (damageDealt <= 0) { - continue; - } - - AttackingBand band = this.getBandOfAttacker(attacker); - if (band == null) { - continue; - } - - boolean trampler = attacker.hasKeyword("Trample"); - orderedBlockers = this.blockersOrderedForDamageAssignment.get(attacker); - assignedDamage = true; - // If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender - if (orderedBlockers == null || orderedBlockers.isEmpty()) { - if (trampler || !band.isBlocked()) { // this is called after declare blockers, no worries 'bout nulls in isBlocked - this.addDefendingDamage(damageDealt, attacker); - } // No damage happens if blocked but no blockers left - } else { - GameEntity defender = getDefenderByAttacker(band); - Player assigningPlayer = this.getAttackingPlayer(); - // Defensive Formation is very similar to Banding with Blockers - // It allows the defending player to assign damage instead of the attacking player - if (defender instanceof Player && defender.hasKeyword("You assign combat damage of each creature attacking you.")) { - assigningPlayer = (Player)defender; - } else if ( AttackingBand.isValidBand(orderedBlockers, true)){ - assigningPlayer = orderedBlockers.get(0).getController(); - } - - Map map = assigningPlayer.getController().assignCombatDamage(attacker, orderedBlockers, damageDealt, defender, this.getAttackingPlayer() != assigningPlayer); - for (Entry dt : map.entrySet()) { - if( dt.getKey() == null) { - if (dt.getValue() > 0) - addDefendingDamage(dt.getValue(), attacker); - } else { - dt.getKey().addAssignedDamage(dt.getValue(), attacker); - } - } - - } // if !hasFirstStrike ... - } // for - return assignedDamage; - } - - private final boolean dealDamageThisPhase(Card combatant, boolean firstStrikeDamage) { - // During first strike damage, double strike and first strike deal damage - // During regular strike damage, double strike and anyone who hasn't dealt damage deal damage - if (combatant.hasDoubleStrike()) - return true; - - if (firstStrikeDamage && combatant.hasFirstStrike()) - return true; - - return !firstStrikeDamage && !this.combatantsThatDealtFirstStrikeDamage.contains(combatant); - } - - // Damage to whatever was protected there. - private final void addDefendingDamage(final int n, final Card source) { - final GameEntity ge = this.getDefenderByAttacker(source); - - if (ge instanceof Card) { - final Card planeswalker = (Card) ge; - planeswalker.addAssignedDamage(n, source); - - return; - } - - if (!this.defendingDamageMap.containsKey(source)) { - this.defendingDamageMap.put(source, n); - } else { - this.defendingDamageMap.put(source, this.defendingDamageMap.get(source) + n); - } - } - - public final boolean assignCombatDamage(boolean firstStrikeDamage) { - boolean assignedDamage = assignAttackersDamage(firstStrikeDamage); - assignedDamage |= assignBlockersDamage(firstStrikeDamage); - if (!firstStrikeDamage) { - // Clear first strike damage list since it doesn't matter anymore - this.combatantsThatDealtFirstStrikeDamage.clear(); - } - return assignedDamage; - } - - /** - *

- * dealAssignedDamage. - *

- */ - public void dealAssignedDamage() { - // This function handles both Regular and First Strike combat assignment - - final HashMap defMap = this.defendingDamageMap; - final HashMap> wasDamaged = new HashMap>(); - - for (final Entry entry : defMap.entrySet()) { - GameEntity defender = getDefenderByAttacker(entry.getKey()); - if (defender instanceof Player) { // player - if (((Player) defender).addCombatDamage(entry.getValue(), entry.getKey())) { - if (wasDamaged.containsKey(defender)) { - wasDamaged.get(defender).add(entry.getKey()); - } else { - List l = new ArrayList(); - l.add(entry.getKey()); - wasDamaged.put(defender, l); - } - } - } else if (defender instanceof Card) { // planeswalker - if (((Card) defender).getController().addCombatDamage(entry.getValue(), entry.getKey())) { - if (wasDamaged.containsKey(defender)) { - wasDamaged.get(defender).add(entry.getKey()); - } else { - List l = new ArrayList(); - l.add(entry.getKey()); - wasDamaged.put(defender, l); - } - } - } - } - - // this can be much better below here... - - final List combatants = new ArrayList(); - combatants.addAll(this.getAttackers()); - combatants.addAll(this.getAllBlockers()); - combatants.addAll(this.getDefendingPlaneswalkers()); - - Card c; - for (int i = 0; i < combatants.size(); i++) { - c = combatants.get(i); - - // if no assigned damage to resolve, move to next - if (c.getTotalAssignedDamage() == 0) { - continue; - } - - final Map assignedDamageMap = c.getAssignedDamageMap(); - final HashMap damageMap = new HashMap(); - - for (final Entry entry : assignedDamageMap.entrySet()) { - final Card crd = entry.getKey(); - damageMap.put(crd, entry.getValue()); - } - c.addCombatDamage(damageMap); - - damageMap.clear(); - c.clearAssignedDamage(); - } - - // Run triggers - for (final GameEntity ge : wasDamaged.keySet()) { - final HashMap runParams = new HashMap(); - runParams.put("DamageSources", wasDamaged.get(ge)); - runParams.put("DamageTarget", ge); - ge.getGame().getTriggerHandler().runTrigger(TriggerType.CombatDamageDoneOnce, runParams, false); - } - - // This was deeper before, but that resulted in the stack entry acting - // like before. - - } - - /** - *

- * isUnblocked. - *

- * - * @param att - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean isUnblocked(final Card att) { - AttackingBand band = getBandOfAttacker(att); - return band == null ? false : Boolean.FALSE.equals(band.isBlocked()); - } - - /** - *

- * getUnblockedAttackers. - *

- * - * @return an array of {@link forge.game.card.Card} objects. - */ - public final List getUnblockedAttackers() { - List unblocked = new ArrayList(); - for (AttackingBand ab : attackedByBands.values()) - if ( Boolean.TRUE.equals(ab.isBlocked()) ) - unblocked.addAll(ab.getAttackers()); - - return unblocked; - } - - public boolean isPlayerAttacked(Player who) { - for(GameEntity defender : attackedByBands.keySet() ) { - Card defenderAsCard = defender instanceof Card ? (Card)defender : null; - if ((null != defenderAsCard && defenderAsCard.getController() != who ) || - (null == defenderAsCard && defender != who) ) - continue; // defender is not related to player 'who' - - for(AttackingBand ab : attackedByBands.get(defender)) { - if ( !ab.isEmpty() ) - return true; - } - } - return false; - } - - public boolean isBlocking(Card blocker) { - if ( !blocker.isInPlay() ) { - CombatLki lki = lkiCache.get(blocker); - return null != lki && !lki.isAttacker; // was blocking something anyway - } - return blockedBands.containsValue(blocker); - } - - public boolean isBlocking(Card blocker, Card attacker) { - AttackingBand ab = getBandOfAttacker(attacker); - if ( !blocker.isInPlay() ) { - CombatLki lki = lkiCache.get(blocker); - return null != lki && !lki.isAttacker && lki.relatedBands.contains(ab); // was blocking that very band - } - Collection blockers = blockedBands.get(ab); - return blockers != null && blockers.contains(blocker); - } - - /** - * TODO: Write javadoc for this method. - * @param lastKnownInfo - */ - public void saveLKI(Card lastKnownInfo) { - List attackersBlocked = null; - AttackingBand attackingBand = getBandOfAttacker(lastKnownInfo); - boolean isAttacker = attackingBand != null; - if ( !isAttacker ) { - attackersBlocked= getAttackingBandsBlockedBy(lastKnownInfo); - if ( attackersBlocked.isEmpty() ) - return; // card was not even in combat - } - List relatedBands = isAttacker ? Lists.newArrayList(attackingBand) : attackersBlocked; - lkiCache.put(lastKnownInfo, new CombatLki(isAttacker, relatedBands)); - } - -} // Class Combat +/* + * 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 . + */ +package forge.game.combat; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.lang3.tuple.Pair; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; + +import forge.game.GameEntity; +import forge.game.card.Card; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +import forge.game.player.Player; +import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; + +/** + *

+ * Combat class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class Combat { + private final Player playerWhoAttacks; + // Defenders, as they are attacked by hostile forces + private final List attackableEntries = new ArrayList(); + + // Keyed by attackable defender (player or planeswalker) + private final Multimap attackedByBands = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); + private final Multimap blockedBands = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); + + private final HashMap defendingDamageMap = new HashMap(); + + private Map> attackersOrderedForDamageAssignment = new HashMap>(); + private Map> blockersOrderedForDamageAssignment = new HashMap>(); + private Map lkiCache = new HashMap(); + + // List holds creatures who have dealt 1st strike damage to disallow them deal damage on regular basis (unless they have double-strike KW) + private List combatantsThatDealtFirstStrikeDamage = Lists.newArrayList(); + + + public Combat(Player attacker) { + playerWhoAttacks = attacker; + + // Create keys for all possible attack targets + for (Player defender : playerWhoAttacks.getOpponents()) { + this.attackableEntries.add(defender); + List planeswalkers = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS); + for (final Card pw : planeswalkers) { + this.attackableEntries.add(pw); + } + } + } + + public final Player getAttackingPlayer() { + return this.playerWhoAttacks; + } + + public final List getDefenders() { + return Lists.newArrayList(attackableEntries); + } + + public final List getDefendersControlledBy(Player who) { + List res = Lists.newArrayList(); + for(GameEntity ge : attackableEntries) { + // if defender is the player himself or his cards + if (ge == who || ge instanceof Card && ((Card) ge).getController() == who) + res.add(ge); + } + return res; + } + + public final List getDefendingPlaneswalkers() { + final List pwDefending = new ArrayList(); + for (final GameEntity o : attackableEntries) { + if (o instanceof Card) { + pwDefending.add((Card) o); + } + } + return pwDefending; + } + + public final List getAttackingBandsOf(GameEntity defender) { + return Lists.newArrayList(attackedByBands.get(defender)); + } + + public final List getAttackersOf(GameEntity defender) { + List result = new ArrayList(); + for(AttackingBand v : attackedByBands.get(defender)) { + result.addAll(v.getAttackers()); + } + return result; + } + + public final void addAttacker(final Card c, GameEntity defender) { + addAttacker(c, defender, null); + } + + public final void addAttacker(final Card c, GameEntity defender, AttackingBand band) { + Collection attackersOfDefender = attackedByBands.get(defender); + if (attackersOfDefender == null) { + System.out.println("Trying to add Attacker " + c + " to missing defender " + defender); + return; + } + + if (band == null || !attackersOfDefender.contains(band)) { + band = new AttackingBand(c, defender); + attackersOfDefender.add(band); + } else { + band.addAttacker(c); + } + } + + public final GameEntity getDefenderByAttacker(final Card c) { + return getDefenderByAttacker(getBandOfAttacker(c)); + } + + public final GameEntity getDefenderByAttacker(final AttackingBand c) { + for(Entry e : attackedByBands.entries()) { + if ( e.getValue() == c ) + return e.getKey(); + } + return null; + } + + public final Player getDefenderPlayerByAttacker(final Card c) { + GameEntity defender = getDefenderByAttacker(c); + + // System.out.println(c.toString() + " attacks " + defender.toString()); + if (defender instanceof Player) { + return (Player) defender; + } + + // maybe attack on a controlled planeswalker? + if (defender instanceof Card) { + return ((Card) defender).getController(); + } + return null; + } + + // takes LKI into consideration, should use it at all times (though a single iteration over multimap seems faster) + public final AttackingBand getBandOfAttacker(final Card c) { + for(AttackingBand ab : attackedByBands.values()) { + if ( ab.contains(c) ) + return ab; + } + CombatLki lki = lkiCache.get(c); + return lki == null ? null : lki.getFirstBand(); + } + + public final List getAttackingBands() { + return Lists.newArrayList(attackedByBands.values()); + } + + public final boolean isAttacking(final Card c) { + AttackingBand ab = getBandOfAttacker(c); + return ab != null; + } + + public boolean isAttacking(Card card, GameEntity defender) { + AttackingBand ab = getBandOfAttacker(card); + for(Entry ee : attackedByBands.entries()) + if ( ee.getValue() == ab ) + return ee.getKey() == defender; + return false; + } + + public final List getAttackers() { + List result = Lists.newArrayList(); + for(AttackingBand ab : attackedByBands.values()) + result.addAll(ab.getAttackers()); + return result; + } + + public final List getBlockers(final Card card) { + // If requesting the ordered blocking list pass true, directly. + AttackingBand band = getBandOfAttacker(card); + Collection blockers = blockedBands.get(band); + return blockers == null ? Lists.newArrayList() : Lists.newArrayList(blockers); + } + + public final boolean isBlocked(final Card attacker) { + AttackingBand band = getBandOfAttacker(attacker); + return band == null ? false : Boolean.TRUE.equals(band.isBlocked()); + + } + + // Some cards in Alpha may UNBLOCK an attacker, so second parameter is not always-true + public final void setBlocked(final Card attacker, boolean value) { + getBandOfAttacker(attacker).setBlocked(value); // called by Curtain of Light, Dazzling Beauty, Trap Runner + } + + public final void addBlocker(final Card attacker, final Card blocker) { + AttackingBand band = getBandOfAttacker(attacker); + blockedBands.put(band, blocker); + } + + // remove blocked from specific attacker + public final void removeBlockAssignment(final Card attacker, final Card blocker) { + AttackingBand band = getBandOfAttacker(attacker); + Collection cc = blockedBands.get(band); + if( cc != null) + cc.remove(blocker); + } + + // remove blocker from everywhere + public final void undoBlockingAssignment(final Card blocker) { + List toRemove = Lists.newArrayList(blocker); + blockedBands.values().removeAll(toRemove); + } + + public final List getAllBlockers() { + List result = new ArrayList(); + for(Card blocker : blockedBands.values()) { + if(!result.contains(blocker)) + result.add(blocker); + } + return result; + } + + public final List getBlockers(final AttackingBand band) { + Collection blockers = blockedBands.get(band); + return blockers == null ? Lists.newArrayList() : Lists.newArrayList(blockers); + } + + + public final List getAttackersBlockedBy(final Card blocker) { + List blocked = new ArrayList(); + for(Entry s : blockedBands.entries()) { + if (s.getValue().equals(blocker)) + blocked.addAll(s.getKey().getAttackers()); + } + return blocked; + } + + public final List getAttackingBandsBlockedBy(Card blocker) { + List bands = Lists.newArrayList(); + for( Entry kv : blockedBands.entries()) { + if (kv.getValue().equals(blocker)) + bands.add(kv.getKey()); + } + return bands; + } + + public Player getDefendingPlayerRelatedTo(final Card source) { + Card attacker = source; + if (source.isAura()) { + attacker = source.getEnchantingCard(); + } else if (source.isEquipment()) { + attacker = source.getEquippingCard(); + } else if (source.isFortification()) { + attacker = source.getFortifyingCard(); + } + + // return the corresponding defender + return getDefenderPlayerByAttacker(attacker); + } + + /** If there are multiple blockers, the Attacker declares the Assignment Order */ + public void orderBlockersForDamageAssignment() { // this method performs controller's role + + List>> blockersNeedManualOrdering = new ArrayList<>(); + + for(AttackingBand band : attackedByBands.values()) + { + if (band.isEmpty()) continue; + + Collection blockers = blockedBands.get(band); + if ( blockers == null || blockers.isEmpty() ) + continue; + + for(Card attacker : band.getAttackers()) { + if ( blockers.size() <= 1 ) + blockersOrderedForDamageAssignment.put(attacker, Lists.newArrayList(blockers)); + else // process it a bit later + blockersNeedManualOrdering.add(Pair.of(attacker, (List)blockers)); // we know there's a list + } + } + + // brought this out of iteration on bands to avoid concurrency problems + for(Pair> pair : blockersNeedManualOrdering){ + // Damage Ordering needs to take cards like Melee into account, is that happening? + List orderedBlockers = playerWhoAttacks.getController().orderBlockers(pair.getLeft(), pair.getRight()); // we know there's a list + blockersOrderedForDamageAssignment.put(pair.getLeft(), orderedBlockers); + } + } + + public void orderAttackersForDamageAssignment() { // this method performs controller's role + // If there are multiple blockers, the Attacker declares the Assignment Order + for (final Card blocker : getAllBlockers()) { + orderAttackersForDamageAssignment(blocker); + } + } + + public void orderAttackersForDamageAssignment(Card blocker) { // this method performs controller's role + List attackers = getAttackersBlockedBy(blocker); + // They need a reverse map here: Blocker => List + + Player blockerCtrl = blocker.getController(); + List orderedAttacker = attackers.size() <= 1 ? attackers : blockerCtrl.getController().orderAttackers(blocker, attackers); + + // Damage Ordering needs to take cards like Melee into account, is that happening? + attackersOrderedForDamageAssignment.put(blocker, orderedAttacker); + } + + // removes references to this attacker from all indices and orders + private void unregisterAttacker(final Card c, AttackingBand ab) { + blockersOrderedForDamageAssignment.remove(c); + + Collection blockers = blockedBands.get(ab); + if ( blockers != null ) { + for (Card b : blockers) { + // Clear removed attacker from assignment order + if (this.attackersOrderedForDamageAssignment.containsKey(b)) { + this.attackersOrderedForDamageAssignment.get(b).remove(c); + } + } + } + return; + } + + // removes references to this defender from all indices and orders + private void unregisterDefender(final Card c, AttackingBand bandBeingBlocked) { + this.attackersOrderedForDamageAssignment.remove(c); + for(Card atk : bandBeingBlocked.getAttackers()) { + if (this.blockersOrderedForDamageAssignment.containsKey(atk)) { + this.blockersOrderedForDamageAssignment.get(atk).remove(c); + } + } + } + + // remove a combatant whose side is unknown + public final void removeFromCombat(final Card c) { + AttackingBand ab = getBandOfAttacker(c); + if (ab != null) { + unregisterAttacker(c, ab); + ab.removeAttacker(c); + } + + // if not found in attackers, look for this card in blockers + for(Entry be : blockedBands.entries()) { + if(be.getValue().equals(c)) { + unregisterDefender(c, be.getKey()); + } + } + + // remove card from map + while(blockedBands.values().remove(c)); + + } // removeFromCombat() + + public final void removeAbsentCombatants() { + // iterate all attackers and remove them + for(Entry ee : attackedByBands.entries()) { + List atk = ee.getValue().getAttackers(); + for(int i = atk.size() - 1; i >= 0; i--) { // might remove items from collection, so no iterators + Card c = atk.get(i); + if ( !c.isInPlay() ) { + unregisterAttacker(c, ee.getValue()); + } + } + } + + Collection toRemove = Lists.newArrayList(); + for(Entry be : blockedBands.entries()) { + if ( !be.getValue().isInPlay() ) { + unregisterDefender(be.getValue(), be.getKey()); + toRemove.add(be.getValue()); + } + } + blockedBands.values().removeAll(toRemove); + + } // verifyCreaturesInPlay() + + + // Call this method right after turn-based action of declare blockers has been performed + public final void fireTriggersForUnblockedAttackers() { + for(AttackingBand ab : attackedByBands.values()) { + Collection blockers = blockedBands.get(ab); + boolean isBlocked = blockers != null && !blockers.isEmpty(); + ab.setBlocked(isBlocked); + + if (!isBlocked ) + for (Card attacker : ab.getAttackers()) { + // Run Unblocked Trigger + final HashMap runParams = new HashMap(); + runParams.put("Attacker", attacker); + runParams.put("Defender",this.getDefenderByAttacker(attacker)); + attacker.getGame().getTriggerHandler().runTrigger(TriggerType.AttackerUnblocked, runParams, false); + } + } + } + + private final boolean assignBlockersDamage(boolean firstStrikeDamage) { + // Assign damage by Blockers + final List blockers = this.getAllBlockers(); + boolean assignedDamage = false; + + for (final Card blocker : blockers) { + if (!dealDamageThisPhase(blocker, firstStrikeDamage)) { + continue; + } + + if (firstStrikeDamage) { + this.combatantsThatDealtFirstStrikeDamage.add(blocker); + } + + List attackers = this.attackersOrderedForDamageAssignment.get(blocker); + + final int damage = blocker.getNetCombatDamage(); + + if (!attackers.isEmpty()) { + Player attackingPlayer = this.getAttackingPlayer(); + Player assigningPlayer = blocker.getController(); + + if (AttackingBand.isValidBand(attackers, true)) + assigningPlayer = attackingPlayer; + + assignedDamage = true; + Map map = assigningPlayer.getController().assignCombatDamage(blocker, attackers, damage, null, assigningPlayer != blocker.getController()); + for (Entry dt : map.entrySet()) { + dt.getKey().addAssignedDamage(dt.getValue(), blocker); + } + } + } + + return assignedDamage; + } + + private final boolean assignAttackersDamage(boolean firstStrikeDamage) { + // Assign damage by Attackers + this.defendingDamageMap.clear(); // this should really happen in deal damage + List orderedBlockers = null; + final List attackers = this.getAttackers(); + boolean assignedDamage = false; + for (final Card attacker : attackers) { + if (!dealDamageThisPhase(attacker, firstStrikeDamage)) { + continue; + } + + if (firstStrikeDamage) { + this.combatantsThatDealtFirstStrikeDamage.add(attacker); + } + + // If potential damage is 0, continue along + final int damageDealt = attacker.getNetCombatDamage(); + if (damageDealt <= 0) { + continue; + } + + AttackingBand band = this.getBandOfAttacker(attacker); + if (band == null) { + continue; + } + + boolean trampler = attacker.hasKeyword("Trample"); + orderedBlockers = this.blockersOrderedForDamageAssignment.get(attacker); + assignedDamage = true; + // If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender + if (orderedBlockers == null || orderedBlockers.isEmpty()) { + if (trampler || !band.isBlocked()) { // this is called after declare blockers, no worries 'bout nulls in isBlocked + this.addDefendingDamage(damageDealt, attacker); + } // No damage happens if blocked but no blockers left + } else { + GameEntity defender = getDefenderByAttacker(band); + Player assigningPlayer = this.getAttackingPlayer(); + // Defensive Formation is very similar to Banding with Blockers + // It allows the defending player to assign damage instead of the attacking player + if (defender instanceof Player && defender.hasKeyword("You assign combat damage of each creature attacking you.")) { + assigningPlayer = (Player)defender; + } else if ( AttackingBand.isValidBand(orderedBlockers, true)){ + assigningPlayer = orderedBlockers.get(0).getController(); + } + + Map map = assigningPlayer.getController().assignCombatDamage(attacker, orderedBlockers, damageDealt, defender, this.getAttackingPlayer() != assigningPlayer); + for (Entry dt : map.entrySet()) { + if( dt.getKey() == null) { + if (dt.getValue() > 0) + addDefendingDamage(dt.getValue(), attacker); + } else { + dt.getKey().addAssignedDamage(dt.getValue(), attacker); + } + } + + } // if !hasFirstStrike ... + } // for + return assignedDamage; + } + + private final boolean dealDamageThisPhase(Card combatant, boolean firstStrikeDamage) { + // During first strike damage, double strike and first strike deal damage + // During regular strike damage, double strike and anyone who hasn't dealt damage deal damage + if (combatant.hasDoubleStrike()) + return true; + + if (firstStrikeDamage && combatant.hasFirstStrike()) + return true; + + return !firstStrikeDamage && !this.combatantsThatDealtFirstStrikeDamage.contains(combatant); + } + + // Damage to whatever was protected there. + private final void addDefendingDamage(final int n, final Card source) { + final GameEntity ge = this.getDefenderByAttacker(source); + + if (ge instanceof Card) { + final Card planeswalker = (Card) ge; + planeswalker.addAssignedDamage(n, source); + + return; + } + + if (!this.defendingDamageMap.containsKey(source)) { + this.defendingDamageMap.put(source, n); + } else { + this.defendingDamageMap.put(source, this.defendingDamageMap.get(source) + n); + } + } + + public final boolean assignCombatDamage(boolean firstStrikeDamage) { + boolean assignedDamage = assignAttackersDamage(firstStrikeDamage); + assignedDamage |= assignBlockersDamage(firstStrikeDamage); + if (!firstStrikeDamage) { + // Clear first strike damage list since it doesn't matter anymore + this.combatantsThatDealtFirstStrikeDamage.clear(); + } + return assignedDamage; + } + + /** + *

+ * dealAssignedDamage. + *

+ */ + public void dealAssignedDamage() { + // This function handles both Regular and First Strike combat assignment + + final HashMap defMap = this.defendingDamageMap; + final HashMap> wasDamaged = new HashMap>(); + + for (final Entry entry : defMap.entrySet()) { + GameEntity defender = getDefenderByAttacker(entry.getKey()); + if (defender instanceof Player) { // player + if (((Player) defender).addCombatDamage(entry.getValue(), entry.getKey())) { + if (wasDamaged.containsKey(defender)) { + wasDamaged.get(defender).add(entry.getKey()); + } else { + List l = new ArrayList(); + l.add(entry.getKey()); + wasDamaged.put(defender, l); + } + } + } else if (defender instanceof Card) { // planeswalker + if (((Card) defender).getController().addCombatDamage(entry.getValue(), entry.getKey())) { + if (wasDamaged.containsKey(defender)) { + wasDamaged.get(defender).add(entry.getKey()); + } else { + List l = new ArrayList(); + l.add(entry.getKey()); + wasDamaged.put(defender, l); + } + } + } + } + + // this can be much better below here... + + final List combatants = new ArrayList(); + combatants.addAll(this.getAttackers()); + combatants.addAll(this.getAllBlockers()); + combatants.addAll(this.getDefendingPlaneswalkers()); + + Card c; + for (int i = 0; i < combatants.size(); i++) { + c = combatants.get(i); + + // if no assigned damage to resolve, move to next + if (c.getTotalAssignedDamage() == 0) { + continue; + } + + final Map assignedDamageMap = c.getAssignedDamageMap(); + final HashMap damageMap = new HashMap(); + + for (final Entry entry : assignedDamageMap.entrySet()) { + final Card crd = entry.getKey(); + damageMap.put(crd, entry.getValue()); + } + c.addCombatDamage(damageMap); + + damageMap.clear(); + c.clearAssignedDamage(); + } + + // Run triggers + for (final GameEntity ge : wasDamaged.keySet()) { + final HashMap runParams = new HashMap(); + runParams.put("DamageSources", wasDamaged.get(ge)); + runParams.put("DamageTarget", ge); + ge.getGame().getTriggerHandler().runTrigger(TriggerType.CombatDamageDoneOnce, runParams, false); + } + + // This was deeper before, but that resulted in the stack entry acting + // like before. + + } + + /** + *

+ * isUnblocked. + *

+ * + * @param att + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean isUnblocked(final Card att) { + AttackingBand band = getBandOfAttacker(att); + return band == null ? false : Boolean.FALSE.equals(band.isBlocked()); + } + + /** + *

+ * getUnblockedAttackers. + *

+ * + * @return an array of {@link forge.game.card.Card} objects. + */ + public final List getUnblockedAttackers() { + List unblocked = new ArrayList(); + for (AttackingBand ab : attackedByBands.values()) + if ( Boolean.TRUE.equals(ab.isBlocked()) ) + unblocked.addAll(ab.getAttackers()); + + return unblocked; + } + + public boolean isPlayerAttacked(Player who) { + for(GameEntity defender : attackedByBands.keySet() ) { + Card defenderAsCard = defender instanceof Card ? (Card)defender : null; + if ((null != defenderAsCard && defenderAsCard.getController() != who ) || + (null == defenderAsCard && defender != who) ) + continue; // defender is not related to player 'who' + + for(AttackingBand ab : attackedByBands.get(defender)) { + if ( !ab.isEmpty() ) + return true; + } + } + return false; + } + + public boolean isBlocking(Card blocker) { + if ( !blocker.isInPlay() ) { + CombatLki lki = lkiCache.get(blocker); + return null != lki && !lki.isAttacker; // was blocking something anyway + } + return blockedBands.containsValue(blocker); + } + + public boolean isBlocking(Card blocker, Card attacker) { + AttackingBand ab = getBandOfAttacker(attacker); + if ( !blocker.isInPlay() ) { + CombatLki lki = lkiCache.get(blocker); + return null != lki && !lki.isAttacker && lki.relatedBands.contains(ab); // was blocking that very band + } + Collection blockers = blockedBands.get(ab); + return blockers != null && blockers.contains(blocker); + } + + /** + * TODO: Write javadoc for this method. + * @param lastKnownInfo + */ + public void saveLKI(Card lastKnownInfo) { + List attackersBlocked = null; + AttackingBand attackingBand = getBandOfAttacker(lastKnownInfo); + boolean isAttacker = attackingBand != null; + if ( !isAttacker ) { + attackersBlocked= getAttackingBandsBlockedBy(lastKnownInfo); + if ( attackersBlocked.isEmpty() ) + return; // card was not even in combat + } + List relatedBands = isAttacker ? Lists.newArrayList(attackingBand) : attackersBlocked; + lkiCache.put(lastKnownInfo, new CombatLki(isAttacker, relatedBands)); + } + +} // Class Combat diff --git a/forge-game/src/main/java/forge/game/combat/CombatLki.java b/forge-gui/src/main/java/forge/game/combat/CombatLki.java similarity index 100% rename from forge-game/src/main/java/forge/game/combat/CombatLki.java rename to forge-gui/src/main/java/forge/game/combat/CombatLki.java diff --git a/forge-game/src/main/java/forge/game/combat/CombatUtil.java b/forge-gui/src/main/java/forge/game/combat/CombatUtil.java similarity index 97% rename from forge-game/src/main/java/forge/game/combat/CombatUtil.java rename to forge-gui/src/main/java/forge/game/combat/CombatUtil.java index dd9e9b6d5ab..088f4f91ec1 100644 --- a/forge-game/src/main/java/forge/game/combat/CombatUtil.java +++ b/forge-gui/src/main/java/forge/game/combat/CombatUtil.java @@ -1,1119 +1,1119 @@ -/* - * 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 . - */ -package forge.game.combat; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; - -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - -import forge.card.CardType; -import forge.card.MagicColor; -import forge.card.mana.ManaCost; -import forge.game.Game; -import forge.game.GameEntity; -import forge.game.GlobalRuleChange; -import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityUtils; -import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; -import forge.game.cost.Cost; -import forge.game.phase.PhaseType; -import forge.game.phase.Untap; -import forge.game.player.Player; -import forge.game.player.PlayerController.ManaPaymentPurpose; -import forge.game.spellability.SpellAbility; -import forge.game.staticability.StaticAbility; -import forge.game.trigger.TriggerType; -import forge.game.zone.ZoneType; -import forge.util.Expressions; -import forge.util.Lang; -import forge.util.TextUtil; - - -/** - *

- * CombatUtil class. - *

- * - * @author Forge - * @version $Id: CombatUtil.java 24355 2014-01-19 01:33:24Z swordshine $ - */ -public class CombatUtil { - - // can the creature block given the combat state? - /** - *

- * canBlock. - *

- * - * @param blocker - * a {@link forge.game.card.Card} object. - * @param combat - * a {@link forge.game.combat.Combat} object. - * @return a boolean. - */ - public static boolean canBlock(final Card blocker, final Combat combat) { - if (blocker == null) { - return false; - } - if (combat == null) { - return CombatUtil.canBlock(blocker); - } - - if (!CombatUtil.canBlockMoreCreatures(blocker, combat.getAttackersBlockedBy(blocker))) { - return false; - } - final Game game = blocker.getGame(); - final int blockers = combat.getAllBlockers().size(); - - if (blockers > 1 && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyTwoBlockers)) { - return false; - } - - if (blockers > 0 && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyOneBlocker)) { - return false; - } - - return CombatUtil.canBlock(blocker); - } - - // can the creature block at all? - /** - *

- * canBlock. - *

- * - * @param blocker - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean canBlock(final Card blocker) { - return canBlock(blocker, false); - } - - // can the creature block at all? - /** - *

- * canBlock. - *

- * - * @param blocker - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean canBlock(final Card blocker, final boolean nextTurn) { - if (blocker == null) { - return false; - } - - if (!nextTurn && blocker.isTapped() && !blocker.hasKeyword("CARDNAME can block as though it were untapped.")) { - return false; - } - - if (blocker.hasKeyword("CARDNAME can't block.") || blocker.hasKeyword("CARDNAME can't attack or block.") - || blocker.isPhasedOut()) { - return false; - } - - final List list = blocker.getController().getCreaturesInPlay(); - if (list.size() < 2 && blocker.hasKeyword("CARDNAME can't attack or block alone.")) { - return false; - } - - return true; - } - - public static boolean canBlockMoreCreatures(final Card blocker, final List blockedBy) { - // TODO(sol) expand this for the additional blocking keyword - if (blockedBy.isEmpty() || blocker.hasKeyword("CARDNAME can block any number of creatures.")) { - return true; - } - int canBlockMore = blocker.getKeywordAmount("CARDNAME can block an additional creature.") - + blocker.getKeywordAmount("CARDNAME can block an additional ninety-nine creatures.") * 99; - return canBlockMore >= blockedBy.size(); - } - - // can the attacker be blocked at all? - /** - *

- * canBeBlocked. - *

- * - * @param attacker - * a {@link forge.game.card.Card} object. - * @param combat - * a {@link forge.game.combat.Combat} object. - * @return a boolean. - */ - public static boolean canBeBlocked(final Card attacker, final Combat combat, Player defendingPlayer) { - if (attacker == null) { - return true; - } - - if ( combat != null ) { - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount GT") && !combat.getBlockers(attacker).isEmpty()) { - return false; - } - - // Rule 802.4a: A player can block only creatures attacking him or a planeswalker he controls - Player attacked = combat.getDefendingPlayerRelatedTo(attacker); - if (attacked != null && attacked != defendingPlayer) { - return false; - } - } - return CombatUtil.canBeBlocked(attacker, defendingPlayer); - } - - // can the attacker be blocked at all? - /** - *

- * canBeBlocked. - *

- * - * @param attacker - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean canBeBlocked(final Card attacker, Player defender) { - if (attacker == null) { - return true; - } - - if (attacker.hasKeyword("Unblockable")) { - return false; - } - - // Landwalk - if (isUnblockableFromLandwalk(attacker, defender)) { - return false; - } - - return true; - } - - - public static boolean isUnblockableFromLandwalk(final Card attacker, Player defendingPlayer) { - //May be blocked as though it doesn't have landwalk. (Staff of the Ages) - if (attacker.hasKeyword("May be blocked as though it doesn't have landwalk.")) { - return false; - } - - ArrayList walkTypes = new ArrayList(); - - for (String basic : MagicColor.Constant.BASIC_LANDS) { - StringBuilder sbLand = new StringBuilder(); - sbLand.append(basic); - sbLand.append("walk"); - String landwalk = sbLand.toString(); - - StringBuilder sbSnow = new StringBuilder(); - sbSnow.append("Snow "); - sbSnow.append(landwalk.toLowerCase()); - String snowwalk = sbSnow.toString(); - - sbLand.insert(0, "May be blocked as though it doesn't have "); //Deadfall, etc. - sbLand.append("."); - - String mayBeBlocked = sbLand.toString(); - - if (attacker.hasKeyword(landwalk) && !attacker.hasKeyword(mayBeBlocked)) { - walkTypes.add(basic); - } - - if (attacker.hasKeyword(snowwalk)) { - StringBuilder sbSnowType = new StringBuilder(); - sbSnowType.append(basic); - sbSnowType.append(".Snow"); - walkTypes.add(sbSnowType.toString()); - } - } - - for (String keyword : attacker.getKeyword()) { - if (keyword.equals("Legendary landwalk")) { - walkTypes.add("Land.Legendary"); - } else if (keyword.equals("Desertwalk")) { - walkTypes.add("Desert"); - } else if (keyword.equals("Nonbasic landwalk")) { - walkTypes.add("Land.nonBasic"); - } else if (keyword.equals("Snow landwalk")) { - walkTypes.add("Land.Snow"); - } else if (keyword.endsWith("walk")) { - final String landtype = keyword.replace("walk", ""); - if (CardType.isALandType(landtype)) { - if (!walkTypes.contains(landtype)) { - walkTypes.add(landtype); - } - } - } - } - - if (walkTypes.isEmpty()) { - return false; - } - - String valid = StringUtils.join(walkTypes, ","); - List defendingLands = defendingPlayer.getCardsIn(ZoneType.Battlefield); - for (Card c : defendingLands) { - if (c.isValid(valid.split(","), defendingPlayer, attacker)) { - return true; - } - } - - return false; - } - - /** - * canBlockAtLeastOne. - * - * @param blocker - * the blocker - * @param attackers - * the attackers - * @return true, if one can be blocked - */ - public static boolean canBlockAtLeastOne(final Card blocker, final Iterable attackers) { - for (Card attacker : attackers) { - if (CombatUtil.canBlock(attacker, blocker)) { - return true; - } - } - return false; - } - - /** - * Can be blocked. - * - * @param attacker - * the attacker - * @param blockers - * the blockers - * @return true, if successful - */ - public static boolean canBeBlocked(final Card attacker, final List blockers, final Combat combat) { - int blocks = 0; - for (final Card blocker : blockers) { - if (CombatUtil.canBeBlocked(attacker, blocker.getController()) && CombatUtil.canBlock(attacker, blocker)) { - blocks++; - } - } - - return canAttackerBeBlockedWithAmount(attacker, blocks, combat); - } - - /** - *

- * needsMoreBlockers. - *

- * - * @param attacker - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static int needsBlockers(final Card attacker) { - - if (attacker == null) { - return 0; - } - - if (attacker.hasKeyword("CantBeBlockedByAmount LT2")) { - return 2; - } else if (attacker.hasKeyword("CantBeBlockedByAmount LT3")) { - return 3; - } else - return 1; - } - - // Has the human player chosen all mandatory blocks? - /** - *

- * finishedMandatotyBlocks. - *

- * - * @param combat - * a {@link forge.game.combat.Combat} object. - * @return a boolean. - */ - public static String validateBlocks(final Combat combat, final Player defending) { - final List defendersArmy = defending.getCreaturesInPlay(); - final List attackers = combat.getAttackers(); - final List blockers = CardLists.filterControlledBy(combat.getAllBlockers(), defending); - - // if a creature does not block but should, return false - for (final Card blocker : defendersArmy) { - if (blocker.getMustBlockCards() != null) { - int mustBlockAmt = blocker.getMustBlockCards().size(); - List blockedSoFar = combat.getAttackersBlockedBy(blocker); - List remainingBlockables = new ArrayList(); - for (final Card attacker : attackers) { - if (!blockedSoFar.contains(attacker) && CombatUtil.canBlock(attacker, blocker)) { - remainingBlockables.add(attacker); - } - } - boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar); - if (canBlockAnother && mustBlockAmt > blockedSoFar.size() && remainingBlockables.size() > 0) { - return String.format("%s must still block %s.", blocker, Lang.joinHomogenous(remainingBlockables)); - } - } - // lure effects - if (!blockers.contains(blocker) && CombatUtil.mustBlockAnAttacker(blocker, combat)) { - return String.format("%s must block an attacker, but has not been assigned to block any.", blocker); - } - - // "CARDNAME blocks each turn if able." - if (!blockers.contains(blocker) && blocker.hasKeyword("CARDNAME blocks each turn if able.")) { - for (final Card attacker : attackers) { - if (CombatUtil.canBlock(attacker, blocker, combat)) { - boolean must = true; - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { - final List possibleBlockers = Lists.newArrayList(defendersArmy); - possibleBlockers.remove(blocker); - if (!CombatUtil.canBeBlocked(attacker, possibleBlockers, combat)) { - must = false; - } - } - if (must) { - return String.format("%s must block each turn, but was not assigned to block any attacker now", blocker); - } - } - } - } - } - - for (final Card attacker : attackers) { - int cntBlockers = combat.getBlockers(attacker).size(); - // don't accept blocker amount for attackers with keyword defining valid blockers amount - if (cntBlockers > 0 && !canAttackerBeBlockedWithAmount(attacker, cntBlockers, combat)) - return String.format("%s cannot be blocked with %d creatures you've assigned", attacker, cntBlockers); - } - - return null; - } - - // can the blocker block an attacker with a lure effect? - /** - *

- * mustBlockAnAttacker. - *

- * - * @param blocker - * a {@link forge.game.card.Card} object. - * @param combat - * a {@link forge.game.combat.Combat} object. - * @return a boolean. - */ - public static boolean mustBlockAnAttacker(final Card blocker, final Combat combat) { - if (blocker == null || combat == null) { - return false; - } - - if (!CombatUtil.canBlock(blocker, combat)) { - return false; - } - - final List attackers = combat.getAttackers(); - final List attackersWithLure = new ArrayList(); - for (final Card attacker : attackers) { - if (attacker.hasStartOfKeyword("All creatures able to block CARDNAME do so.") - || (attacker.hasStartOfKeyword("All Walls able to block CARDNAME do so.") && blocker.isType("Wall")) - || (attacker.hasStartOfKeyword("All creatures with flying able to block CARDNAME do so.") && blocker.hasKeyword("Flying")) - || (attacker.hasStartOfKeyword("CARDNAME must be blocked if able.") - && combat.getBlockers(attacker).isEmpty())) { - attackersWithLure.add(attacker); - } - } - - final Player defender = blocker.getController(); - for (final Card attacker : attackersWithLure) { - if (CombatUtil.canBeBlocked(attacker, combat, defender) && CombatUtil.canBlock(attacker, blocker)) { - boolean canBe = true; - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { - final List blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay(); - blockers.remove(blocker); - if (!CombatUtil.canBeBlocked(attacker, blockers, combat)) { - canBe = false; - } - } - if (canBe) { - return true; - } - } - } - - if (blocker.getMustBlockCards() != null) { - for (final Card attacker : blocker.getMustBlockCards()) { - if (CombatUtil.canBeBlocked(attacker, combat, defender) && CombatUtil.canBlock(attacker, blocker) - && combat.isAttacking(attacker)) { - boolean canBe = true; - if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { - final List blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay(); - blockers.remove(blocker); - if (!CombatUtil.canBeBlocked(attacker, blockers, combat)) { - canBe = false; - } - } - if (canBe) { - return true; - } - } - } - } - - return false; - } - - // can a player block with one or more creatures at the moment? - /** - *

- * canAttack. - *

- * - * @param p - * a {@link forge.game.player} object. - * @param combat - * a {@link forge.game.combat.Combat} object. - * @return a boolean. - */ - public static boolean canBlock(Player p, Combat combat) { - List creatures = p.getCreaturesInPlay(); - if (creatures.isEmpty()) { return false; } - - List attackers = combat.getAttackers(); - if (attackers.isEmpty()) { return false; } - - for (Card c : creatures) { - for (Card a : attackers) { - if (CombatUtil.canBlock(a, c, combat)) { - return true; - } - } - } - return false; - } - - // can the blocker block the attacker given the combat state? - /** - *

- * canBlock. - *

- * - * @param attacker - * a {@link forge.game.card.Card} object. - * @param blocker - * a {@link forge.game.card.Card} object. - * @param combat - * a {@link forge.game.combat.Combat} object. - * @return a boolean. - */ - public static boolean canBlock(final Card attacker, final Card blocker, final Combat combat) { - if (attacker == null || blocker == null) { - return false; - } - - if (!CombatUtil.canBlock(blocker, combat)) { - return false; - } - if (!CombatUtil.canBeBlocked(attacker, combat, blocker.getController())) { - return false; - } - if (combat != null && combat.isBlocking(blocker, attacker)) { // Can't block if already blocking the attacker - return false; - } - - // if the attacker has no lure effect, but the blocker can block another - // attacker with lure, the blocker can't block the former - if (!attacker.hasKeyword("All creatures able to block CARDNAME do so.") - && !(attacker.hasStartOfKeyword("All Walls able to block CARDNAME do so.") && blocker.isType("Wall")) - && !(attacker.hasStartOfKeyword("All creatures with flying able to block CARDNAME do so.") && blocker.hasKeyword("Flying")) - && !(attacker.hasKeyword("CARDNAME must be blocked if able.") && combat.getBlockers(attacker).isEmpty()) - && !(blocker.getMustBlockCards() != null && blocker.getMustBlockCards().contains(attacker)) - && CombatUtil.mustBlockAnAttacker(blocker, combat)) { - return false; - } - - return CombatUtil.canBlock(attacker, blocker); - } - - // can the blocker block the attacker? - /** - *

- * canBlock. - *

- * - * @param attacker - * a {@link forge.game.card.Card} object. - * @param blocker - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean canBlock(final Card attacker, final Card blocker) { - return canBlock(attacker, blocker, false); - } - - // can the blocker block the attacker? - /** - *

- * canBlock. - *

- * - * @param attacker - * a {@link forge.game.card.Card} object. - * @param blocker - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean canBlock(final Card attacker, final Card blocker, final boolean nextTurn) { - if ((attacker == null) || (blocker == null)) { - return false; - } - - if (!CombatUtil.canBlock(blocker, nextTurn)) { - return false; - } - if (!CombatUtil.canBeBlocked(attacker, blocker.getController())) { - return false; - } - - if (CardFactoryUtil.hasProtectionFrom(blocker, attacker)) { - return false; - } - -// if (blocker.hasStartOfKeyword("CARDNAME can't block ")) { -// for (final String kw : blocker.getKeyword()) { -// if (kw.startsWith("CARDNAME can't block ")) { -// final String unblockableCard = kw.substring(21); -// final int id = Integer.parseInt(unblockableCard.substring(unblockableCard.lastIndexOf("(") + 1, -// unblockableCard.length() - 1)); -// if (attacker.getUniqueNumber() == id) { -// return false; -// } -// } -// } -// } - - // rare case: - if (blocker.hasKeyword("Shadow") - && blocker.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) { - return false; - } - - if (attacker.hasKeyword("Shadow") && !blocker.hasKeyword("Shadow") - && !blocker.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) { - return false; - } - - if (!attacker.hasKeyword("Shadow") && blocker.hasKeyword("Shadow")) { - return false; - } - - if (attacker.hasKeyword("Creatures with power less than CARDNAME's power can't block it.") && attacker.getNetAttack() > blocker.getNetAttack()) { - return false; - } - - if (attacker.hasStartOfKeyword("CantBeBlockedBy ")) { - final int keywordPosition = attacker.getKeywordPosition("CantBeBlockedBy "); - final String parse = attacker.getKeyword().get(keywordPosition).toString(); - final String[] k = parse.split(" ", 2); - final String[] restrictions = k[1].split(","); - if (blocker.isValid(restrictions, attacker.getController(), attacker)) { - return false; - } - } - - if (blocker.hasStartOfKeyword("CantBlock")) { - final int keywordPosition = blocker.getKeywordPosition("CantBlock"); - final String parse = blocker.getKeyword().get(keywordPosition).toString(); - final String[] k = parse.split(" ", 2); - final String[] restrictions = k[1].split(","); - if (attacker.isValid(restrictions, blocker.getController(), blocker)) { - return false; - } - } - - if (blocker.hasKeyword("CARDNAME can block only creatures with flying.") && !attacker.hasKeyword("Flying")) { - return false; - } - - if (attacker.hasKeyword("Flying") && !blocker.hasKeyword("Flying") && !blocker.hasKeyword("Reach")) { - return false; - } - - if (attacker.hasKeyword("Horsemanship") && !blocker.hasKeyword("Horsemanship")) { - return false; - } - - if (attacker.hasKeyword("Fear") && !blocker.isArtifact() && !blocker.isBlack()) { - return false; - } - - if (attacker.hasKeyword("Intimidate") && !blocker.isArtifact() && !blocker.sharesColorWith(attacker)) { - return false; - } - - return true; - } // canBlock() - - public static void checkAttackOrBlockAlone(Combat combat) { - // Handles removing cards like Mogg Flunkies from combat if group attack - // didn't occur - for (Card c1 : combat.getAttackers()) { - if (c1.hasKeyword("CARDNAME can't attack or block alone.")) { - if (combat.getAttackers().size() < 2) { - combat.removeFromCombat(c1); - } - } - } - } - - // can a player attack with one or more creatures at the moment? - /** - *

- * canAttack. - *

- * - * @param p - * a {@link forge.game.player} object. - * @return a boolean. - */ - public static boolean canAttack(Player p) { - List creatures = p.getCreaturesInPlay(); - if (creatures.isEmpty()) { return false; } - - List defenders = p.getOpponents(); - if (defenders.isEmpty()) { return false; } - - boolean foundCreatureThatCantAttackAlone = false; - - for (Card c : creatures) { - if (CombatUtil.canAttack(c)) { - for (Player def : defenders) { - if (CombatUtil.canAttackNextTurn(c, def)) { - if (c.hasKeyword("CARDNAME can't attack or block alone.")) { - //ensure another possible attacker is found - //if the first one found can't attack alone - if (!foundCreatureThatCantAttackAlone) { - foundCreatureThatCantAttackAlone = true; - break; - } - } - return true; - } - } - } - } - return false; - } - - // can a creature attack given the combat state - /** - *

- * canAttack. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param combat - * a {@link forge.game.combat.Combat} object. - * @return a boolean. - */ - public static boolean canAttack(final Card c, final GameEntity def, final Combat combat) { - int cntAttackers = combat.getAttackers().size(); - final Game game = c.getGame(); - - if (cntAttackers > 0) { - for (final Card card : game.getCardsIn(ZoneType.Battlefield)) { - for (final String keyword : card.getKeyword()) { - if (cntAttackers > 1) { - if (keyword.equals("No more than two creatures can attack each combat.")) { - return false; - } - if (keyword.equals("No more than two creatures can attack you each combat.") && - card.getController().getOpponent().equals(c.getController())) { - return false; - } - } - if (keyword.equals("CARDNAME can only attack alone.") && combat.isAttacking(card)) { - return false; - } - } - } - - if (c.hasKeyword("CARDNAME can only attack alone.")) { - return false; - } - - if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyOneAttackerACombat)) { - return false; - } - } - - if (c.hasKeyword("CARDNAME can't attack or block alone.") && - c.getController().getCreaturesInPlay().size() < 2) { - return false; - } - - if ((cntAttackers > 0 || c.getController().getAttackedWithCreatureThisTurn()) && - game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyOneAttackerATurn)) { - return false; - } - - return CombatUtil.canAttack(c, def); - } - - // can a creature attack at the moment? - /** - *

- * canAttack. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean canAttack(final Card c, final GameEntity defender) { - return canAttack(c) && canAttackNextTurn(c, defender); - } - - public static boolean canAttack(final Card c) { - final Game game = c.getGame(); - if (c.isTapped() || c.isPhasedOut() - || (c.hasSickness() && !c.hasKeyword("CARDNAME can attack as though it had haste.")) - || game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) { - return false; - } - return true; - } - - public static boolean canAttackerBeBlockedWithAmount(Card attacker, int amount, Combat combat) { - if( amount == 0 ) - return false; // no block - - List restrictions = Lists.newArrayList(); - for ( String kw : attacker.getKeyword() ) { - if ( kw.startsWith("CantBeBlockedByAmount") ) - restrictions.add(TextUtil.split(kw, ' ', 2)[1]); - } - for ( String res : restrictions ) { - int operand = Integer.parseInt(res.substring(2)); - String operator = res.substring(0,2); - if (Expressions.compare(amount, operator, operand) ) - return false; - } - if (combat != null && attacker.hasKeyword("CARDNAME can't be blocked " + - "unless all creatures defending player controls block it.")) { - Player defender = combat.getDefenderPlayerByAttacker(attacker); - if (amount < defender.getCreaturesInPlay().size()) { - return false; - } - } - - return true; - } - - // can a creature attack if untapped and without summoning sickness? - /** - *

- * canAttackNextTurn. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean canAttackNextTurn(final Card c) { - return canAttackNextTurn(c, c.getController().getOpponent()); - } - - // can a creature attack if untapped and without summoning sickness? - /** - *

- * canAttackNextTurn. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean canAttackNextTurn(final Card c, final GameEntity defender) { - if (!c.isCreature()) { - return false; - } - - Player defendingPlayer = null; - if (defender instanceof Card) { - defendingPlayer = ((Card) defender).getController(); - } else { - defendingPlayer = (Player) defender; - } - - // CARDNAME can't attack if defending player controls an untapped - // creature with power ... - final int[] powerLimit = { 0 }; - String cantAttackKw = null; - - for( String kw : c.getKeyword()) { - if (kw.startsWith("CARDNAME can't attack if defending player controls an untapped creature with power")) { - cantAttackKw = kw; - } - } - - // The keyword - // "CARDNAME can't attack if defending player controls an untapped creature with power" - // ... is present - if (cantAttackKw != null) { - final String[] asSeparateWords = cantAttackKw.trim().split(" "); - - if (asSeparateWords.length >= 15) { - if (StringUtils.isNumeric(asSeparateWords[12])) { - powerLimit[0] = Integer.parseInt((asSeparateWords[12]).trim()); - - List list = defendingPlayer.getCreaturesInPlay(); - list = CardLists.filter(list, new Predicate() { - @Override - public boolean apply(final Card ct) { - return (ct.isUntapped() - && ((ct.getNetAttack() >= powerLimit[0] && asSeparateWords[14].contains("greater")) - || (ct.getNetAttack() <= powerLimit[0] && asSeparateWords[14].contains("less")))); - } - }); - if (!list.isEmpty()) { - return false; - } - } - } - } // hasKeyword = CARDNAME can't attack if defending player controls an - // untapped creature with power ... - - final List list = defendingPlayer.getCardsIn(ZoneType.Battlefield); - List temp; - for (String keyword : c.getKeyword()) { - if (keyword.equals("CARDNAME can't attack.") || keyword.equals("CARDNAME can't attack or block.")) { - return false; - } else if (keyword.equals("Defender") && !c.hasKeyword("CARDNAME can attack as though it didn't have defender.")) { - return false; - } else if (keyword.equals("CARDNAME can't attack unless defending player controls an Island.")) { - temp = CardLists.getType(list, "Island"); - if (temp.isEmpty()) { - return false; - } - } else if (keyword.equals("CARDNAME can't attack unless defending player controls a Forest.")) { - temp = CardLists.getType(list, "Forest"); - if (temp.isEmpty()) { - return false; - } - } else if (keyword.equals("CARDNAME can't attack unless defending player controls a Swamp.")) { - temp = CardLists.getType(list, "Swamp"); - if (temp.isEmpty()) { - return false; - } - } else if (keyword.equals("CARDNAME can't attack unless defending player controls a Mountain.")) { - temp = CardLists.getType(list, "Mountain"); - if (temp.isEmpty()) { - return false; - } - } else if (keyword.equals("CARDNAME can't attack unless defending player controls a snow land.")) { - temp = CardLists.filter(list, CardPredicates.Presets.SNOW_LANDS); - if (temp.isEmpty()) { - return false; - } - } else if (keyword.equals("CARDNAME can't attack unless defending player controls a blue permanent.")) { - if (!Iterables.any(list, CardPredicates.isColor(MagicColor.BLUE))) - return false; - } else if (keyword.equals("CARDNAME can't attack during extra turns.")) { - if (c.getGame().getPhaseHandler().getPlayerTurn().isPlayingExtraTurn()) - return false; - } else if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) { - final String defined = keyword.split(":")[1]; - final Player player = AbilityUtils.getDefinedPlayers(c, defined, null).get(0); - if (!defender.equals(player)) { - return false; - } - } - } - - // The creature won't untap next turn - if (c.isTapped() && !Untap.canUntap(c)) { - return false; - } - final Game game = c.getGame(); - // CantBeActivated static abilities - for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { - final ArrayList staticAbilities = ca.getStaticAbilities(); - for (final StaticAbility stAb : staticAbilities) { - if (stAb.applyAbility("CantAttack", c, defender)) { - return false; - } - } - } - - return true; - } // canAttack() - - /** - *

- * checkPropagandaEffects. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param bLast - * a boolean. - */ - public static boolean checkPropagandaEffects(final Game game, final Card c, final Combat combat) { - Cost attackCost = new Cost(ManaCost.ZERO, true); - // Sort abilities to apply them in proper order - for (Card card : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { - final ArrayList staticAbilities = card.getStaticAbilities(); - for (final StaticAbility stAb : staticAbilities) { - Cost additionalCost = stAb.getAttackCost(c, combat.getDefenderByAttacker(c)); - if ( null != additionalCost ) - attackCost.add(additionalCost); - } - } - - boolean isFree = attackCost.getTotalMana().isZero() && attackCost.isOnlyManaCost(); // true if needless to pay - return isFree || c.getController().getController().payManaOptional(c, attackCost, null, "Pay additional cost to declare " + c + " an attacker", ManaPaymentPurpose.DeclareAttacker); - } - - /** - *

- * This method checks triggered effects of attacking creatures, right before - * defending player declares blockers. - *

- * @param game - * - * @param c - * a {@link forge.game.card.Card} object. - */ - public static void checkDeclaredAttacker(final Game game, final Card c, final Combat combat) { - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("Attacker", c); - final List otherAttackers = combat.getAttackers(); - otherAttackers.remove(c); - runParams.put("OtherAttackers", otherAttackers); - runParams.put("Attacked", combat.getDefenderByAttacker(c)); - game.getTriggerHandler().runTrigger(TriggerType.Attacks, runParams, false); - - // Annihilator: can be copied by Strionic Resonator now - if (!c.getDamageHistory().getCreatureAttackedThisCombat()) { - for (final String key : c.getKeyword()) { - if (!key.startsWith("Annihilator ")) continue; - final String[] k = key.split(" ", 2); - - String sb = "Annihilator - Defending player sacrifices " + k[1] + " permanents."; - String effect = "AB$ Sacrifice | Cost$ 0 | Defined$ DefendingPlayer | SacValid$ Permanent | Amount$ " + k[1]; - - SpellAbility ability = AbilityFactory.getAbility(effect, c); - ability.setActivatingPlayer(c.getController()); - ability.setDescription(sb); - ability.setStackDescription(sb); - ability.setTrigger(true); - - game.getStack().addSimultaneousStackEntry(ability); - - } - } - - c.getDamageHistory().setCreatureAttackedThisCombat(true); - c.getDamageHistory().clearNotAttackedSinceLastUpkeepOf(); - c.getController().setAttackedWithCreatureThisTurn(true); - c.getController().incrementAttackersDeclaredThisTurn(); - } // checkDeclareAttackers - - - public static void handleRampage(final Game game, final Card a, final List blockers) { - for (final String keyword : a.getKeyword()) { - int idx = keyword.indexOf("Rampage "); - if ( idx < 0) - continue; - - final int numBlockers = blockers.size(); - if (numBlockers > 1) { - final int magnitude = Integer.valueOf(keyword.substring(idx + "Rampage ".length())); - CombatUtil.executeRampageAbility(game, a, magnitude, numBlockers); - } - } // end Rampage - } - - public static void handleFlankingKeyword(final Game game, final Card attacker, final List blockers) { - for (Card blocker : blockers) { - if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) { - int flankingMagnitude = 0; - - for (String kw : attacker.getKeyword()) { - if (kw.equals("Flanking")) { - flankingMagnitude++; - } - } - - // Rule 702.23b: If a creature has multiple instances of flanking, each triggers separately. - for( int i = 0; i < flankingMagnitude; i++ ) { - String effect = String.format("AB$ Pump | Cost$ 0 | Defined$ CardUID_%d | NumAtt$ -1 | NumDef$ -1 | ", blocker.getUniqueNumber()); - String desc = String.format("StackDescription$ Flanking (The blocking %s gets -1/-1 until end of turn)", blocker.getName()); - - SpellAbility ability = AbilityFactory.getAbility(effect + desc, attacker); - ability.setActivatingPlayer(attacker.getController()); - ability.setDescription(ability.getStackDescription()); - ability.setTrigger(true); - - game.getStack().addSimultaneousStackEntry(ability); - } - } // flanking - - blocker.addBlockedThisTurn(attacker); - attacker.addBlockedByThisTurn(blocker); - } - } - - /** - * executes Rampage abilities for a given card. - * @param game - * - * @param c - * the card to add rampage bonus to - * @param magnitude - * the magnitude of rampage (ie Rampage 2 means magnitude should - * be 2) - * @param numBlockers - * - the number of creatures blocking this rampaging creature - */ - private static void executeRampageAbility(final Game game, final Card c, final int magnitude, final int numBlockers) { - // numBlockers starts with 1 since it is for every creature beyond the first - for (int i = 1; i < numBlockers; i++) { - String effect = "AB$ Pump | Cost$ 0 | " + c.getUniqueNumber() + " | NumAtt$ " + magnitude + " | NumDef$ " + magnitude + " | "; - String desc = "StackDescription$ Rampage " + magnitude + " (Whenever CARDNAME becomes blocked, it gets +" + magnitude + "/+" - + magnitude + " until end of turn for each creature blocking it beyond the first.)"; - - SpellAbility ability = AbilityFactory.getAbility(effect + desc, c); - ability.setActivatingPlayer(c.getController()); - ability.setDescription(ability.getStackDescription()); - ability.setTrigger(true); - - game.getStack().addSimultaneousStackEntry(ability); - } - } - -} // end class CombatUtil +/* + * 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 . + */ +package forge.game.combat; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import forge.card.CardType; +import forge.card.MagicColor; +import forge.card.mana.ManaCost; +import forge.game.Game; +import forge.game.GameEntity; +import forge.game.GlobalRuleChange; +import forge.game.ability.AbilityFactory; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.card.CardFactoryUtil; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +import forge.game.cost.Cost; +import forge.game.phase.PhaseType; +import forge.game.phase.Untap; +import forge.game.player.Player; +import forge.game.player.PlayerController.ManaPaymentPurpose; +import forge.game.spellability.SpellAbility; +import forge.game.staticability.StaticAbility; +import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; +import forge.util.Expressions; +import forge.util.Lang; +import forge.util.TextUtil; + + +/** + *

+ * CombatUtil class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class CombatUtil { + + // can the creature block given the combat state? + /** + *

+ * canBlock. + *

+ * + * @param blocker + * a {@link forge.game.card.Card} object. + * @param combat + * a {@link forge.game.combat.Combat} object. + * @return a boolean. + */ + public static boolean canBlock(final Card blocker, final Combat combat) { + if (blocker == null) { + return false; + } + if (combat == null) { + return CombatUtil.canBlock(blocker); + } + + if (!CombatUtil.canBlockMoreCreatures(blocker, combat.getAttackersBlockedBy(blocker))) { + return false; + } + final Game game = blocker.getGame(); + final int blockers = combat.getAllBlockers().size(); + + if (blockers > 1 && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyTwoBlockers)) { + return false; + } + + if (blockers > 0 && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyOneBlocker)) { + return false; + } + + return CombatUtil.canBlock(blocker); + } + + // can the creature block at all? + /** + *

+ * canBlock. + *

+ * + * @param blocker + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public static boolean canBlock(final Card blocker) { + return canBlock(blocker, false); + } + + // can the creature block at all? + /** + *

+ * canBlock. + *

+ * + * @param blocker + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public static boolean canBlock(final Card blocker, final boolean nextTurn) { + if (blocker == null) { + return false; + } + + if (!nextTurn && blocker.isTapped() && !blocker.hasKeyword("CARDNAME can block as though it were untapped.")) { + return false; + } + + if (blocker.hasKeyword("CARDNAME can't block.") || blocker.hasKeyword("CARDNAME can't attack or block.") + || blocker.isPhasedOut()) { + return false; + } + + final List list = blocker.getController().getCreaturesInPlay(); + if (list.size() < 2 && blocker.hasKeyword("CARDNAME can't attack or block alone.")) { + return false; + } + + return true; + } + + public static boolean canBlockMoreCreatures(final Card blocker, final List blockedBy) { + // TODO(sol) expand this for the additional blocking keyword + if (blockedBy.isEmpty() || blocker.hasKeyword("CARDNAME can block any number of creatures.")) { + return true; + } + int canBlockMore = blocker.getKeywordAmount("CARDNAME can block an additional creature.") + + blocker.getKeywordAmount("CARDNAME can block an additional ninety-nine creatures.") * 99; + return canBlockMore >= blockedBy.size(); + } + + // can the attacker be blocked at all? + /** + *

+ * canBeBlocked. + *

+ * + * @param attacker + * a {@link forge.game.card.Card} object. + * @param combat + * a {@link forge.game.combat.Combat} object. + * @return a boolean. + */ + public static boolean canBeBlocked(final Card attacker, final Combat combat, Player defendingPlayer) { + if (attacker == null) { + return true; + } + + if ( combat != null ) { + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount GT") && !combat.getBlockers(attacker).isEmpty()) { + return false; + } + + // Rule 802.4a: A player can block only creatures attacking him or a planeswalker he controls + Player attacked = combat.getDefendingPlayerRelatedTo(attacker); + if (attacked != null && attacked != defendingPlayer) { + return false; + } + } + return CombatUtil.canBeBlocked(attacker, defendingPlayer); + } + + // can the attacker be blocked at all? + /** + *

+ * canBeBlocked. + *

+ * + * @param attacker + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public static boolean canBeBlocked(final Card attacker, Player defender) { + if (attacker == null) { + return true; + } + + if (attacker.hasKeyword("Unblockable")) { + return false; + } + + // Landwalk + if (isUnblockableFromLandwalk(attacker, defender)) { + return false; + } + + return true; + } + + + public static boolean isUnblockableFromLandwalk(final Card attacker, Player defendingPlayer) { + //May be blocked as though it doesn't have landwalk. (Staff of the Ages) + if (attacker.hasKeyword("May be blocked as though it doesn't have landwalk.")) { + return false; + } + + ArrayList walkTypes = new ArrayList(); + + for (String basic : MagicColor.Constant.BASIC_LANDS) { + StringBuilder sbLand = new StringBuilder(); + sbLand.append(basic); + sbLand.append("walk"); + String landwalk = sbLand.toString(); + + StringBuilder sbSnow = new StringBuilder(); + sbSnow.append("Snow "); + sbSnow.append(landwalk.toLowerCase()); + String snowwalk = sbSnow.toString(); + + sbLand.insert(0, "May be blocked as though it doesn't have "); //Deadfall, etc. + sbLand.append("."); + + String mayBeBlocked = sbLand.toString(); + + if (attacker.hasKeyword(landwalk) && !attacker.hasKeyword(mayBeBlocked)) { + walkTypes.add(basic); + } + + if (attacker.hasKeyword(snowwalk)) { + StringBuilder sbSnowType = new StringBuilder(); + sbSnowType.append(basic); + sbSnowType.append(".Snow"); + walkTypes.add(sbSnowType.toString()); + } + } + + for (String keyword : attacker.getKeyword()) { + if (keyword.equals("Legendary landwalk")) { + walkTypes.add("Land.Legendary"); + } else if (keyword.equals("Desertwalk")) { + walkTypes.add("Desert"); + } else if (keyword.equals("Nonbasic landwalk")) { + walkTypes.add("Land.nonBasic"); + } else if (keyword.equals("Snow landwalk")) { + walkTypes.add("Land.Snow"); + } else if (keyword.endsWith("walk")) { + final String landtype = keyword.replace("walk", ""); + if (CardType.isALandType(landtype)) { + if (!walkTypes.contains(landtype)) { + walkTypes.add(landtype); + } + } + } + } + + if (walkTypes.isEmpty()) { + return false; + } + + String valid = StringUtils.join(walkTypes, ","); + List defendingLands = defendingPlayer.getCardsIn(ZoneType.Battlefield); + for (Card c : defendingLands) { + if (c.isValid(valid.split(","), defendingPlayer, attacker)) { + return true; + } + } + + return false; + } + + /** + * canBlockAtLeastOne. + * + * @param blocker + * the blocker + * @param attackers + * the attackers + * @return true, if one can be blocked + */ + public static boolean canBlockAtLeastOne(final Card blocker, final Iterable attackers) { + for (Card attacker : attackers) { + if (CombatUtil.canBlock(attacker, blocker)) { + return true; + } + } + return false; + } + + /** + * Can be blocked. + * + * @param attacker + * the attacker + * @param blockers + * the blockers + * @return true, if successful + */ + public static boolean canBeBlocked(final Card attacker, final List blockers, final Combat combat) { + int blocks = 0; + for (final Card blocker : blockers) { + if (CombatUtil.canBeBlocked(attacker, blocker.getController()) && CombatUtil.canBlock(attacker, blocker)) { + blocks++; + } + } + + return canAttackerBeBlockedWithAmount(attacker, blocks, combat); + } + + /** + *

+ * needsMoreBlockers. + *

+ * + * @param attacker + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public static int needsBlockers(final Card attacker) { + + if (attacker == null) { + return 0; + } + + if (attacker.hasKeyword("CantBeBlockedByAmount LT2")) { + return 2; + } else if (attacker.hasKeyword("CantBeBlockedByAmount LT3")) { + return 3; + } else + return 1; + } + + // Has the human player chosen all mandatory blocks? + /** + *

+ * finishedMandatotyBlocks. + *

+ * + * @param combat + * a {@link forge.game.combat.Combat} object. + * @return a boolean. + */ + public static String validateBlocks(final Combat combat, final Player defending) { + final List defendersArmy = defending.getCreaturesInPlay(); + final List attackers = combat.getAttackers(); + final List blockers = CardLists.filterControlledBy(combat.getAllBlockers(), defending); + + // if a creature does not block but should, return false + for (final Card blocker : defendersArmy) { + if (blocker.getMustBlockCards() != null) { + int mustBlockAmt = blocker.getMustBlockCards().size(); + List blockedSoFar = combat.getAttackersBlockedBy(blocker); + List remainingBlockables = new ArrayList(); + for (final Card attacker : attackers) { + if (!blockedSoFar.contains(attacker) && CombatUtil.canBlock(attacker, blocker)) { + remainingBlockables.add(attacker); + } + } + boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar); + if (canBlockAnother && mustBlockAmt > blockedSoFar.size() && remainingBlockables.size() > 0) { + return String.format("%s must still block %s.", blocker, Lang.joinHomogenous(remainingBlockables)); + } + } + // lure effects + if (!blockers.contains(blocker) && CombatUtil.mustBlockAnAttacker(blocker, combat)) { + return String.format("%s must block an attacker, but has not been assigned to block any.", blocker); + } + + // "CARDNAME blocks each turn if able." + if (!blockers.contains(blocker) && blocker.hasKeyword("CARDNAME blocks each turn if able.")) { + for (final Card attacker : attackers) { + if (CombatUtil.canBlock(attacker, blocker, combat)) { + boolean must = true; + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { + final List possibleBlockers = Lists.newArrayList(defendersArmy); + possibleBlockers.remove(blocker); + if (!CombatUtil.canBeBlocked(attacker, possibleBlockers, combat)) { + must = false; + } + } + if (must) { + return String.format("%s must block each turn, but was not assigned to block any attacker now", blocker); + } + } + } + } + } + + for (final Card attacker : attackers) { + int cntBlockers = combat.getBlockers(attacker).size(); + // don't accept blocker amount for attackers with keyword defining valid blockers amount + if (cntBlockers > 0 && !canAttackerBeBlockedWithAmount(attacker, cntBlockers, combat)) + return String.format("%s cannot be blocked with %d creatures you've assigned", attacker, cntBlockers); + } + + return null; + } + + // can the blocker block an attacker with a lure effect? + /** + *

+ * mustBlockAnAttacker. + *

+ * + * @param blocker + * a {@link forge.game.card.Card} object. + * @param combat + * a {@link forge.game.combat.Combat} object. + * @return a boolean. + */ + public static boolean mustBlockAnAttacker(final Card blocker, final Combat combat) { + if (blocker == null || combat == null) { + return false; + } + + if (!CombatUtil.canBlock(blocker, combat)) { + return false; + } + + final List attackers = combat.getAttackers(); + final List attackersWithLure = new ArrayList(); + for (final Card attacker : attackers) { + if (attacker.hasStartOfKeyword("All creatures able to block CARDNAME do so.") + || (attacker.hasStartOfKeyword("All Walls able to block CARDNAME do so.") && blocker.isType("Wall")) + || (attacker.hasStartOfKeyword("All creatures with flying able to block CARDNAME do so.") && blocker.hasKeyword("Flying")) + || (attacker.hasStartOfKeyword("CARDNAME must be blocked if able.") + && combat.getBlockers(attacker).isEmpty())) { + attackersWithLure.add(attacker); + } + } + + final Player defender = blocker.getController(); + for (final Card attacker : attackersWithLure) { + if (CombatUtil.canBeBlocked(attacker, combat, defender) && CombatUtil.canBlock(attacker, blocker)) { + boolean canBe = true; + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { + final List blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay(); + blockers.remove(blocker); + if (!CombatUtil.canBeBlocked(attacker, blockers, combat)) { + canBe = false; + } + } + if (canBe) { + return true; + } + } + } + + if (blocker.getMustBlockCards() != null) { + for (final Card attacker : blocker.getMustBlockCards()) { + if (CombatUtil.canBeBlocked(attacker, combat, defender) && CombatUtil.canBlock(attacker, blocker) + && combat.isAttacking(attacker)) { + boolean canBe = true; + if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { + final List blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay(); + blockers.remove(blocker); + if (!CombatUtil.canBeBlocked(attacker, blockers, combat)) { + canBe = false; + } + } + if (canBe) { + return true; + } + } + } + } + + return false; + } + + // can a player block with one or more creatures at the moment? + /** + *

+ * canAttack. + *

+ * + * @param p + * a {@link forge.game.player} object. + * @param combat + * a {@link forge.game.combat.Combat} object. + * @return a boolean. + */ + public static boolean canBlock(Player p, Combat combat) { + List creatures = p.getCreaturesInPlay(); + if (creatures.isEmpty()) { return false; } + + List attackers = combat.getAttackers(); + if (attackers.isEmpty()) { return false; } + + for (Card c : creatures) { + for (Card a : attackers) { + if (CombatUtil.canBlock(a, c, combat)) { + return true; + } + } + } + return false; + } + + // can the blocker block the attacker given the combat state? + /** + *

+ * canBlock. + *

+ * + * @param attacker + * a {@link forge.game.card.Card} object. + * @param blocker + * a {@link forge.game.card.Card} object. + * @param combat + * a {@link forge.game.combat.Combat} object. + * @return a boolean. + */ + public static boolean canBlock(final Card attacker, final Card blocker, final Combat combat) { + if (attacker == null || blocker == null) { + return false; + } + + if (!CombatUtil.canBlock(blocker, combat)) { + return false; + } + if (!CombatUtil.canBeBlocked(attacker, combat, blocker.getController())) { + return false; + } + if (combat != null && combat.isBlocking(blocker, attacker)) { // Can't block if already blocking the attacker + return false; + } + + // if the attacker has no lure effect, but the blocker can block another + // attacker with lure, the blocker can't block the former + if (!attacker.hasKeyword("All creatures able to block CARDNAME do so.") + && !(attacker.hasStartOfKeyword("All Walls able to block CARDNAME do so.") && blocker.isType("Wall")) + && !(attacker.hasStartOfKeyword("All creatures with flying able to block CARDNAME do so.") && blocker.hasKeyword("Flying")) + && !(attacker.hasKeyword("CARDNAME must be blocked if able.") && combat.getBlockers(attacker).isEmpty()) + && !(blocker.getMustBlockCards() != null && blocker.getMustBlockCards().contains(attacker)) + && CombatUtil.mustBlockAnAttacker(blocker, combat)) { + return false; + } + + return CombatUtil.canBlock(attacker, blocker); + } + + // can the blocker block the attacker? + /** + *

+ * canBlock. + *

+ * + * @param attacker + * a {@link forge.game.card.Card} object. + * @param blocker + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public static boolean canBlock(final Card attacker, final Card blocker) { + return canBlock(attacker, blocker, false); + } + + // can the blocker block the attacker? + /** + *

+ * canBlock. + *

+ * + * @param attacker + * a {@link forge.game.card.Card} object. + * @param blocker + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public static boolean canBlock(final Card attacker, final Card blocker, final boolean nextTurn) { + if ((attacker == null) || (blocker == null)) { + return false; + } + + if (!CombatUtil.canBlock(blocker, nextTurn)) { + return false; + } + if (!CombatUtil.canBeBlocked(attacker, blocker.getController())) { + return false; + } + + if (CardFactoryUtil.hasProtectionFrom(blocker, attacker)) { + return false; + } + +// if (blocker.hasStartOfKeyword("CARDNAME can't block ")) { +// for (final String kw : blocker.getKeyword()) { +// if (kw.startsWith("CARDNAME can't block ")) { +// final String unblockableCard = kw.substring(21); +// final int id = Integer.parseInt(unblockableCard.substring(unblockableCard.lastIndexOf("(") + 1, +// unblockableCard.length() - 1)); +// if (attacker.getUniqueNumber() == id) { +// return false; +// } +// } +// } +// } + + // rare case: + if (blocker.hasKeyword("Shadow") + && blocker.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) { + return false; + } + + if (attacker.hasKeyword("Shadow") && !blocker.hasKeyword("Shadow") + && !blocker.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) { + return false; + } + + if (!attacker.hasKeyword("Shadow") && blocker.hasKeyword("Shadow")) { + return false; + } + + if (attacker.hasKeyword("Creatures with power less than CARDNAME's power can't block it.") && attacker.getNetAttack() > blocker.getNetAttack()) { + return false; + } + + if (attacker.hasStartOfKeyword("CantBeBlockedBy ")) { + final int keywordPosition = attacker.getKeywordPosition("CantBeBlockedBy "); + final String parse = attacker.getKeyword().get(keywordPosition).toString(); + final String[] k = parse.split(" ", 2); + final String[] restrictions = k[1].split(","); + if (blocker.isValid(restrictions, attacker.getController(), attacker)) { + return false; + } + } + + if (blocker.hasStartOfKeyword("CantBlock")) { + final int keywordPosition = blocker.getKeywordPosition("CantBlock"); + final String parse = blocker.getKeyword().get(keywordPosition).toString(); + final String[] k = parse.split(" ", 2); + final String[] restrictions = k[1].split(","); + if (attacker.isValid(restrictions, blocker.getController(), blocker)) { + return false; + } + } + + if (blocker.hasKeyword("CARDNAME can block only creatures with flying.") && !attacker.hasKeyword("Flying")) { + return false; + } + + if (attacker.hasKeyword("Flying") && !blocker.hasKeyword("Flying") && !blocker.hasKeyword("Reach")) { + return false; + } + + if (attacker.hasKeyword("Horsemanship") && !blocker.hasKeyword("Horsemanship")) { + return false; + } + + if (attacker.hasKeyword("Fear") && !blocker.isArtifact() && !blocker.isBlack()) { + return false; + } + + if (attacker.hasKeyword("Intimidate") && !blocker.isArtifact() && !blocker.sharesColorWith(attacker)) { + return false; + } + + return true; + } // canBlock() + + public static void checkAttackOrBlockAlone(Combat combat) { + // Handles removing cards like Mogg Flunkies from combat if group attack + // didn't occur + for (Card c1 : combat.getAttackers()) { + if (c1.hasKeyword("CARDNAME can't attack or block alone.")) { + if (combat.getAttackers().size() < 2) { + combat.removeFromCombat(c1); + } + } + } + } + + // can a player attack with one or more creatures at the moment? + /** + *

+ * canAttack. + *

+ * + * @param p + * a {@link forge.game.player} object. + * @return a boolean. + */ + public static boolean canAttack(Player p) { + List creatures = p.getCreaturesInPlay(); + if (creatures.isEmpty()) { return false; } + + List defenders = p.getOpponents(); + if (defenders.isEmpty()) { return false; } + + boolean foundCreatureThatCantAttackAlone = false; + + for (Card c : creatures) { + if (CombatUtil.canAttack(c)) { + for (Player def : defenders) { + if (CombatUtil.canAttackNextTurn(c, def)) { + if (c.hasKeyword("CARDNAME can't attack or block alone.")) { + //ensure another possible attacker is found + //if the first one found can't attack alone + if (!foundCreatureThatCantAttackAlone) { + foundCreatureThatCantAttackAlone = true; + break; + } + } + return true; + } + } + } + } + return false; + } + + // can a creature attack given the combat state + /** + *

+ * canAttack. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param combat + * a {@link forge.game.combat.Combat} object. + * @return a boolean. + */ + public static boolean canAttack(final Card c, final GameEntity def, final Combat combat) { + int cntAttackers = combat.getAttackers().size(); + final Game game = c.getGame(); + + if (cntAttackers > 0) { + for (final Card card : game.getCardsIn(ZoneType.Battlefield)) { + for (final String keyword : card.getKeyword()) { + if (cntAttackers > 1) { + if (keyword.equals("No more than two creatures can attack each combat.")) { + return false; + } + if (keyword.equals("No more than two creatures can attack you each combat.") && + card.getController().getOpponent().equals(c.getController())) { + return false; + } + } + if (keyword.equals("CARDNAME can only attack alone.") && combat.isAttacking(card)) { + return false; + } + } + } + + if (c.hasKeyword("CARDNAME can only attack alone.")) { + return false; + } + + if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyOneAttackerACombat)) { + return false; + } + } + + if (c.hasKeyword("CARDNAME can't attack or block alone.") && + c.getController().getCreaturesInPlay().size() < 2) { + return false; + } + + if ((cntAttackers > 0 || c.getController().getAttackedWithCreatureThisTurn()) && + game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyOneAttackerATurn)) { + return false; + } + + return CombatUtil.canAttack(c, def); + } + + // can a creature attack at the moment? + /** + *

+ * canAttack. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public static boolean canAttack(final Card c, final GameEntity defender) { + return canAttack(c) && canAttackNextTurn(c, defender); + } + + public static boolean canAttack(final Card c) { + final Game game = c.getGame(); + if (c.isTapped() || c.isPhasedOut() + || (c.hasSickness() && !c.hasKeyword("CARDNAME can attack as though it had haste.")) + || game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) { + return false; + } + return true; + } + + public static boolean canAttackerBeBlockedWithAmount(Card attacker, int amount, Combat combat) { + if( amount == 0 ) + return false; // no block + + List restrictions = Lists.newArrayList(); + for ( String kw : attacker.getKeyword() ) { + if ( kw.startsWith("CantBeBlockedByAmount") ) + restrictions.add(TextUtil.split(kw, ' ', 2)[1]); + } + for ( String res : restrictions ) { + int operand = Integer.parseInt(res.substring(2)); + String operator = res.substring(0,2); + if (Expressions.compare(amount, operator, operand) ) + return false; + } + if (combat != null && attacker.hasKeyword("CARDNAME can't be blocked " + + "unless all creatures defending player controls block it.")) { + Player defender = combat.getDefenderPlayerByAttacker(attacker); + if (amount < defender.getCreaturesInPlay().size()) { + return false; + } + } + + return true; + } + + // can a creature attack if untapped and without summoning sickness? + /** + *

+ * canAttackNextTurn. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public static boolean canAttackNextTurn(final Card c) { + return canAttackNextTurn(c, c.getController().getOpponent()); + } + + // can a creature attack if untapped and without summoning sickness? + /** + *

+ * canAttackNextTurn. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public static boolean canAttackNextTurn(final Card c, final GameEntity defender) { + if (!c.isCreature()) { + return false; + } + + Player defendingPlayer = null; + if (defender instanceof Card) { + defendingPlayer = ((Card) defender).getController(); + } else { + defendingPlayer = (Player) defender; + } + + // CARDNAME can't attack if defending player controls an untapped + // creature with power ... + final int[] powerLimit = { 0 }; + String cantAttackKw = null; + + for( String kw : c.getKeyword()) { + if (kw.startsWith("CARDNAME can't attack if defending player controls an untapped creature with power")) { + cantAttackKw = kw; + } + } + + // The keyword + // "CARDNAME can't attack if defending player controls an untapped creature with power" + // ... is present + if (cantAttackKw != null) { + final String[] asSeparateWords = cantAttackKw.trim().split(" "); + + if (asSeparateWords.length >= 15) { + if (StringUtils.isNumeric(asSeparateWords[12])) { + powerLimit[0] = Integer.parseInt((asSeparateWords[12]).trim()); + + List list = defendingPlayer.getCreaturesInPlay(); + list = CardLists.filter(list, new Predicate() { + @Override + public boolean apply(final Card ct) { + return (ct.isUntapped() + && ((ct.getNetAttack() >= powerLimit[0] && asSeparateWords[14].contains("greater")) + || (ct.getNetAttack() <= powerLimit[0] && asSeparateWords[14].contains("less")))); + } + }); + if (!list.isEmpty()) { + return false; + } + } + } + } // hasKeyword = CARDNAME can't attack if defending player controls an + // untapped creature with power ... + + final List list = defendingPlayer.getCardsIn(ZoneType.Battlefield); + List temp; + for (String keyword : c.getKeyword()) { + if (keyword.equals("CARDNAME can't attack.") || keyword.equals("CARDNAME can't attack or block.")) { + return false; + } else if (keyword.equals("Defender") && !c.hasKeyword("CARDNAME can attack as though it didn't have defender.")) { + return false; + } else if (keyword.equals("CARDNAME can't attack unless defending player controls an Island.")) { + temp = CardLists.getType(list, "Island"); + if (temp.isEmpty()) { + return false; + } + } else if (keyword.equals("CARDNAME can't attack unless defending player controls a Forest.")) { + temp = CardLists.getType(list, "Forest"); + if (temp.isEmpty()) { + return false; + } + } else if (keyword.equals("CARDNAME can't attack unless defending player controls a Swamp.")) { + temp = CardLists.getType(list, "Swamp"); + if (temp.isEmpty()) { + return false; + } + } else if (keyword.equals("CARDNAME can't attack unless defending player controls a Mountain.")) { + temp = CardLists.getType(list, "Mountain"); + if (temp.isEmpty()) { + return false; + } + } else if (keyword.equals("CARDNAME can't attack unless defending player controls a snow land.")) { + temp = CardLists.filter(list, CardPredicates.Presets.SNOW_LANDS); + if (temp.isEmpty()) { + return false; + } + } else if (keyword.equals("CARDNAME can't attack unless defending player controls a blue permanent.")) { + if (!Iterables.any(list, CardPredicates.isColor(MagicColor.BLUE))) + return false; + } else if (keyword.equals("CARDNAME can't attack during extra turns.")) { + if (c.getGame().getPhaseHandler().getPlayerTurn().isPlayingExtraTurn()) + return false; + } else if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) { + final String defined = keyword.split(":")[1]; + final Player player = AbilityUtils.getDefinedPlayers(c, defined, null).get(0); + if (!defender.equals(player)) { + return false; + } + } + } + + // The creature won't untap next turn + if (c.isTapped() && !Untap.canUntap(c)) { + return false; + } + final Game game = c.getGame(); + // CantBeActivated static abilities + for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { + final ArrayList staticAbilities = ca.getStaticAbilities(); + for (final StaticAbility stAb : staticAbilities) { + if (stAb.applyAbility("CantAttack", c, defender)) { + return false; + } + } + } + + return true; + } // canAttack() + + /** + *

+ * checkPropagandaEffects. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param bLast + * a boolean. + */ + public static boolean checkPropagandaEffects(final Game game, final Card c, final Combat combat) { + Cost attackCost = new Cost(ManaCost.ZERO, true); + // Sort abilities to apply them in proper order + for (Card card : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { + final ArrayList staticAbilities = card.getStaticAbilities(); + for (final StaticAbility stAb : staticAbilities) { + Cost additionalCost = stAb.getAttackCost(c, combat.getDefenderByAttacker(c)); + if ( null != additionalCost ) + attackCost.add(additionalCost); + } + } + + boolean isFree = attackCost.getTotalMana().isZero() && attackCost.isOnlyManaCost(); // true if needless to pay + return isFree || c.getController().getController().payManaOptional(c, attackCost, null, "Pay additional cost to declare " + c + " an attacker", ManaPaymentPurpose.DeclareAttacker); + } + + /** + *

+ * This method checks triggered effects of attacking creatures, right before + * defending player declares blockers. + *

+ * @param game + * + * @param c + * a {@link forge.game.card.Card} object. + */ + public static void checkDeclaredAttacker(final Game game, final Card c, final Combat combat) { + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Attacker", c); + final List otherAttackers = combat.getAttackers(); + otherAttackers.remove(c); + runParams.put("OtherAttackers", otherAttackers); + runParams.put("Attacked", combat.getDefenderByAttacker(c)); + game.getTriggerHandler().runTrigger(TriggerType.Attacks, runParams, false); + + // Annihilator: can be copied by Strionic Resonator now + if (!c.getDamageHistory().getCreatureAttackedThisCombat()) { + for (final String key : c.getKeyword()) { + if (!key.startsWith("Annihilator ")) continue; + final String[] k = key.split(" ", 2); + + String sb = "Annihilator - Defending player sacrifices " + k[1] + " permanents."; + String effect = "AB$ Sacrifice | Cost$ 0 | Defined$ DefendingPlayer | SacValid$ Permanent | Amount$ " + k[1]; + + SpellAbility ability = AbilityFactory.getAbility(effect, c); + ability.setActivatingPlayer(c.getController()); + ability.setDescription(sb); + ability.setStackDescription(sb); + ability.setTrigger(true); + + game.getStack().addSimultaneousStackEntry(ability); + + } + } + + c.getDamageHistory().setCreatureAttackedThisCombat(true); + c.getDamageHistory().clearNotAttackedSinceLastUpkeepOf(); + c.getController().setAttackedWithCreatureThisTurn(true); + c.getController().incrementAttackersDeclaredThisTurn(); + } // checkDeclareAttackers + + + public static void handleRampage(final Game game, final Card a, final List blockers) { + for (final String keyword : a.getKeyword()) { + int idx = keyword.indexOf("Rampage "); + if ( idx < 0) + continue; + + final int numBlockers = blockers.size(); + if (numBlockers > 1) { + final int magnitude = Integer.valueOf(keyword.substring(idx + "Rampage ".length())); + CombatUtil.executeRampageAbility(game, a, magnitude, numBlockers); + } + } // end Rampage + } + + public static void handleFlankingKeyword(final Game game, final Card attacker, final List blockers) { + for (Card blocker : blockers) { + if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) { + int flankingMagnitude = 0; + + for (String kw : attacker.getKeyword()) { + if (kw.equals("Flanking")) { + flankingMagnitude++; + } + } + + // Rule 702.23b: If a creature has multiple instances of flanking, each triggers separately. + for( int i = 0; i < flankingMagnitude; i++ ) { + String effect = String.format("AB$ Pump | Cost$ 0 | Defined$ CardUID_%d | NumAtt$ -1 | NumDef$ -1 | ", blocker.getUniqueNumber()); + String desc = String.format("StackDescription$ Flanking (The blocking %s gets -1/-1 until end of turn)", blocker.getName()); + + SpellAbility ability = AbilityFactory.getAbility(effect + desc, attacker); + ability.setActivatingPlayer(attacker.getController()); + ability.setDescription(ability.getStackDescription()); + ability.setTrigger(true); + + game.getStack().addSimultaneousStackEntry(ability); + } + } // flanking + + blocker.addBlockedThisTurn(attacker); + attacker.addBlockedByThisTurn(blocker); + } + } + + /** + * executes Rampage abilities for a given card. + * @param game + * + * @param c + * the card to add rampage bonus to + * @param magnitude + * the magnitude of rampage (ie Rampage 2 means magnitude should + * be 2) + * @param numBlockers + * - the number of creatures blocking this rampaging creature + */ + private static void executeRampageAbility(final Game game, final Card c, final int magnitude, final int numBlockers) { + // numBlockers starts with 1 since it is for every creature beyond the first + for (int i = 1; i < numBlockers; i++) { + String effect = "AB$ Pump | Cost$ 0 | " + c.getUniqueNumber() + " | NumAtt$ " + magnitude + " | NumDef$ " + magnitude + " | "; + String desc = "StackDescription$ Rampage " + magnitude + " (Whenever CARDNAME becomes blocked, it gets +" + magnitude + "/+" + + magnitude + " until end of turn for each creature blocking it beyond the first.)"; + + SpellAbility ability = AbilityFactory.getAbility(effect + desc, c); + ability.setActivatingPlayer(c.getController()); + ability.setDescription(ability.getStackDescription()); + ability.setTrigger(true); + + game.getStack().addSimultaneousStackEntry(ability); + } + } + +} // end class CombatUtil diff --git a/forge-game/src/main/java/forge/game/cost/Cost.java b/forge-gui/src/main/java/forge/game/cost/Cost.java similarity index 96% rename from forge-game/src/main/java/forge/game/cost/Cost.java rename to forge-gui/src/main/java/forge/game/cost/Cost.java index bdc06f8d38c..a7ca934c3b1 100644 --- a/forge-game/src/main/java/forge/game/cost/Cost.java +++ b/forge-gui/src/main/java/forge/game/cost/Cost.java @@ -1,719 +1,719 @@ -/* - * 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 . - */ -package forge.game.cost; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; - -import forge.card.mana.ManaCost; -import forge.card.mana.ManaCostParser; -import forge.game.card.Card; -import forge.game.card.CounterType; -import forge.game.mana.ManaCostBeingPaid; -import forge.game.zone.ZoneType; -import forge.util.TextUtil; - -/** - *

- * Cost class. - *

- * - * @author Forge - * @version $Id: Cost.java 24317 2014-01-17 08:32:39Z Max mtg $ - */ -public class Cost { - private boolean isAbility = true; - private final ArrayList costParts = new ArrayList(); - - private boolean tapCost = false; - - public final boolean hasTapCost() { - return this.tapCost; - } - - public final boolean hasNoManaCost() { - return this.getTotalMana().isZero(); - } - - /** - * Gets the cost parts. - * - * @return the cost parts - */ - public final List getCostParts() { - return this.costParts; - } - - /** - *

- * isOnlyManaCost. - *

- * - * @return a boolean. - */ - public final boolean isOnlyManaCost() { - // Only used by Morph and Equip... why do we need this? - for (final CostPart part : this.costParts) { - if (!(part instanceof CostPartMana)) { - return false; - } - } - - return true; - } - - /** - *

- * getTotalMana. - *

- * - * @return a {@link java.lang.String} object. - */ - public final ManaCost getTotalMana() { - for (final CostPart part : this.costParts) { - if (part instanceof CostPartMana) { - return ((CostPartMana) part).getManaToPay(); - } - } - - return ManaCost.ZERO; - } - - private Cost(int colorlessmana) { - costParts.add(new CostPartMana(ManaCost.get(colorlessmana), null)); - } - - // Parsing Strings - - public Cost(ManaCost cost, final boolean bAbility) { - costParts.add(new CostPartMana(cost, null)); - } - - /** - *

- * Constructor for Cost. - *

- * @param parse - * a {@link java.lang.String} object. - * @param bAbility - * a boolean. - */ - public Cost(String parse, final boolean bAbility) { - this.isAbility = bAbility; - // when adding new costs for cost string, place them here - - boolean xCantBe0 = false; - boolean untapCost = false; - - StringBuilder manaParts = new StringBuilder(); - String[] parts = TextUtil.splitWithParenthesis(parse, ' ', '<', '>'); - - // make this before parse so that classes that need it get data in their constructor - for (String part : parts) { - if (part.equals("T") || part.equals("Tap")) - this.tapCost = true; - if (part.equals("Q") || part.equals("Untap")) - untapCost = true; - } - - CostPartMana parsedMana = null; - for (String part : parts) { - if ("XCantBe0".equals(part)) - xCantBe0 = true; - else { - CostPart cp = parseCostPart(part, tapCost, untapCost); - if (null != cp ) - if (cp instanceof CostPartMana ) { - parsedMana = (CostPartMana) cp; - } else { - this.costParts.add(cp); - } - else - manaParts.append(part).append(" "); - } - - } - - if (parsedMana == null && manaParts.length() > 0) { - parsedMana = new CostPartMana(new ManaCost(new ManaCostParser(manaParts.toString())), xCantBe0 ? "XCantBe0" : null); - } - if (parsedMana != null) { - if(parsedMana.shouldPayLast()) // back from the brink pays mana after 'exile' part is paid - this.costParts.add(parsedMana); - else - this.costParts.add(0, parsedMana); - } - - // inspect parts to set Sac, {T} and {Q} flags - for (int iCp = 0; iCp < costParts.size(); iCp++) { - CostPart cp = costParts.get(iCp); - - // my guess why Q/T are to be first and are followed by mana parts - // is because Q/T are undoable and mana is interactive, so it well be easy to rollback if player refuses to pay - if (cp instanceof CostUntap) { - costParts.remove(iCp); - costParts.add(0, cp); - } - if (cp instanceof CostTap) { - costParts.remove(iCp); - costParts.add(0, cp); - } - } - } - - private static CostPart parseCostPart(String parse, boolean tapCost, boolean untapCost) { - - if (parse.startsWith("Mana<")) { - final String[] splitStr = TextUtil.split(abCostParse(parse, 1)[0], '\\'); - final String restriction = splitStr.length > 1 ? splitStr[1] : null; - return new CostPartMana(new ManaCost(new ManaCostParser(splitStr[0])), restriction); - } - - if (parse.startsWith("tapXType<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostTapType(splitStr[0], splitStr[1], description, tapCost); - } - - if (parse.startsWith("untapYType<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostUntapType(splitStr[0], splitStr[1], description, untapCost); - } - - if (parse.startsWith("SubCounter<")) { - // SubCounter - final String[] splitStr = abCostParse(parse, 5); - final String type = splitStr.length > 2 ? splitStr[2] : "CARDNAME"; - final String description = splitStr.length > 3 ? splitStr[3] : null; - final ZoneType zone = splitStr.length > 4 ? ZoneType.smartValueOf(splitStr[4]) : ZoneType.Battlefield; - - return new CostRemoveCounter(splitStr[0], CounterType.valueOf(splitStr[1]), type, description, zone); - } - - if (parse.startsWith("AddCounter<")) { - // AddCounter - final String[] splitStr = abCostParse(parse, 4); - final String target = splitStr.length > 2 ? splitStr[2] : "CARDNAME"; - final String description = splitStr.length > 3 ? splitStr[3] : null; - return new CostPutCounter(splitStr[0], CounterType.valueOf(splitStr[1]), target, description); - } - - // While no card has "PayLife<2> PayLife<3> there might be a card that - // Changes Cost by adding a Life Payment - if (parse.startsWith("PayLife<")) { - // PayLife - final String[] splitStr = abCostParse(parse, 1); - return new CostPayLife(splitStr[0]); - } - - if (parse.startsWith("GainLife<")) { - // PayLife - final String[] splitStr = abCostParse(parse, 3); - int cnt = splitStr.length > 2 ? "*".equals(splitStr[2]) ? Integer.MAX_VALUE : Integer.parseInt(splitStr[2]) : 1; - return new CostGainLife(splitStr[0], splitStr[1], cnt); - } - - if (parse.startsWith("GainControl<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostGainControl(splitStr[0], splitStr[1], description); - } - - if (parse.startsWith("Unattach<")) { - // Unattach - final String[] splitStr = abCostParse(parse, 2); - final String description = splitStr.length > 1 ? splitStr[1] : null; - return new CostUnattach(splitStr[0], description); - } - - if (parse.startsWith("ChooseCreatureType<")) { - final String[] splitStr = abCostParse(parse, 1); - return new CostChooseCreatureType(splitStr[0]); - } - - if (parse.startsWith("DamageYou<")) { - // Damage - final String[] splitStr = abCostParse(parse, 1); - return new CostDamage(splitStr[0]); - } - - if (parse.startsWith("Mill<")) { - // Mill - final String[] splitStr = abCostParse(parse, 1); - return new CostMill(splitStr[0]); - } - - if (parse.startsWith("FlipCoin<")) { - // FlipCoin - final String[] splitStr = abCostParse(parse, 1); - return new CostFlipCoin(splitStr[0]); - } - - if (parse.startsWith("Discard<")) { - // Discard - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostDiscard(splitStr[0], splitStr[1], description); - } - - if (parse.startsWith("AddMana<")) { - // AddMana - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostAddMana(splitStr[0], splitStr[1], description); - } - - if (parse.startsWith("Sac<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostSacrifice(splitStr[0], splitStr[1], description); - } - - if (parse.startsWith("RemoveAnyCounter<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostRemoveAnyCounter(splitStr[0], splitStr[1], description); - } - - if (parse.startsWith("Exile<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostExile(splitStr[0], splitStr[1], description, ZoneType.Battlefield); - } - - if (parse.startsWith("ExileFromHand<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostExile(splitStr[0], splitStr[1], description, ZoneType.Hand); - } - - if (parse.startsWith("ExileFromGrave<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostExile(splitStr[0], splitStr[1], description, ZoneType.Graveyard); - } - - if (parse.startsWith("ExileFromStack<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostExile(splitStr[0], splitStr[1], description, ZoneType.Stack); - } - - if (parse.startsWith("ExileFromTop<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostExile(splitStr[0], splitStr[1], description, ZoneType.Library); - } - - if (parse.startsWith("ExileSameGrave<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostExile(splitStr[0], splitStr[1], description, ZoneType.Graveyard, true); - } - - if (parse.startsWith("Return<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostReturn(splitStr[0], splitStr[1], description); - } - - if (parse.startsWith("Reveal<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostReveal(splitStr[0], splitStr[1], description); - } - - if (parse.startsWith("ExiledMoveToGrave<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostExiledMoveToGrave(splitStr[0], splitStr[1], description); - } - - if (parse.startsWith("Draw<")) { - final String[] splitStr = abCostParse(parse, 2); - return new CostDraw(splitStr[0], splitStr[1]); - } - - if (parse.startsWith("PutCardToLibFromHand<")) { - final String[] splitStr = abCostParse(parse, 4); - final String description = splitStr.length > 3 ? splitStr[3] : null; - return new CostPutCardToLib(splitStr[0], splitStr[1], splitStr[2], description, ZoneType.Hand); - } - - if (parse.startsWith("PutCardToLibFromGrave<")) { - final String[] splitStr = abCostParse(parse, 4); - final String description = splitStr.length > 3 ? splitStr[3] : null; - return new CostPutCardToLib(splitStr[0], splitStr[1], splitStr[2], description, ZoneType.Graveyard); - } - - if (parse.startsWith("PutCardToLibFromSameGrave<")) { - final String[] splitStr = abCostParse(parse, 4); - final String description = splitStr.length > 3 ? splitStr[3] : null; - return new CostPutCardToLib(splitStr[0], splitStr[1], splitStr[2], description, ZoneType.Graveyard, true); - } - - // These won't show up with multiples - if (parse.equals("Untap") || parse.equals("Q")) { - return new CostUntap(); - } - - if (parse.equals("T")) { - return new CostTap(); - } - return null; - } - - /** - *

- * abCostParse. - *

- * - * @param parse - * a {@link java.lang.String} object. - * @param subkey - * a {@link java.lang.String} object. - * @param numParse - * a int. - * @return an array of {@link java.lang.String} objects. - */ - private static String[] abCostParse(final String parse, final int numParse) { - final int startPos = 1 + parse.indexOf("<"); - final int endPos = parse.indexOf(">", startPos); - String str = parse.substring(startPos, endPos); - final String[] splitStr = TextUtil.split(str, '/', numParse); - return splitStr; - } - - public final Cost copyWithNoMana() { - Cost toRet = new Cost(0); - toRet.isAbility = this.isAbility; - for (CostPart cp : this.costParts) { - if (!(cp instanceof CostPartMana)) - toRet.costParts.add(cp); - } - return toRet; - } - - public final CostPartMana getCostMana() { - for (final CostPart part : this.costParts) { - if (part instanceof CostPartMana) { - return (CostPartMana) part; - } - } - return null; - } - - /** - *

- * refundPaidCost. - *

- * - * @param source - * a {@link forge.game.card.Card} object. - */ - public final void refundPaidCost(final Card source) { - // prereq: isUndoable is called first - for (final CostPart part : this.costParts) { - part.refund(source); - } - } - - /** - *

- * isUndoable. - *

- * - * @return a boolean. - */ - public final boolean isUndoable() { - for (final CostPart part : this.costParts) { - if (!part.isUndoable()) { - return false; - } - } - - return true; - } - - /** - *

- * isReusuableResource. - *

- * - * @return a boolean. - */ - public final boolean isReusuableResource() { - for (final CostPart part : this.costParts) { - if (!part.isReusable()) { - return false; - } - } - - return this.isAbility; - } - - /** - *

- * isRenewableResource. - *

- * - * @return a boolean. - */ - public final boolean isRenewableResource() { - for (final CostPart part : this.costParts) { - if (!part.isRenewable()) { - return false; - } - } - - return this.isAbility; - } - - /** - *

- * toString. - *

- * - * @return a {@link java.lang.String} object. - */ - @Override - public final String toString() { - if (this.isAbility) { - return this.abilityToString(); - } else { - return this.spellToString(true); - } - } - - // maybe add a conversion method that turns the amounts into words 1=a(n), - // 2=two etc. - - /** - *

- * toStringAlt. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String toStringAlt() { - return this.spellToString(false); - } - - /** - *

- * toSimpleString. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String toSimpleString() { - final StringBuilder cost = new StringBuilder(); - boolean first = true; - for (final CostPart part : this.costParts) { - if (!first) { - cost.append(" and "); - } - cost.append(part.toString()); - first = false; - } - return cost.toString(); - } - - /** - *

- * spellToString. - *

- * - * @param bFlag - * a boolean. - * @return a {@link java.lang.String} object. - */ - private String spellToString(final boolean bFlag) { - final StringBuilder cost = new StringBuilder(); - boolean first = true; - - if (bFlag) { - cost.append("As an additional cost to cast selected card, "); - } else { - // usually no additional mana cost for spells - // only three Alliances cards have additional mana costs, but they - // are basically kicker/multikicker - /* - * if (!getTotalMana().equals("0")) { - * cost.append("pay ").append(getTotalMana()); first = false; } - */ - } - - for (final CostPart part : this.costParts) { - if (part instanceof CostPartMana) { - continue; - } - if (!first) { - cost.append(" and "); - } - cost.append(part.toString()); - first = false; - } - - if (first) { - return ""; - } - - if (bFlag) { - cost.append(".").append("\n"); - } - - return cost.toString(); - } - - /** - *

- * abilityToString. - *

- * - * @return a {@link java.lang.String} object. - */ - private String abilityToString() { - final StringBuilder cost = new StringBuilder(); - boolean first = true; - - for (final CostPart part : this.costParts) { - boolean append = true; - if (!first) { - if (part instanceof CostPartMana) { - cost.insert(0, ", ").insert(0, part.toString()); - append = false; - } else { - cost.append(", "); - } - } - if (append) { - cost.append(part.toString()); - } - first = false; - } - - if (first) { - cost.append("0"); - } - - cost.append(": "); - return cost.toString(); - } - - // TODO: If a Cost needs to pay more than 10 of something, fill this array - // as appropriate - /** - * Constant. - * numNames="{zero, a, two, three, four, five, six, "{trunked} - */ - private static final String[] NUM_NAMES = { "zero", "a", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten" }; - /** Constant vowelPattern. */ - private static final Pattern VOWEL_PATTERN = Pattern.compile("^[aeiou]", Pattern.CASE_INSENSITIVE); - - /** - * Convert amount type to words. - * - * @param i - * the i - * @param amount - * the amount - * @param type - * the type - * @return the string - */ - public static String convertAmountTypeToWords(final Integer i, final String amount, final String type) { - if (i == null) { - return Cost.convertAmountTypeToWords(amount, type); - } - - return Cost.convertIntAndTypeToWords(i.intValue(), type); - } - - /** - *

- * convertIntAndTypeToWords. - *

- * - * @param i - * a int. - * @param type - * a {@link java.lang.String} object. - * @return a {@link java.lang.String} object. - */ - public static String convertIntAndTypeToWords(final int i, final String type) { - final StringBuilder sb = new StringBuilder(); - - if (i >= Cost.NUM_NAMES.length) { - sb.append(i); - } else if ((1 == i) && Cost.VOWEL_PATTERN.matcher(type).find()) { - sb.append("an"); - } else { - sb.append(Cost.NUM_NAMES[i]); - } - - sb.append(" "); - sb.append(type); - if (1 != i) { - sb.append("s"); - } - - return sb.toString(); - } - - /** - * Convert amount type to words. - * - * @param amount - * the amount - * @param type - * the type - * @return the string - */ - public static String convertAmountTypeToWords(final String amount, final String type) { - final StringBuilder sb = new StringBuilder(); - - sb.append(amount); - sb.append(" "); - sb.append(type); - - return sb.toString(); - } - - public Cost add(Cost cost1) { - CostPartMana costPart2 = this.getCostMana(); - for (final CostPart part : cost1.getCostParts()) { - if (part instanceof CostPartMana && costPart2 != null) { - ManaCostBeingPaid oldManaCost = new ManaCostBeingPaid(((CostPartMana) part).getMana()); - oldManaCost.combineManaCost(costPart2.getMana()); - String r2 = costPart2.getRestiction(); - String r1 = ((CostPartMana) part).getRestiction(); - String r = r1 == null ? r2 : ( r2 == null ? r1 : r1 + "." + r2); - getCostParts().remove(costPart2); - getCostParts().add(0, new CostPartMana(oldManaCost.toManaCost(), r)); - } else { - getCostParts().add(part); - } - } - return this; - } - - public static final Cost Zero = new Cost(0); -} +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.game.cost; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostParser; +import forge.game.card.Card; +import forge.game.card.CounterType; +import forge.game.mana.ManaCostBeingPaid; +import forge.game.zone.ZoneType; +import forge.util.TextUtil; + +/** + *

+ * Cost class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class Cost { + private boolean isAbility = true; + private final ArrayList costParts = new ArrayList(); + + private boolean tapCost = false; + + public final boolean hasTapCost() { + return this.tapCost; + } + + public final boolean hasNoManaCost() { + return this.getTotalMana().isZero(); + } + + /** + * Gets the cost parts. + * + * @return the cost parts + */ + public final List getCostParts() { + return this.costParts; + } + + /** + *

+ * isOnlyManaCost. + *

+ * + * @return a boolean. + */ + public final boolean isOnlyManaCost() { + // Only used by Morph and Equip... why do we need this? + for (final CostPart part : this.costParts) { + if (!(part instanceof CostPartMana)) { + return false; + } + } + + return true; + } + + /** + *

+ * getTotalMana. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final ManaCost getTotalMana() { + for (final CostPart part : this.costParts) { + if (part instanceof CostPartMana) { + return ((CostPartMana) part).getManaToPay(); + } + } + + return ManaCost.ZERO; + } + + private Cost(int colorlessmana) { + costParts.add(new CostPartMana(ManaCost.get(colorlessmana), null)); + } + + // Parsing Strings + + public Cost(ManaCost cost, final boolean bAbility) { + costParts.add(new CostPartMana(cost, null)); + } + + /** + *

+ * Constructor for Cost. + *

+ * @param parse + * a {@link java.lang.String} object. + * @param bAbility + * a boolean. + */ + public Cost(String parse, final boolean bAbility) { + this.isAbility = bAbility; + // when adding new costs for cost string, place them here + + boolean xCantBe0 = false; + boolean untapCost = false; + + StringBuilder manaParts = new StringBuilder(); + String[] parts = TextUtil.splitWithParenthesis(parse, ' ', '<', '>'); + + // make this before parse so that classes that need it get data in their constructor + for (String part : parts) { + if (part.equals("T") || part.equals("Tap")) + this.tapCost = true; + if (part.equals("Q") || part.equals("Untap")) + untapCost = true; + } + + CostPartMana parsedMana = null; + for (String part : parts) { + if ("XCantBe0".equals(part)) + xCantBe0 = true; + else { + CostPart cp = parseCostPart(part, tapCost, untapCost); + if (null != cp ) + if (cp instanceof CostPartMana ) { + parsedMana = (CostPartMana) cp; + } else { + this.costParts.add(cp); + } + else + manaParts.append(part).append(" "); + } + + } + + if (parsedMana == null && manaParts.length() > 0) { + parsedMana = new CostPartMana(new ManaCost(new ManaCostParser(manaParts.toString())), xCantBe0 ? "XCantBe0" : null); + } + if (parsedMana != null) { + if(parsedMana.shouldPayLast()) // back from the brink pays mana after 'exile' part is paid + this.costParts.add(parsedMana); + else + this.costParts.add(0, parsedMana); + } + + // inspect parts to set Sac, {T} and {Q} flags + for (int iCp = 0; iCp < costParts.size(); iCp++) { + CostPart cp = costParts.get(iCp); + + // my guess why Q/T are to be first and are followed by mana parts + // is because Q/T are undoable and mana is interactive, so it well be easy to rollback if player refuses to pay + if (cp instanceof CostUntap) { + costParts.remove(iCp); + costParts.add(0, cp); + } + if (cp instanceof CostTap) { + costParts.remove(iCp); + costParts.add(0, cp); + } + } + } + + private static CostPart parseCostPart(String parse, boolean tapCost, boolean untapCost) { + + if (parse.startsWith("Mana<")) { + final String[] splitStr = TextUtil.split(abCostParse(parse, 1)[0], '\\'); + final String restriction = splitStr.length > 1 ? splitStr[1] : null; + return new CostPartMana(new ManaCost(new ManaCostParser(splitStr[0])), restriction); + } + + if (parse.startsWith("tapXType<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostTapType(splitStr[0], splitStr[1], description, tapCost); + } + + if (parse.startsWith("untapYType<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostUntapType(splitStr[0], splitStr[1], description, untapCost); + } + + if (parse.startsWith("SubCounter<")) { + // SubCounter + final String[] splitStr = abCostParse(parse, 5); + final String type = splitStr.length > 2 ? splitStr[2] : "CARDNAME"; + final String description = splitStr.length > 3 ? splitStr[3] : null; + final ZoneType zone = splitStr.length > 4 ? ZoneType.smartValueOf(splitStr[4]) : ZoneType.Battlefield; + + return new CostRemoveCounter(splitStr[0], CounterType.valueOf(splitStr[1]), type, description, zone); + } + + if (parse.startsWith("AddCounter<")) { + // AddCounter + final String[] splitStr = abCostParse(parse, 4); + final String target = splitStr.length > 2 ? splitStr[2] : "CARDNAME"; + final String description = splitStr.length > 3 ? splitStr[3] : null; + return new CostPutCounter(splitStr[0], CounterType.valueOf(splitStr[1]), target, description); + } + + // While no card has "PayLife<2> PayLife<3> there might be a card that + // Changes Cost by adding a Life Payment + if (parse.startsWith("PayLife<")) { + // PayLife + final String[] splitStr = abCostParse(parse, 1); + return new CostPayLife(splitStr[0]); + } + + if (parse.startsWith("GainLife<")) { + // PayLife + final String[] splitStr = abCostParse(parse, 3); + int cnt = splitStr.length > 2 ? "*".equals(splitStr[2]) ? Integer.MAX_VALUE : Integer.parseInt(splitStr[2]) : 1; + return new CostGainLife(splitStr[0], splitStr[1], cnt); + } + + if (parse.startsWith("GainControl<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostGainControl(splitStr[0], splitStr[1], description); + } + + if (parse.startsWith("Unattach<")) { + // Unattach + final String[] splitStr = abCostParse(parse, 2); + final String description = splitStr.length > 1 ? splitStr[1] : null; + return new CostUnattach(splitStr[0], description); + } + + if (parse.startsWith("ChooseCreatureType<")) { + final String[] splitStr = abCostParse(parse, 1); + return new CostChooseCreatureType(splitStr[0]); + } + + if (parse.startsWith("DamageYou<")) { + // Damage + final String[] splitStr = abCostParse(parse, 1); + return new CostDamage(splitStr[0]); + } + + if (parse.startsWith("Mill<")) { + // Mill + final String[] splitStr = abCostParse(parse, 1); + return new CostMill(splitStr[0]); + } + + if (parse.startsWith("FlipCoin<")) { + // FlipCoin + final String[] splitStr = abCostParse(parse, 1); + return new CostFlipCoin(splitStr[0]); + } + + if (parse.startsWith("Discard<")) { + // Discard + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostDiscard(splitStr[0], splitStr[1], description); + } + + if (parse.startsWith("AddMana<")) { + // AddMana + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostAddMana(splitStr[0], splitStr[1], description); + } + + if (parse.startsWith("Sac<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostSacrifice(splitStr[0], splitStr[1], description); + } + + if (parse.startsWith("RemoveAnyCounter<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostRemoveAnyCounter(splitStr[0], splitStr[1], description); + } + + if (parse.startsWith("Exile<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostExile(splitStr[0], splitStr[1], description, ZoneType.Battlefield); + } + + if (parse.startsWith("ExileFromHand<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostExile(splitStr[0], splitStr[1], description, ZoneType.Hand); + } + + if (parse.startsWith("ExileFromGrave<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostExile(splitStr[0], splitStr[1], description, ZoneType.Graveyard); + } + + if (parse.startsWith("ExileFromStack<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostExile(splitStr[0], splitStr[1], description, ZoneType.Stack); + } + + if (parse.startsWith("ExileFromTop<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostExile(splitStr[0], splitStr[1], description, ZoneType.Library); + } + + if (parse.startsWith("ExileSameGrave<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostExile(splitStr[0], splitStr[1], description, ZoneType.Graveyard, true); + } + + if (parse.startsWith("Return<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostReturn(splitStr[0], splitStr[1], description); + } + + if (parse.startsWith("Reveal<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostReveal(splitStr[0], splitStr[1], description); + } + + if (parse.startsWith("ExiledMoveToGrave<")) { + final String[] splitStr = abCostParse(parse, 3); + final String description = splitStr.length > 2 ? splitStr[2] : null; + return new CostExiledMoveToGrave(splitStr[0], splitStr[1], description); + } + + if (parse.startsWith("Draw<")) { + final String[] splitStr = abCostParse(parse, 2); + return new CostDraw(splitStr[0], splitStr[1]); + } + + if (parse.startsWith("PutCardToLibFromHand<")) { + final String[] splitStr = abCostParse(parse, 4); + final String description = splitStr.length > 3 ? splitStr[3] : null; + return new CostPutCardToLib(splitStr[0], splitStr[1], splitStr[2], description, ZoneType.Hand); + } + + if (parse.startsWith("PutCardToLibFromGrave<")) { + final String[] splitStr = abCostParse(parse, 4); + final String description = splitStr.length > 3 ? splitStr[3] : null; + return new CostPutCardToLib(splitStr[0], splitStr[1], splitStr[2], description, ZoneType.Graveyard); + } + + if (parse.startsWith("PutCardToLibFromSameGrave<")) { + final String[] splitStr = abCostParse(parse, 4); + final String description = splitStr.length > 3 ? splitStr[3] : null; + return new CostPutCardToLib(splitStr[0], splitStr[1], splitStr[2], description, ZoneType.Graveyard, true); + } + + // These won't show up with multiples + if (parse.equals("Untap") || parse.equals("Q")) { + return new CostUntap(); + } + + if (parse.equals("T")) { + return new CostTap(); + } + return null; + } + + /** + *

+ * abCostParse. + *

+ * + * @param parse + * a {@link java.lang.String} object. + * @param subkey + * a {@link java.lang.String} object. + * @param numParse + * a int. + * @return an array of {@link java.lang.String} objects. + */ + private static String[] abCostParse(final String parse, final int numParse) { + final int startPos = 1 + parse.indexOf("<"); + final int endPos = parse.indexOf(">", startPos); + String str = parse.substring(startPos, endPos); + final String[] splitStr = TextUtil.split(str, '/', numParse); + return splitStr; + } + + public final Cost copyWithNoMana() { + Cost toRet = new Cost(0); + toRet.isAbility = this.isAbility; + for (CostPart cp : this.costParts) { + if (!(cp instanceof CostPartMana)) + toRet.costParts.add(cp); + } + return toRet; + } + + public final CostPartMana getCostMana() { + for (final CostPart part : this.costParts) { + if (part instanceof CostPartMana) { + return (CostPartMana) part; + } + } + return null; + } + + /** + *

+ * refundPaidCost. + *

+ * + * @param source + * a {@link forge.game.card.Card} object. + */ + public final void refundPaidCost(final Card source) { + // prereq: isUndoable is called first + for (final CostPart part : this.costParts) { + part.refund(source); + } + } + + /** + *

+ * isUndoable. + *

+ * + * @return a boolean. + */ + public final boolean isUndoable() { + for (final CostPart part : this.costParts) { + if (!part.isUndoable()) { + return false; + } + } + + return true; + } + + /** + *

+ * isReusuableResource. + *

+ * + * @return a boolean. + */ + public final boolean isReusuableResource() { + for (final CostPart part : this.costParts) { + if (!part.isReusable()) { + return false; + } + } + + return this.isAbility; + } + + /** + *

+ * isRenewableResource. + *

+ * + * @return a boolean. + */ + public final boolean isRenewableResource() { + for (final CostPart part : this.costParts) { + if (!part.isRenewable()) { + return false; + } + } + + return this.isAbility; + } + + /** + *

+ * toString. + *

+ * + * @return a {@link java.lang.String} object. + */ + @Override + public final String toString() { + if (this.isAbility) { + return this.abilityToString(); + } else { + return this.spellToString(true); + } + } + + // maybe add a conversion method that turns the amounts into words 1=a(n), + // 2=two etc. + + /** + *

+ * toStringAlt. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String toStringAlt() { + return this.spellToString(false); + } + + /** + *

+ * toSimpleString. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String toSimpleString() { + final StringBuilder cost = new StringBuilder(); + boolean first = true; + for (final CostPart part : this.costParts) { + if (!first) { + cost.append(" and "); + } + cost.append(part.toString()); + first = false; + } + return cost.toString(); + } + + /** + *

+ * spellToString. + *

+ * + * @param bFlag + * a boolean. + * @return a {@link java.lang.String} object. + */ + private String spellToString(final boolean bFlag) { + final StringBuilder cost = new StringBuilder(); + boolean first = true; + + if (bFlag) { + cost.append("As an additional cost to cast selected card, "); + } else { + // usually no additional mana cost for spells + // only three Alliances cards have additional mana costs, but they + // are basically kicker/multikicker + /* + * if (!getTotalMana().equals("0")) { + * cost.append("pay ").append(getTotalMana()); first = false; } + */ + } + + for (final CostPart part : this.costParts) { + if (part instanceof CostPartMana) { + continue; + } + if (!first) { + cost.append(" and "); + } + cost.append(part.toString()); + first = false; + } + + if (first) { + return ""; + } + + if (bFlag) { + cost.append(".").append("\n"); + } + + return cost.toString(); + } + + /** + *

+ * abilityToString. + *

+ * + * @return a {@link java.lang.String} object. + */ + private String abilityToString() { + final StringBuilder cost = new StringBuilder(); + boolean first = true; + + for (final CostPart part : this.costParts) { + boolean append = true; + if (!first) { + if (part instanceof CostPartMana) { + cost.insert(0, ", ").insert(0, part.toString()); + append = false; + } else { + cost.append(", "); + } + } + if (append) { + cost.append(part.toString()); + } + first = false; + } + + if (first) { + cost.append("0"); + } + + cost.append(": "); + return cost.toString(); + } + + // TODO: If a Cost needs to pay more than 10 of something, fill this array + // as appropriate + /** + * Constant. + * numNames="{zero, a, two, three, four, five, six, "{trunked} + */ + private static final String[] NUM_NAMES = { "zero", "a", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten" }; + /** Constant vowelPattern. */ + private static final Pattern VOWEL_PATTERN = Pattern.compile("^[aeiou]", Pattern.CASE_INSENSITIVE); + + /** + * Convert amount type to words. + * + * @param i + * the i + * @param amount + * the amount + * @param type + * the type + * @return the string + */ + public static String convertAmountTypeToWords(final Integer i, final String amount, final String type) { + if (i == null) { + return Cost.convertAmountTypeToWords(amount, type); + } + + return Cost.convertIntAndTypeToWords(i.intValue(), type); + } + + /** + *

+ * convertIntAndTypeToWords. + *

+ * + * @param i + * a int. + * @param type + * a {@link java.lang.String} object. + * @return a {@link java.lang.String} object. + */ + public static String convertIntAndTypeToWords(final int i, final String type) { + final StringBuilder sb = new StringBuilder(); + + if (i >= Cost.NUM_NAMES.length) { + sb.append(i); + } else if ((1 == i) && Cost.VOWEL_PATTERN.matcher(type).find()) { + sb.append("an"); + } else { + sb.append(Cost.NUM_NAMES[i]); + } + + sb.append(" "); + sb.append(type); + if (1 != i) { + sb.append("s"); + } + + return sb.toString(); + } + + /** + * Convert amount type to words. + * + * @param amount + * the amount + * @param type + * the type + * @return the string + */ + public static String convertAmountTypeToWords(final String amount, final String type) { + final StringBuilder sb = new StringBuilder(); + + sb.append(amount); + sb.append(" "); + sb.append(type); + + return sb.toString(); + } + + public Cost add(Cost cost1) { + CostPartMana costPart2 = this.getCostMana(); + for (final CostPart part : cost1.getCostParts()) { + if (part instanceof CostPartMana && costPart2 != null) { + ManaCostBeingPaid oldManaCost = new ManaCostBeingPaid(((CostPartMana) part).getMana()); + oldManaCost.combineManaCost(costPart2.getMana()); + String r2 = costPart2.getRestiction(); + String r1 = ((CostPartMana) part).getRestiction(); + String r = r1 == null ? r2 : ( r2 == null ? r1 : r1 + "." + r2); + getCostParts().remove(costPart2); + getCostParts().add(0, new CostPartMana(oldManaCost.toManaCost(), r)); + } else { + getCostParts().add(part); + } + } + return this; + } + + public static final Cost Zero = new Cost(0); +} diff --git a/forge-game/src/main/java/forge/game/cost/CostAddMana.java b/forge-gui/src/main/java/forge/game/cost/CostAddMana.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostAddMana.java rename to forge-gui/src/main/java/forge/game/cost/CostAddMana.java diff --git a/forge-game/src/main/java/forge/game/cost/CostChooseCreatureType.java b/forge-gui/src/main/java/forge/game/cost/CostChooseCreatureType.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostChooseCreatureType.java rename to forge-gui/src/main/java/forge/game/cost/CostChooseCreatureType.java diff --git a/forge-game/src/main/java/forge/game/cost/CostDamage.java b/forge-gui/src/main/java/forge/game/cost/CostDamage.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostDamage.java rename to forge-gui/src/main/java/forge/game/cost/CostDamage.java diff --git a/forge-game/src/main/java/forge/game/cost/CostDecisionMakerBase.java b/forge-gui/src/main/java/forge/game/cost/CostDecisionMakerBase.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostDecisionMakerBase.java rename to forge-gui/src/main/java/forge/game/cost/CostDecisionMakerBase.java diff --git a/forge-game/src/main/java/forge/game/cost/CostDiscard.java b/forge-gui/src/main/java/forge/game/cost/CostDiscard.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostDiscard.java rename to forge-gui/src/main/java/forge/game/cost/CostDiscard.java diff --git a/forge-game/src/main/java/forge/game/cost/CostDraw.java b/forge-gui/src/main/java/forge/game/cost/CostDraw.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostDraw.java rename to forge-gui/src/main/java/forge/game/cost/CostDraw.java diff --git a/forge-game/src/main/java/forge/game/cost/CostExile.java b/forge-gui/src/main/java/forge/game/cost/CostExile.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostExile.java rename to forge-gui/src/main/java/forge/game/cost/CostExile.java diff --git a/forge-game/src/main/java/forge/game/cost/CostExiledMoveToGrave.java b/forge-gui/src/main/java/forge/game/cost/CostExiledMoveToGrave.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostExiledMoveToGrave.java rename to forge-gui/src/main/java/forge/game/cost/CostExiledMoveToGrave.java diff --git a/forge-game/src/main/java/forge/game/cost/CostFlipCoin.java b/forge-gui/src/main/java/forge/game/cost/CostFlipCoin.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostFlipCoin.java rename to forge-gui/src/main/java/forge/game/cost/CostFlipCoin.java diff --git a/forge-game/src/main/java/forge/game/cost/CostGainControl.java b/forge-gui/src/main/java/forge/game/cost/CostGainControl.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostGainControl.java rename to forge-gui/src/main/java/forge/game/cost/CostGainControl.java diff --git a/forge-game/src/main/java/forge/game/cost/CostGainLife.java b/forge-gui/src/main/java/forge/game/cost/CostGainLife.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostGainLife.java rename to forge-gui/src/main/java/forge/game/cost/CostGainLife.java diff --git a/forge-game/src/main/java/forge/game/cost/CostMill.java b/forge-gui/src/main/java/forge/game/cost/CostMill.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostMill.java rename to forge-gui/src/main/java/forge/game/cost/CostMill.java diff --git a/forge-game/src/main/java/forge/game/cost/CostPart.java b/forge-gui/src/main/java/forge/game/cost/CostPart.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostPart.java rename to forge-gui/src/main/java/forge/game/cost/CostPart.java diff --git a/forge-game/src/main/java/forge/game/cost/CostPartMana.java b/forge-gui/src/main/java/forge/game/cost/CostPartMana.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostPartMana.java rename to forge-gui/src/main/java/forge/game/cost/CostPartMana.java diff --git a/forge-game/src/main/java/forge/game/cost/CostPartWithList.java b/forge-gui/src/main/java/forge/game/cost/CostPartWithList.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostPartWithList.java rename to forge-gui/src/main/java/forge/game/cost/CostPartWithList.java diff --git a/forge-game/src/main/java/forge/game/cost/CostPayLife.java b/forge-gui/src/main/java/forge/game/cost/CostPayLife.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostPayLife.java rename to forge-gui/src/main/java/forge/game/cost/CostPayLife.java diff --git a/forge-game/src/main/java/forge/game/cost/CostPayment.java b/forge-gui/src/main/java/forge/game/cost/CostPayment.java similarity index 95% rename from forge-game/src/main/java/forge/game/cost/CostPayment.java rename to forge-gui/src/main/java/forge/game/cost/CostPayment.java index 290c7ac4801..1250da7bfc6 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPayment.java +++ b/forge-gui/src/main/java/forge/game/cost/CostPayment.java @@ -1,188 +1,188 @@ -/* - * 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 . - */ -package forge.game.cost; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - - -import forge.game.card.Card; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; - -/** - *

- * Cost_Payment class. - *

- * - * @author Forge - * @version $Id: CostPayment.java 24317 2014-01-17 08:32:39Z Max mtg $ - */ -public class CostPayment { - private final Cost cost; - private final SpellAbility ability; - private final ArrayList paidCostParts = new ArrayList(); - - /** - *

- * Getter for the field cost. - *

- * - * @return a {@link forge.game.cost.Cost} object. - */ - public final Cost getCost() { - return this.cost; - } - - /** - *

- * Constructor for Cost_Payment. - *

- * - * @param cost - * a {@link forge.game.cost.Cost} object. - * @param abil - * a {@link forge.game.spellability.SpellAbility} object. - */ - public CostPayment(final Cost cost, final SpellAbility abil) { - this.cost = cost; - this.ability = abil; - } - - /** - *

- * canPayAdditionalCosts. - *

- * - * @param cost - * a {@link forge.game.cost.Cost} object. - * @param ability - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean. - */ - public static boolean canPayAdditionalCosts(final Cost cost, final SpellAbility ability) { - if (cost == null) { - return true; - } - - final Card card = ability.getSourceCard(); - - Player activator = ability.getActivatingPlayer(); - if (activator == null) { - activator = card.getController(); - } - - for (final CostPart part : cost.getCostParts()) { - if (!part.canPay(ability)) { - return false; - } - } - - return true; - } - - /** - *

- * isAllPaid. - *

- * - * @return a boolean. - */ - public final boolean isFullyPaid() { - for (final CostPart part : this.cost.getCostParts()) { - if (!this.paidCostParts.contains(part)) { - return false; - } - } - - return true; - } - - /** - *

- * cancelPayment. - *

- */ - public final void refundPayment() { - Card sourceCard = this.ability.getSourceCard(); - for (final CostPart part : this.paidCostParts) { - if (part.isUndoable()) { - part.refund(sourceCard); - } - } - - // Move this to CostMana - this.ability.getActivatingPlayer().getManaPool().refundManaPaid(this.ability); - } - - public boolean payCost(final CostDecisionMakerBase decisionMaker) { - - for (final CostPart part : this.cost.getCostParts()) { - PaymentDecision pd = part.accept(decisionMaker); - - if ( null == pd || !part.payAsDecided(decisionMaker.getPlayer(), pd, ability)) - return false; - - // abilities care what was used to pay for them - if( part instanceof CostPartWithList ) - ((CostPartWithList) part).reportPaidCardsTo(ability); - - this.paidCostParts.add(part); - } - - // this clears lists used for undo. - for (final CostPart part1 : this.paidCostParts) { - if (part1 instanceof CostPartWithList) { - ((CostPartWithList) part1).resetList(); - } - } - return true; - } - - public final boolean payComputerCosts(final CostDecisionMakerBase decisionMaker) { - // Just in case it wasn't set, but honestly it shouldn't have gotten - // here without being set - this.ability.setActivatingPlayer(decisionMaker.getPlayer()); - - Map, PaymentDecision> decisions = new HashMap, PaymentDecision>(); - - // Set all of the decisions before attempting to pay anything - for (final CostPart part : this.cost.getCostParts()) { - PaymentDecision decision = part.accept(decisionMaker); - if (null == decision) return false; - - if (decisionMaker.paysRightAfterDecision() && !part.payAsDecided(decisionMaker.getPlayer(), decision, ability)) - return false; - - decisions.put(part.getClass(), decision); - } - - for (final CostPart part : this.cost.getCostParts()) { - if (!part.payAsDecided(decisionMaker.getPlayer(), decisions.get(part.getClass()), this.ability)) { - return false; - } - // abilities care what was used to pay for them - if( part instanceof CostPartWithList ) { - ((CostPartWithList) part).reportPaidCardsTo(ability); - ((CostPartWithList) part).resetList(); - } - } - 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 . + */ +package forge.game.cost; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + + +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Cost_Payment class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class CostPayment { + private final Cost cost; + private final SpellAbility ability; + private final ArrayList paidCostParts = new ArrayList(); + + /** + *

+ * Getter for the field cost. + *

+ * + * @return a {@link forge.game.cost.Cost} object. + */ + public final Cost getCost() { + return this.cost; + } + + /** + *

+ * Constructor for Cost_Payment. + *

+ * + * @param cost + * a {@link forge.game.cost.Cost} object. + * @param abil + * a {@link forge.game.spellability.SpellAbility} object. + */ + public CostPayment(final Cost cost, final SpellAbility abil) { + this.cost = cost; + this.ability = abil; + } + + /** + *

+ * canPayAdditionalCosts. + *

+ * + * @param cost + * a {@link forge.game.cost.Cost} object. + * @param ability + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean. + */ + public static boolean canPayAdditionalCosts(final Cost cost, final SpellAbility ability) { + if (cost == null) { + return true; + } + + final Card card = ability.getSourceCard(); + + Player activator = ability.getActivatingPlayer(); + if (activator == null) { + activator = card.getController(); + } + + for (final CostPart part : cost.getCostParts()) { + if (!part.canPay(ability)) { + return false; + } + } + + return true; + } + + /** + *

+ * isAllPaid. + *

+ * + * @return a boolean. + */ + public final boolean isFullyPaid() { + for (final CostPart part : this.cost.getCostParts()) { + if (!this.paidCostParts.contains(part)) { + return false; + } + } + + return true; + } + + /** + *

+ * cancelPayment. + *

+ */ + public final void refundPayment() { + Card sourceCard = this.ability.getSourceCard(); + for (final CostPart part : this.paidCostParts) { + if (part.isUndoable()) { + part.refund(sourceCard); + } + } + + // Move this to CostMana + this.ability.getActivatingPlayer().getManaPool().refundManaPaid(this.ability); + } + + public boolean payCost(final CostDecisionMakerBase decisionMaker) { + + for (final CostPart part : this.cost.getCostParts()) { + PaymentDecision pd = part.accept(decisionMaker); + + if ( null == pd || !part.payAsDecided(decisionMaker.getPlayer(), pd, ability)) + return false; + + // abilities care what was used to pay for them + if( part instanceof CostPartWithList ) + ((CostPartWithList) part).reportPaidCardsTo(ability); + + this.paidCostParts.add(part); + } + + // this clears lists used for undo. + for (final CostPart part1 : this.paidCostParts) { + if (part1 instanceof CostPartWithList) { + ((CostPartWithList) part1).resetList(); + } + } + return true; + } + + public final boolean payComputerCosts(final CostDecisionMakerBase decisionMaker) { + // Just in case it wasn't set, but honestly it shouldn't have gotten + // here without being set + this.ability.setActivatingPlayer(decisionMaker.getPlayer()); + + Map, PaymentDecision> decisions = new HashMap, PaymentDecision>(); + + // Set all of the decisions before attempting to pay anything + for (final CostPart part : this.cost.getCostParts()) { + PaymentDecision decision = part.accept(decisionMaker); + if (null == decision) return false; + + if (decisionMaker.paysRightAfterDecision() && !part.payAsDecided(decisionMaker.getPlayer(), decision, ability)) + return false; + + decisions.put(part.getClass(), decision); + } + + for (final CostPart part : this.cost.getCostParts()) { + if (!part.payAsDecided(decisionMaker.getPlayer(), decisions.get(part.getClass()), this.ability)) { + return false; + } + // abilities care what was used to pay for them + if( part instanceof CostPartWithList ) { + ((CostPartWithList) part).reportPaidCardsTo(ability); + ((CostPartWithList) part).resetList(); + } + } + return true; + } +} diff --git a/forge-game/src/main/java/forge/game/cost/CostPutCardToLib.java b/forge-gui/src/main/java/forge/game/cost/CostPutCardToLib.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostPutCardToLib.java rename to forge-gui/src/main/java/forge/game/cost/CostPutCardToLib.java diff --git a/forge-game/src/main/java/forge/game/cost/CostPutCounter.java b/forge-gui/src/main/java/forge/game/cost/CostPutCounter.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostPutCounter.java rename to forge-gui/src/main/java/forge/game/cost/CostPutCounter.java diff --git a/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java b/forge-gui/src/main/java/forge/game/cost/CostRemoveAnyCounter.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java rename to forge-gui/src/main/java/forge/game/cost/CostRemoveAnyCounter.java diff --git a/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java b/forge-gui/src/main/java/forge/game/cost/CostRemoveCounter.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java rename to forge-gui/src/main/java/forge/game/cost/CostRemoveCounter.java diff --git a/forge-game/src/main/java/forge/game/cost/CostReturn.java b/forge-gui/src/main/java/forge/game/cost/CostReturn.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostReturn.java rename to forge-gui/src/main/java/forge/game/cost/CostReturn.java diff --git a/forge-game/src/main/java/forge/game/cost/CostReveal.java b/forge-gui/src/main/java/forge/game/cost/CostReveal.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostReveal.java rename to forge-gui/src/main/java/forge/game/cost/CostReveal.java diff --git a/forge-game/src/main/java/forge/game/cost/CostSacrifice.java b/forge-gui/src/main/java/forge/game/cost/CostSacrifice.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostSacrifice.java rename to forge-gui/src/main/java/forge/game/cost/CostSacrifice.java diff --git a/forge-game/src/main/java/forge/game/cost/CostTap.java b/forge-gui/src/main/java/forge/game/cost/CostTap.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostTap.java rename to forge-gui/src/main/java/forge/game/cost/CostTap.java diff --git a/forge-game/src/main/java/forge/game/cost/CostTapType.java b/forge-gui/src/main/java/forge/game/cost/CostTapType.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostTapType.java rename to forge-gui/src/main/java/forge/game/cost/CostTapType.java diff --git a/forge-game/src/main/java/forge/game/cost/CostUnattach.java b/forge-gui/src/main/java/forge/game/cost/CostUnattach.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostUnattach.java rename to forge-gui/src/main/java/forge/game/cost/CostUnattach.java diff --git a/forge-game/src/main/java/forge/game/cost/CostUntap.java b/forge-gui/src/main/java/forge/game/cost/CostUntap.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostUntap.java rename to forge-gui/src/main/java/forge/game/cost/CostUntap.java diff --git a/forge-game/src/main/java/forge/game/cost/CostUntapType.java b/forge-gui/src/main/java/forge/game/cost/CostUntapType.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/CostUntapType.java rename to forge-gui/src/main/java/forge/game/cost/CostUntapType.java diff --git a/forge-game/src/main/java/forge/game/cost/ICostVisitor.java b/forge-gui/src/main/java/forge/game/cost/ICostVisitor.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/ICostVisitor.java rename to forge-gui/src/main/java/forge/game/cost/ICostVisitor.java diff --git a/forge-game/src/main/java/forge/game/cost/PaymentDecision.java b/forge-gui/src/main/java/forge/game/cost/PaymentDecision.java similarity index 100% rename from forge-game/src/main/java/forge/game/cost/PaymentDecision.java rename to forge-gui/src/main/java/forge/game/cost/PaymentDecision.java diff --git a/forge-game/src/main/java/forge/game/cost/package-info.java b/forge-gui/src/main/java/forge/game/cost/package-info.java similarity index 94% rename from forge-game/src/main/java/forge/game/cost/package-info.java rename to forge-gui/src/main/java/forge/game/cost/package-info.java index e98a5f92c45..e4910979e33 100644 --- a/forge-game/src/main/java/forge/game/cost/package-info.java +++ b/forge-gui/src/main/java/forge/game/cost/package-info.java @@ -1,3 +1,3 @@ -/** Forge Card Game. */ -package forge.game.cost; - +/** Forge Card Game. */ +package forge.game.cost; + diff --git a/forge-game/src/main/java/forge/game/event/EventValueChangeType.java b/forge-gui/src/main/java/forge/game/event/EventValueChangeType.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/EventValueChangeType.java rename to forge-gui/src/main/java/forge/game/event/EventValueChangeType.java diff --git a/forge-game/src/main/java/forge/game/event/GameEvent.java b/forge-gui/src/main/java/forge/game/event/GameEvent.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEvent.java rename to forge-gui/src/main/java/forge/game/event/GameEvent.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventAnteCardsSelected.java b/forge-gui/src/main/java/forge/game/event/GameEventAnteCardsSelected.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventAnteCardsSelected.java rename to forge-gui/src/main/java/forge/game/event/GameEventAnteCardsSelected.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventAttackersDeclared.java b/forge-gui/src/main/java/forge/game/event/GameEventAttackersDeclared.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventAttackersDeclared.java rename to forge-gui/src/main/java/forge/game/event/GameEventAttackersDeclared.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventBlockersDeclared.java b/forge-gui/src/main/java/forge/game/event/GameEventBlockersDeclared.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventBlockersDeclared.java rename to forge-gui/src/main/java/forge/game/event/GameEventBlockersDeclared.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventCardAttachment.java b/forge-gui/src/main/java/forge/game/event/GameEventCardAttachment.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventCardAttachment.java rename to forge-gui/src/main/java/forge/game/event/GameEventCardAttachment.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventCardChangeZone.java b/forge-gui/src/main/java/forge/game/event/GameEventCardChangeZone.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventCardChangeZone.java rename to forge-gui/src/main/java/forge/game/event/GameEventCardChangeZone.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventCardCounters.java b/forge-gui/src/main/java/forge/game/event/GameEventCardCounters.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventCardCounters.java rename to forge-gui/src/main/java/forge/game/event/GameEventCardCounters.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventCardDamaged.java b/forge-gui/src/main/java/forge/game/event/GameEventCardDamaged.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventCardDamaged.java rename to forge-gui/src/main/java/forge/game/event/GameEventCardDamaged.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventCardDestroyed.java b/forge-gui/src/main/java/forge/game/event/GameEventCardDestroyed.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventCardDestroyed.java rename to forge-gui/src/main/java/forge/game/event/GameEventCardDestroyed.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventCardPhased.java b/forge-gui/src/main/java/forge/game/event/GameEventCardPhased.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventCardPhased.java rename to forge-gui/src/main/java/forge/game/event/GameEventCardPhased.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventCardRegenerated.java b/forge-gui/src/main/java/forge/game/event/GameEventCardRegenerated.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventCardRegenerated.java rename to forge-gui/src/main/java/forge/game/event/GameEventCardRegenerated.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventCardSacrificed.java b/forge-gui/src/main/java/forge/game/event/GameEventCardSacrificed.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventCardSacrificed.java rename to forge-gui/src/main/java/forge/game/event/GameEventCardSacrificed.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventCardStatsChanged.java b/forge-gui/src/main/java/forge/game/event/GameEventCardStatsChanged.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventCardStatsChanged.java rename to forge-gui/src/main/java/forge/game/event/GameEventCardStatsChanged.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventCardTapped.java b/forge-gui/src/main/java/forge/game/event/GameEventCardTapped.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventCardTapped.java rename to forge-gui/src/main/java/forge/game/event/GameEventCardTapped.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventCombatEnded.java b/forge-gui/src/main/java/forge/game/event/GameEventCombatEnded.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventCombatEnded.java rename to forge-gui/src/main/java/forge/game/event/GameEventCombatEnded.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventFlipCoin.java b/forge-gui/src/main/java/forge/game/event/GameEventFlipCoin.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventFlipCoin.java rename to forge-gui/src/main/java/forge/game/event/GameEventFlipCoin.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventGameFinished.java b/forge-gui/src/main/java/forge/game/event/GameEventGameFinished.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventGameFinished.java rename to forge-gui/src/main/java/forge/game/event/GameEventGameFinished.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventGameOutcome.java b/forge-gui/src/main/java/forge/game/event/GameEventGameOutcome.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventGameOutcome.java rename to forge-gui/src/main/java/forge/game/event/GameEventGameOutcome.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventGameRestarted.java b/forge-gui/src/main/java/forge/game/event/GameEventGameRestarted.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventGameRestarted.java rename to forge-gui/src/main/java/forge/game/event/GameEventGameRestarted.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventGameStarted.java b/forge-gui/src/main/java/forge/game/event/GameEventGameStarted.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventGameStarted.java rename to forge-gui/src/main/java/forge/game/event/GameEventGameStarted.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventLandPlayed.java b/forge-gui/src/main/java/forge/game/event/GameEventLandPlayed.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventLandPlayed.java rename to forge-gui/src/main/java/forge/game/event/GameEventLandPlayed.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventManaBurn.java b/forge-gui/src/main/java/forge/game/event/GameEventManaBurn.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventManaBurn.java rename to forge-gui/src/main/java/forge/game/event/GameEventManaBurn.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventManaPool.java b/forge-gui/src/main/java/forge/game/event/GameEventManaPool.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventManaPool.java rename to forge-gui/src/main/java/forge/game/event/GameEventManaPool.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventMulligan.java b/forge-gui/src/main/java/forge/game/event/GameEventMulligan.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventMulligan.java rename to forge-gui/src/main/java/forge/game/event/GameEventMulligan.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventPlayerControl.java b/forge-gui/src/main/java/forge/game/event/GameEventPlayerControl.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventPlayerControl.java rename to forge-gui/src/main/java/forge/game/event/GameEventPlayerControl.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventPlayerDamaged.java b/forge-gui/src/main/java/forge/game/event/GameEventPlayerDamaged.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventPlayerDamaged.java rename to forge-gui/src/main/java/forge/game/event/GameEventPlayerDamaged.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventPlayerLivesChanged.java b/forge-gui/src/main/java/forge/game/event/GameEventPlayerLivesChanged.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventPlayerLivesChanged.java rename to forge-gui/src/main/java/forge/game/event/GameEventPlayerLivesChanged.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventPlayerPoisoned.java b/forge-gui/src/main/java/forge/game/event/GameEventPlayerPoisoned.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventPlayerPoisoned.java rename to forge-gui/src/main/java/forge/game/event/GameEventPlayerPoisoned.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventPlayerPriority.java b/forge-gui/src/main/java/forge/game/event/GameEventPlayerPriority.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventPlayerPriority.java rename to forge-gui/src/main/java/forge/game/event/GameEventPlayerPriority.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventShuffle.java b/forge-gui/src/main/java/forge/game/event/GameEventShuffle.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventShuffle.java rename to forge-gui/src/main/java/forge/game/event/GameEventShuffle.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventSpellAbilityCast.java b/forge-gui/src/main/java/forge/game/event/GameEventSpellAbilityCast.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventSpellAbilityCast.java rename to forge-gui/src/main/java/forge/game/event/GameEventSpellAbilityCast.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventSpellRemovedFromStack.java b/forge-gui/src/main/java/forge/game/event/GameEventSpellRemovedFromStack.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventSpellRemovedFromStack.java rename to forge-gui/src/main/java/forge/game/event/GameEventSpellRemovedFromStack.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventSpellResolved.java b/forge-gui/src/main/java/forge/game/event/GameEventSpellResolved.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventSpellResolved.java rename to forge-gui/src/main/java/forge/game/event/GameEventSpellResolved.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventTokenCreated.java b/forge-gui/src/main/java/forge/game/event/GameEventTokenCreated.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventTokenCreated.java rename to forge-gui/src/main/java/forge/game/event/GameEventTokenCreated.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventTurnBegan.java b/forge-gui/src/main/java/forge/game/event/GameEventTurnBegan.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventTurnBegan.java rename to forge-gui/src/main/java/forge/game/event/GameEventTurnBegan.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventTurnEnded.java b/forge-gui/src/main/java/forge/game/event/GameEventTurnEnded.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventTurnEnded.java rename to forge-gui/src/main/java/forge/game/event/GameEventTurnEnded.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventTurnPhase.java b/forge-gui/src/main/java/forge/game/event/GameEventTurnPhase.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventTurnPhase.java rename to forge-gui/src/main/java/forge/game/event/GameEventTurnPhase.java diff --git a/forge-game/src/main/java/forge/game/event/GameEventZone.java b/forge-gui/src/main/java/forge/game/event/GameEventZone.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/GameEventZone.java rename to forge-gui/src/main/java/forge/game/event/GameEventZone.java diff --git a/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java b/forge-gui/src/main/java/forge/game/event/IGameEventVisitor.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/IGameEventVisitor.java rename to forge-gui/src/main/java/forge/game/event/IGameEventVisitor.java diff --git a/forge-game/src/main/java/forge/game/event/package-info.java b/forge-gui/src/main/java/forge/game/event/package-info.java similarity index 100% rename from forge-game/src/main/java/forge/game/event/package-info.java rename to forge-gui/src/main/java/forge/game/event/package-info.java diff --git a/forge-game/src/main/java/forge/game/mana/Mana.java b/forge-gui/src/main/java/forge/game/mana/Mana.java similarity index 94% rename from forge-game/src/main/java/forge/game/mana/Mana.java rename to forge-gui/src/main/java/forge/game/mana/Mana.java index 69fee12d30c..bdf9133418c 100644 --- a/forge-game/src/main/java/forge/game/mana/Mana.java +++ b/forge-gui/src/main/java/forge/game/mana/Mana.java @@ -1,235 +1,235 @@ -/* - * 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 . - */ -package forge.game.mana; - -import forge.card.MagicColor; -import forge.game.card.Card; -import forge.game.spellability.AbilityManaPart; -import forge.game.spellability.SpellAbility; - -/** - *

- * Mana class. - * This represents a single mana 'globe' floating in a player's pool. - *

- * - * @author Forge - * @version $Id: Mana.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class Mana { - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + color; - result = prime * result + (hasRestrictions ? 1231 : 1237); - result = prime * result + ((manaAbility == null) ? 0 : manaAbility.hashCode()); - result = prime * result + ((sourceCard == null) ? 0 : sourceCard.hashCode()); - return result; - } - - - @Override - public boolean equals(Object other) { - if (!(other instanceof Mana)) { - return false; - } - Mana m2 = (Mana) other; - - if (color != m2.color) { - return false; - } - - AbilityManaPart mp = this.getManaAbility(); - AbilityManaPart mp2 = m2.getManaAbility(); - if ((mp == null) != (mp2 == null)) { - return false; - } - - return mp == mp2 || mp.getManaRestrictions().equals(mp2.getManaRestrictions()); - } - - - private byte color; - private Card sourceCard = null; - private AbilityManaPart manaAbility = null; - private boolean hasRestrictions = false; - - /** - *

- * Constructor for Mana. - *

- * - * @param col - * a {@link java.lang.String} object. - * @param source - * a {@link forge.game.card.Card} object. - * @param manaAbility - * a {@link forge.card.spellability.AbilityMana} object - */ - public Mana(final byte color, final Card source, final AbilityManaPart manaAbility) { - this.color = color; - if (manaAbility != null) { - this.manaAbility = manaAbility; - if (!manaAbility.getManaRestrictions().isEmpty()) { - this.hasRestrictions = true; - } - } - if (source == null) { - return; - } - - - this.sourceCard = source; - } - - - /** - *

- * toString. - *

- * - * @return a {@link java.lang.String} object. - */ - @Override - public final String toString() { - return MagicColor.toShortString(color); - } - - /** - *

- * isSnow. - *

- * - * @return a boolean. - */ - public final boolean isSnow() { - return this.sourceCard.isSnow(); - } - - /** - *

- * isRestricted. - *

- * - * @return a boolean. - */ - public final boolean isRestricted() { - return this.hasRestrictions; - } - - /** - *

- * isRestricted. - *

- * - * @return a boolean. - */ - public final boolean addsNoCounterMagic(SpellAbility saBeingPaid) { - return this.manaAbility != null && manaAbility.cannotCounterPaidWith(saBeingPaid); - } - - /** - *

- * addsCounters. - *

- * - * @return a boolean. - */ - public final boolean addsCounters(SpellAbility saBeingPaid) { - return this.manaAbility != null && manaAbility.addsCounters(saBeingPaid); - } - - - /** - *

- * addsKeywords. - *

- * - * @return a boolean. - */ - public final boolean addsKeywords(SpellAbility saBeingPaid) { - return this.manaAbility != null && manaAbility.addKeywords(saBeingPaid); - } - - /** - *

- * getAddedKeywords. - *

- * @return a String. - */ - public final String getAddedKeywords() { - return this.manaAbility.getKeywords(); - } - - /** - *

- * isColor. - *

- * - * @param col - * a {@link java.lang.String} object. - * @return a boolean. - */ - public final boolean isColor(final String col) { - return this.getColor().equals(col); - } - - /** - *

- * Getter for the field color. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getColor() { - return MagicColor.toLongString(this.color); - } - - /** - *

- * Getter for the field sourceCard. - *

- * - * @return a {@link forge.game.card.Card} object. - */ - public final Card getSourceCard() { - return this.sourceCard; - } - - /** - *

- * Getter for the field sourceCard. - *

- * - * @return a {@link forge.card.spellability.AbilityMana} object. - */ - public final AbilityManaPart getManaAbility() { - return this.manaAbility; - } - - public byte getColorCode() { - return color; - } - - - public boolean isColorless() { - return color == 0; - } - -} +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.game.mana; + +import forge.card.MagicColor; +import forge.game.card.Card; +import forge.game.spellability.AbilityManaPart; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Mana class. + * This represents a single mana 'globe' floating in a player's pool. + *

+ * + * @author Forge + * @version $Id$ + */ +public class Mana { + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + color; + result = prime * result + (hasRestrictions ? 1231 : 1237); + result = prime * result + ((manaAbility == null) ? 0 : manaAbility.hashCode()); + result = prime * result + ((sourceCard == null) ? 0 : sourceCard.hashCode()); + return result; + } + + + @Override + public boolean equals(Object other) { + if (!(other instanceof Mana)) { + return false; + } + Mana m2 = (Mana) other; + + if (color != m2.color) { + return false; + } + + AbilityManaPart mp = this.getManaAbility(); + AbilityManaPart mp2 = m2.getManaAbility(); + if ((mp == null) != (mp2 == null)) { + return false; + } + + return mp == mp2 || mp.getManaRestrictions().equals(mp2.getManaRestrictions()); + } + + + private byte color; + private Card sourceCard = null; + private AbilityManaPart manaAbility = null; + private boolean hasRestrictions = false; + + /** + *

+ * Constructor for Mana. + *

+ * + * @param col + * a {@link java.lang.String} object. + * @param source + * a {@link forge.game.card.Card} object. + * @param manaAbility + * a {@link forge.card.spellability.AbilityMana} object + */ + public Mana(final byte color, final Card source, final AbilityManaPart manaAbility) { + this.color = color; + if (manaAbility != null) { + this.manaAbility = manaAbility; + if (!manaAbility.getManaRestrictions().isEmpty()) { + this.hasRestrictions = true; + } + } + if (source == null) { + return; + } + + + this.sourceCard = source; + } + + + /** + *

+ * toString. + *

+ * + * @return a {@link java.lang.String} object. + */ + @Override + public final String toString() { + return MagicColor.toShortString(color); + } + + /** + *

+ * isSnow. + *

+ * + * @return a boolean. + */ + public final boolean isSnow() { + return this.sourceCard.isSnow(); + } + + /** + *

+ * isRestricted. + *

+ * + * @return a boolean. + */ + public final boolean isRestricted() { + return this.hasRestrictions; + } + + /** + *

+ * isRestricted. + *

+ * + * @return a boolean. + */ + public final boolean addsNoCounterMagic(SpellAbility saBeingPaid) { + return this.manaAbility != null && manaAbility.cannotCounterPaidWith(saBeingPaid); + } + + /** + *

+ * addsCounters. + *

+ * + * @return a boolean. + */ + public final boolean addsCounters(SpellAbility saBeingPaid) { + return this.manaAbility != null && manaAbility.addsCounters(saBeingPaid); + } + + + /** + *

+ * addsKeywords. + *

+ * + * @return a boolean. + */ + public final boolean addsKeywords(SpellAbility saBeingPaid) { + return this.manaAbility != null && manaAbility.addKeywords(saBeingPaid); + } + + /** + *

+ * getAddedKeywords. + *

+ * @return a String. + */ + public final String getAddedKeywords() { + return this.manaAbility.getKeywords(); + } + + /** + *

+ * isColor. + *

+ * + * @param col + * a {@link java.lang.String} object. + * @return a boolean. + */ + public final boolean isColor(final String col) { + return this.getColor().equals(col); + } + + /** + *

+ * Getter for the field color. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getColor() { + return MagicColor.toLongString(this.color); + } + + /** + *

+ * Getter for the field sourceCard. + *

+ * + * @return a {@link forge.game.card.Card} object. + */ + public final Card getSourceCard() { + return this.sourceCard; + } + + /** + *

+ * Getter for the field sourceCard. + *

+ * + * @return a {@link forge.card.spellability.AbilityMana} object. + */ + public final AbilityManaPart getManaAbility() { + return this.manaAbility; + } + + public byte getColorCode() { + return color; + } + + + public boolean isColorless() { + return color == 0; + } + +} diff --git a/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java b/forge-gui/src/main/java/forge/game/mana/ManaCostBeingPaid.java similarity index 96% rename from forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java rename to forge-gui/src/main/java/forge/game/mana/ManaCostBeingPaid.java index 8cd5a396f5d..145c0f8d3e0 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java +++ b/forge-gui/src/main/java/forge/game/mana/ManaCostBeingPaid.java @@ -1,676 +1,676 @@ -/* - * 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 . - */ -package forge.game.mana; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.apache.commons.lang3.StringUtils; -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - -import forge.card.ColorSet; -import forge.card.MagicColor; -import forge.card.mana.IParserManaCost; -import forge.card.mana.ManaCost; -import forge.card.mana.ManaCostParser; -import forge.card.mana.ManaCostShard; -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.staticability.StaticAbility; -import forge.game.zone.ZoneType; -import forge.util.TextUtil; -import forge.util.maps.EnumMapToAmount; -import forge.util.maps.MapToAmount; - -/** - *

- * ManaCostBeingPaid class. - *

- * - * @author Forge - * @version $Id: ManaCostBeingPaid.java 24373 2014-01-20 07:32:12Z Max mtg $ - */ -public class ManaCostBeingPaid { - private class ManaCostBeingPaidIterator implements IParserManaCost, Iterator { - private Iterator mch; - private ManaCostShard nextShard = null; - private int remainingShards = 0; - private boolean hasSentX = false; - - public ManaCostBeingPaidIterator() { - mch = unpaidShards.keySet().iterator(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - @Override - public ManaCostShard next() { - if (remainingShards == 0) { - throw new UnsupportedOperationException("All shards were depleted, call hasNext()"); - } - remainingShards--; - return nextShard; - } - - @Override - public boolean hasNext() { - if (remainingShards > 0) { return true; } - if (!hasSentX) { - if (nextShard != ManaCostShard.X && cntX > 0) { - nextShard = ManaCostShard.X; - remainingShards = cntX; - return true; - } - else { - hasSentX = true; - } - } - if (!mch.hasNext()) { return false; } - - nextShard = mch.next(); - if (nextShard == ManaCostShard.COLORLESS) { - return this.hasNext(); // skip colorless - } - remainingShards = unpaidShards.get(nextShard); - - return true; - } - - @Override - public int getTotalColorlessCost() { - Integer c = unpaidShards.get(ManaCostShard.COLORLESS); - return c == null ? 0 : c.intValue(); - } - } - - // holds Mana_Part objects - // ManaPartColor is stored before ManaPartColorless - private final MapToAmount unpaidShards; - private final String sourceRestriction; - private byte sunburstMap = 0; - private int cntX = 0; - - /** - * Copy constructor - * @param manaCostBeingPaid - */ - public ManaCostBeingPaid(ManaCostBeingPaid manaCostBeingPaid) { - unpaidShards = new EnumMapToAmount(manaCostBeingPaid.unpaidShards); - sourceRestriction = manaCostBeingPaid.sourceRestriction; - sunburstMap = manaCostBeingPaid.sunburstMap; - cntX = manaCostBeingPaid.cntX; - } - - // manaCost can be like "0", "3", "G", "GW", "10", "3 GW", "10 GW" - // or "split hybrid mana" like "2/G 2/G", "2/B 2/B 2/B" - // "GW" can be paid with either G or W - /** - *

- * Constructor for ManaCost. - *

- * - * @param manaCost - * a {@link java.lang.String} object. - */ - public ManaCostBeingPaid(String sCost) { - this("0".equals(sCost) || "C".equals(sCost) || sCost.isEmpty() ? ManaCost.ZERO : new ManaCost(new ManaCostParser(sCost))); - } - - public ManaCostBeingPaid(ManaCost manaCost) { - this(manaCost, null); - } - - public ManaCostBeingPaid(ManaCost manaCost, String srcRestriction) { - unpaidShards = new EnumMapToAmount(ManaCostShard.class); - sourceRestriction = srcRestriction; - if (manaCost == null) { return; } - for (ManaCostShard shard : manaCost) { - if (shard == ManaCostShard.X) { - cntX++; - } else { - unpaidShards.add(shard); - } - } - increaseColorlessMana(manaCost.getGenericCost()); - } - - /** - *

- * getSunburst. - *

- * - * @return a int. - */ - public final int getSunburst() { - return ColorSet.fromMask(sunburstMap).countColors(); - } - - - public final byte getColorsPaid() { - return sunburstMap; - } - - public final boolean containsPhyrexianMana() { - for (ManaCostShard shard : unpaidShards.keySet()) { - if (shard.isPhyrexian()) { - return true; - } - } - return false; - } - - public final boolean payPhyrexian() { - ManaCostShard phy = null; - for (ManaCostShard mcs : unpaidShards.keySet()) { - if (mcs.isPhyrexian()) { - phy = mcs; - break; - } - } - - if (phy == null) { - return false; - } - - decreaseShard(phy, 1); - return true; - } - - // takes a Short Color and returns true if it exists in the mana cost. - // Easier for split costs - public final boolean needsColor(final byte colorMask) { - for (ManaCostShard shard : unpaidShards.keySet()) { - if (shard == ManaCostShard.COLORLESS) { - continue; - } - if (shard.isOr2Colorless()) { - if ((shard.getColorMask() & colorMask) != 0) { - return true; - } - } - else if (shard.canBePaidWithManaOfColor(colorMask)) { - return true; - } - } - return false; - } - - // isNeeded(String) still used by the Computer, might have problems activating Snow abilities - public final boolean isAnyPartPayableWith(byte colorMask) { - for (ManaCostShard shard : unpaidShards.keySet()) { - if (shard.canBePaidWithManaOfColor(colorMask)) { - return true; - } - } - return false; - } - - public final boolean isNeeded(final Mana paid) { - for (ManaCostShard shard : unpaidShards.keySet()) { - if (canBePaidWith(shard, paid)) { - return true; - } - } - return false; - } - - public final boolean isPaid() { - return unpaidShards.isEmpty(); - } - - /** - *

- * payMultipleMana. - *

- * - * @param mana - * a {@link java.lang.String} object. - * @return a boolean. - */ - public final String payMultipleMana(String mana) { - List unused = new ArrayList(4); - for (String manaPart : TextUtil.split(mana, ' ')) { - if (StringUtils.isNumeric(manaPart)) { - for (int i = Integer.parseInt(manaPart); i > 0; i--) { - boolean wasNeeded = this.payMana("1"); - if (!wasNeeded) { - unused.add(Integer.toString(i)); - break; - } - } - } - else { - String color = MagicColor.toShortString(manaPart); - boolean wasNeeded = this.payMana(color); - if (!wasNeeded) { - unused.add(color); - } - } - } - return unused.isEmpty() ? null : StringUtils.join(unused, ' '); - } - - public final void increaseColorlessMana(final int manaToAdd) { - increaseShard(ManaCostShard.COLORLESS, manaToAdd); - } - - public final void increaseShard(final ManaCostShard shard, final int toAdd) { - unpaidShards.add(shard, toAdd); - } - - public final void decreaseColorlessMana(final int manaToSubtract) { - decreaseShard(ManaCostShard.COLORLESS, manaToSubtract); - } - - public final void decreaseShard(final ManaCostShard shard, final int manaToSubtract) { - if (manaToSubtract <= 0) { - return; - } - - if (!unpaidShards.containsKey(shard)) { - System.err.println("Tried to substract a " + shard.toString() + " shard that is not present in this ManaCostBeingPaid"); - return; - } - unpaidShards.substract(shard, manaToSubtract); - } - - public final int getColorlessManaAmount() { - return unpaidShards.count(ManaCostShard.COLORLESS); - } - - /** - *

- * addMana. - *

- * - * @param mana - * a {@link java.lang.String} object. - * @return a boolean. - */ - public final boolean payMana(final String mana) { - final byte colorMask = MagicColor.fromName(mana); - if (!this.isAnyPartPayableWith(colorMask)) { - //System.out.println("ManaCost : addMana() error, mana not needed - " + mana); - return false; - //throw new RuntimeException("ManaCost : addMana() error, mana not needed - " + mana); - } - - Predicate predCanBePaid = new Predicate() { - @Override - public boolean apply(ManaCostShard ms) { - return ms.canBePaidWithManaOfColor(colorMask); - } - }; - - return tryPayMana(colorMask, Iterables.filter(unpaidShards.keySet(), predCanBePaid)); - } - - /** - *

- * addMana. - *

- * - * @param mana - * a {@link forge.game.mana.Mana} object. - * @return a boolean. - */ - public final boolean payMana(final Mana mana) { - if (!this.isNeeded(mana)) { - throw new RuntimeException("ManaCost : addMana() error, mana not needed - " + mana); - } - - Predicate predCanBePaid = new Predicate() { - @Override - public boolean apply(ManaCostShard ms) { - return canBePaidWith(ms, mana); - } - }; - - return tryPayMana(mana.getColorCode(), Iterables.filter(unpaidShards.keySet(), predCanBePaid)); - } - - private boolean tryPayMana(final byte colorMask, Iterable payableShards) { - ManaCostShard choice = null; - for (ManaCostShard toPay : payableShards) { - // if m is a better to pay than choice - if (choice == null) { - choice = toPay; - continue; - } - if (isFirstChoiceBetter(toPay, choice, colorMask)) { - choice = toPay; - } - } // for - if (choice == null) { - return false; - } - - decreaseShard(choice, 1); - if (choice.isOr2Colorless() && choice.getColorMask() != colorMask) { - this.increaseColorlessMana(1); - } - - this.sunburstMap |= colorMask; - return true; - } - - private boolean isFirstChoiceBetter(ManaCostShard s1, ManaCostShard s2, byte colorMask) { - return getPayPriority(s1, colorMask) > getPayPriority(s2, colorMask); - } - - private int getPayPriority(ManaCostShard bill, byte paymentColor) { - if (bill == ManaCostShard.COLORLESS) { - return 0; - } - - if (bill.isMonoColor()) { - if (bill.isOr2Colorless()) { - return bill.getColorMask() == paymentColor ? 9 : 4; - } - if (!bill.isPhyrexian()) { - return 10; - } - return 8; - } - return 5; - } - - private boolean canBePaidWith(ManaCostShard shard, Mana mana) { - if (shard.isSnow() && !mana.isSnow()) { - return false; - } - - byte color = mana.getColorCode(); - return shard.canBePaidWithManaOfColor(color); - } - - public final void combineManaCost(final ManaCost extra) { - for (ManaCostShard shard : extra) { - if (shard == ManaCostShard.X) { - cntX++; - } - else { - increaseShard(shard, 1); - } - } - increaseColorlessMana(extra.getGenericCost()); - } - - public final void determineManaCostDifference(final ManaCost subThisManaCost) { - for (ManaCostShard shard : subThisManaCost) { - if (shard == ManaCostShard.X) { - cntX--; - } - else if (unpaidShards.containsKey(shard)) { - decreaseShard(shard, 1); - } - else { - decreaseColorlessMana(1); - } - } - decreaseColorlessMana(subThisManaCost.getGenericCost()); - } - - /** - * To string. - * - * @param addX - * the add x - * @return the string - */ - public final String toString(final boolean addX) { - // Boolean addX used to add Xs into the returned value - final StringBuilder sb = new StringBuilder(); - - if (addX) { - for (int i = 0; i < this.getXcounter(); i++) { - sb.append("{X}"); - } - } - - int nGeneric = getColorlessManaAmount(); - if (nGeneric > 0) { - if (nGeneric <= 20) { - sb.append("{" + nGeneric + "}"); - } - else { //if no mana symbol exists for colorless amount, use combination of symbols for each digit - String genericStr = String.valueOf(nGeneric); - for (int i = 0; i < genericStr.length(); i++) { - sb.append("{" + genericStr.charAt(i) + "}"); - } - } - } - - for (Entry s : unpaidShards.entrySet()) { - if (s.getKey() == ManaCostShard.COLORLESS) { - continue; - } - for (int i = 0; i < s.getValue(); i++) { - sb.append(s.getKey().toString()); - } - } - - final String str = sb.toString(); - - if (str.equals("")) { - return "0"; - } - - return str; - } - - /** {@inheritDoc} */ - @Override - public final String toString() { - return this.toString(true); - } - - /** - *

- * getConvertedManaCost. - *

- * - * @return a int. - */ - public final int getConvertedManaCost() { - int cmc = 0; - - for (final Entry s : this.unpaidShards.entrySet()) { - cmc += s.getKey().getCmc() * s.getValue(); - } - return cmc; - } - - public ManaCost toManaCost() { - return new ManaCost(new ManaCostBeingPaidIterator()); - } - - public final int getXcounter() { - return cntX; - } - - public final List getUnpaidShards() { - List result = new ArrayList(); - for (Entry kv : unpaidShards.entrySet()) { - for (int i = kv.getValue().intValue(); i > 0; i--) { - result.add(kv.getKey()); - } - } - for (int i = cntX; i > 0; i--) { - result.add(ManaCostShard.X); - } - return result; - } - - /** - *

- * removeColorlessMana. - *

- * - * @since 1.0.15 - */ - public final void removeColorlessMana() { - unpaidShards.remove(ManaCostShard.COLORLESS); - } - - public final void applySpellCostChange(final SpellAbility sa, boolean test) { - final Game game = sa.getActivatingPlayer().getGame(); - // Beached - final Card originalCard = sa.getSourceCard(); - final SpellAbility spell = sa; - - if (sa.isXCost() && !originalCard.isCopiedSpell()) { - originalCard.setXManaCostPaid(0); - } - - if (sa.isTrigger()) { - return; - } - - if (spell.isSpell()) { - if (spell.isDelve()) { - final Player pc = originalCard.getController(); - final List mutableGrave = new ArrayList(pc.getCardsIn(ZoneType.Graveyard)); - final List toExile = pc.getController().chooseCardsToDelve(this.getColorlessManaAmount(), mutableGrave); - for (final Card c : toExile) { - decreaseColorlessMana(1); - if (!test) { - pc.getGame().getAction().exile(c); - } - } - } - else if (spell.getSourceCard().hasKeyword("Convoke")) { - adjustCostByConvoke(sa); - } - } // isSpell - - List cardsOnBattlefield = Lists.newArrayList(game.getCardsIn(ZoneType.Battlefield)); - cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Stack)); - cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Command)); - if (!cardsOnBattlefield.contains(originalCard)) { - cardsOnBattlefield.add(originalCard); - } - final ArrayList raiseAbilities = new ArrayList(); - final ArrayList reduceAbilities = new ArrayList(); - final ArrayList setAbilities = new ArrayList(); - - // Sort abilities to apply them in proper order - for (Card c : cardsOnBattlefield) { - final ArrayList staticAbilities = c.getStaticAbilities(); - for (final StaticAbility stAb : staticAbilities) { - if (stAb.getMapParams().get("Mode").equals("RaiseCost")) { - raiseAbilities.add(stAb); - } - else if (stAb.getMapParams().get("Mode").equals("ReduceCost")) { - reduceAbilities.add(stAb); - } - else if (stAb.getMapParams().get("Mode").equals("SetCost")) { - setAbilities.add(stAb); - } - } - } - // Raise cost - for (final StaticAbility stAb : raiseAbilities) { - stAb.applyAbility("RaiseCost", spell, this); - } - - // Reduce cost - for (final StaticAbility stAb : reduceAbilities) { - stAb.applyAbility("ReduceCost", spell, this); - } - if (spell.isSpell() && spell.isOffering()) { // cost reduction from offerings - adjustCostByOffering(sa, spell); - } - - // Set cost (only used by Trinisphere) is applied last - for (final StaticAbility stAb : setAbilities) { - stAb.applyAbility("SetCost", spell, this); - } - } // GetSpellCostChange - - private void adjustCostByConvoke(final SpellAbility sa) { - - List untappedCreats = CardLists.filter(sa.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); - untappedCreats = CardLists.filter(untappedCreats, CardPredicates.Presets.UNTAPPED); - - Map convokedCards = sa.getActivatingPlayer().getController().chooseCardsForConvoke(sa, this.toManaCost(), untappedCreats); - - // Convoked creats are tapped here with triggers suppressed, - // Then again when payment is done(In InputPayManaCost.done()) with suppression cleared. - // This is to make sure that triggers go off at the right time - // AND that you can't use mana tapabilities of convoked creatures to pay the convoked cost. - for (final Entry conv : convokedCards.entrySet()) { - sa.addTappedForConvoke(conv.getKey()); - this.decreaseShard(conv.getValue(), 1); - conv.getKey().setTapped(true); - } - } - - private void adjustCostByOffering(final SpellAbility sa, final SpellAbility spell) { - String offeringType = ""; - for (String kw : sa.getSourceCard().getKeyword()) { - if (kw.endsWith(" offering")) { - offeringType = kw.split(" ")[0]; - break; - } - } - - Card toSac = null; - List canOffer = CardLists.filter(spell.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), - CardPredicates.isType(offeringType)); - - final List toSacList = sa.getSourceCard().getController().getController().choosePermanentsToSacrifice(spell, 0, 1, canOffer, - offeringType); - - if (!toSacList.isEmpty()) { - toSac = toSacList.get(0); - } - else { - return; - } - - determineManaCostDifference(toSac.getManaCost()); - - sa.setSacrificedAsOffering(toSac); - toSac.setUsedToPay(true); //stop it from interfering with mana input - } - - public String getSourceRestriction() { - return sourceRestriction; - } - - public Iterable getDistinctShards() { - return unpaidShards.keySet(); - } - - public int getUnpaidShards(ManaCostShard key) { - return unpaidShards.count(key); - } -} +/* + * 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 . + */ +package forge.game.mana; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.lang3.StringUtils; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import forge.card.ColorSet; +import forge.card.MagicColor; +import forge.card.mana.IParserManaCost; +import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostParser; +import forge.card.mana.ManaCostShard; +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.staticability.StaticAbility; +import forge.game.zone.ZoneType; +import forge.util.TextUtil; +import forge.util.maps.EnumMapToAmount; +import forge.util.maps.MapToAmount; + +/** + *

+ * ManaCostBeingPaid class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class ManaCostBeingPaid { + private class ManaCostBeingPaidIterator implements IParserManaCost, Iterator { + private Iterator mch; + private ManaCostShard nextShard = null; + private int remainingShards = 0; + private boolean hasSentX = false; + + public ManaCostBeingPaidIterator() { + mch = unpaidShards.keySet().iterator(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public ManaCostShard next() { + if (remainingShards == 0) { + throw new UnsupportedOperationException("All shards were depleted, call hasNext()"); + } + remainingShards--; + return nextShard; + } + + @Override + public boolean hasNext() { + if (remainingShards > 0) { return true; } + if (!hasSentX) { + if (nextShard != ManaCostShard.X && cntX > 0) { + nextShard = ManaCostShard.X; + remainingShards = cntX; + return true; + } + else { + hasSentX = true; + } + } + if (!mch.hasNext()) { return false; } + + nextShard = mch.next(); + if (nextShard == ManaCostShard.COLORLESS) { + return this.hasNext(); // skip colorless + } + remainingShards = unpaidShards.get(nextShard); + + return true; + } + + @Override + public int getTotalColorlessCost() { + Integer c = unpaidShards.get(ManaCostShard.COLORLESS); + return c == null ? 0 : c.intValue(); + } + } + + // holds Mana_Part objects + // ManaPartColor is stored before ManaPartColorless + private final MapToAmount unpaidShards; + private final String sourceRestriction; + private byte sunburstMap = 0; + private int cntX = 0; + + /** + * Copy constructor + * @param manaCostBeingPaid + */ + public ManaCostBeingPaid(ManaCostBeingPaid manaCostBeingPaid) { + unpaidShards = new EnumMapToAmount(manaCostBeingPaid.unpaidShards); + sourceRestriction = manaCostBeingPaid.sourceRestriction; + sunburstMap = manaCostBeingPaid.sunburstMap; + cntX = manaCostBeingPaid.cntX; + } + + // manaCost can be like "0", "3", "G", "GW", "10", "3 GW", "10 GW" + // or "split hybrid mana" like "2/G 2/G", "2/B 2/B 2/B" + // "GW" can be paid with either G or W + /** + *

+ * Constructor for ManaCost. + *

+ * + * @param manaCost + * a {@link java.lang.String} object. + */ + public ManaCostBeingPaid(String sCost) { + this("0".equals(sCost) || "C".equals(sCost) || sCost.isEmpty() ? ManaCost.ZERO : new ManaCost(new ManaCostParser(sCost))); + } + + public ManaCostBeingPaid(ManaCost manaCost) { + this(manaCost, null); + } + + public ManaCostBeingPaid(ManaCost manaCost, String srcRestriction) { + unpaidShards = new EnumMapToAmount(ManaCostShard.class); + sourceRestriction = srcRestriction; + if (manaCost == null) { return; } + for (ManaCostShard shard : manaCost) { + if (shard == ManaCostShard.X) { + cntX++; + } else { + unpaidShards.add(shard); + } + } + increaseColorlessMana(manaCost.getGenericCost()); + } + + /** + *

+ * getSunburst. + *

+ * + * @return a int. + */ + public final int getSunburst() { + return ColorSet.fromMask(sunburstMap).countColors(); + } + + + public final byte getColorsPaid() { + return sunburstMap; + } + + public final boolean containsPhyrexianMana() { + for (ManaCostShard shard : unpaidShards.keySet()) { + if (shard.isPhyrexian()) { + return true; + } + } + return false; + } + + public final boolean payPhyrexian() { + ManaCostShard phy = null; + for (ManaCostShard mcs : unpaidShards.keySet()) { + if (mcs.isPhyrexian()) { + phy = mcs; + break; + } + } + + if (phy == null) { + return false; + } + + decreaseShard(phy, 1); + return true; + } + + // takes a Short Color and returns true if it exists in the mana cost. + // Easier for split costs + public final boolean needsColor(final byte colorMask) { + for (ManaCostShard shard : unpaidShards.keySet()) { + if (shard == ManaCostShard.COLORLESS) { + continue; + } + if (shard.isOr2Colorless()) { + if ((shard.getColorMask() & colorMask) != 0) { + return true; + } + } + else if (shard.canBePaidWithManaOfColor(colorMask)) { + return true; + } + } + return false; + } + + // isNeeded(String) still used by the Computer, might have problems activating Snow abilities + public final boolean isAnyPartPayableWith(byte colorMask) { + for (ManaCostShard shard : unpaidShards.keySet()) { + if (shard.canBePaidWithManaOfColor(colorMask)) { + return true; + } + } + return false; + } + + public final boolean isNeeded(final Mana paid) { + for (ManaCostShard shard : unpaidShards.keySet()) { + if (canBePaidWith(shard, paid)) { + return true; + } + } + return false; + } + + public final boolean isPaid() { + return unpaidShards.isEmpty(); + } + + /** + *

+ * payMultipleMana. + *

+ * + * @param mana + * a {@link java.lang.String} object. + * @return a boolean. + */ + public final String payMultipleMana(String mana) { + List unused = new ArrayList(4); + for (String manaPart : TextUtil.split(mana, ' ')) { + if (StringUtils.isNumeric(manaPart)) { + for (int i = Integer.parseInt(manaPart); i > 0; i--) { + boolean wasNeeded = this.payMana("1"); + if (!wasNeeded) { + unused.add(Integer.toString(i)); + break; + } + } + } + else { + String color = MagicColor.toShortString(manaPart); + boolean wasNeeded = this.payMana(color); + if (!wasNeeded) { + unused.add(color); + } + } + } + return unused.isEmpty() ? null : StringUtils.join(unused, ' '); + } + + public final void increaseColorlessMana(final int manaToAdd) { + increaseShard(ManaCostShard.COLORLESS, manaToAdd); + } + + public final void increaseShard(final ManaCostShard shard, final int toAdd) { + unpaidShards.add(shard, toAdd); + } + + public final void decreaseColorlessMana(final int manaToSubtract) { + decreaseShard(ManaCostShard.COLORLESS, manaToSubtract); + } + + public final void decreaseShard(final ManaCostShard shard, final int manaToSubtract) { + if (manaToSubtract <= 0) { + return; + } + + if (!unpaidShards.containsKey(shard)) { + System.err.println("Tried to substract a " + shard.toString() + " shard that is not present in this ManaCostBeingPaid"); + return; + } + unpaidShards.substract(shard, manaToSubtract); + } + + public final int getColorlessManaAmount() { + return unpaidShards.count(ManaCostShard.COLORLESS); + } + + /** + *

+ * addMana. + *

+ * + * @param mana + * a {@link java.lang.String} object. + * @return a boolean. + */ + public final boolean payMana(final String mana) { + final byte colorMask = MagicColor.fromName(mana); + if (!this.isAnyPartPayableWith(colorMask)) { + //System.out.println("ManaCost : addMana() error, mana not needed - " + mana); + return false; + //throw new RuntimeException("ManaCost : addMana() error, mana not needed - " + mana); + } + + Predicate predCanBePaid = new Predicate() { + @Override + public boolean apply(ManaCostShard ms) { + return ms.canBePaidWithManaOfColor(colorMask); + } + }; + + return tryPayMana(colorMask, Iterables.filter(unpaidShards.keySet(), predCanBePaid)); + } + + /** + *

+ * addMana. + *

+ * + * @param mana + * a {@link forge.game.mana.Mana} object. + * @return a boolean. + */ + public final boolean payMana(final Mana mana) { + if (!this.isNeeded(mana)) { + throw new RuntimeException("ManaCost : addMana() error, mana not needed - " + mana); + } + + Predicate predCanBePaid = new Predicate() { + @Override + public boolean apply(ManaCostShard ms) { + return canBePaidWith(ms, mana); + } + }; + + return tryPayMana(mana.getColorCode(), Iterables.filter(unpaidShards.keySet(), predCanBePaid)); + } + + private boolean tryPayMana(final byte colorMask, Iterable payableShards) { + ManaCostShard choice = null; + for (ManaCostShard toPay : payableShards) { + // if m is a better to pay than choice + if (choice == null) { + choice = toPay; + continue; + } + if (isFirstChoiceBetter(toPay, choice, colorMask)) { + choice = toPay; + } + } // for + if (choice == null) { + return false; + } + + decreaseShard(choice, 1); + if (choice.isOr2Colorless() && choice.getColorMask() != colorMask) { + this.increaseColorlessMana(1); + } + + this.sunburstMap |= colorMask; + return true; + } + + private boolean isFirstChoiceBetter(ManaCostShard s1, ManaCostShard s2, byte colorMask) { + return getPayPriority(s1, colorMask) > getPayPriority(s2, colorMask); + } + + private int getPayPriority(ManaCostShard bill, byte paymentColor) { + if (bill == ManaCostShard.COLORLESS) { + return 0; + } + + if (bill.isMonoColor()) { + if (bill.isOr2Colorless()) { + return bill.getColorMask() == paymentColor ? 9 : 4; + } + if (!bill.isPhyrexian()) { + return 10; + } + return 8; + } + return 5; + } + + private boolean canBePaidWith(ManaCostShard shard, Mana mana) { + if (shard.isSnow() && !mana.isSnow()) { + return false; + } + + byte color = mana.getColorCode(); + return shard.canBePaidWithManaOfColor(color); + } + + public final void combineManaCost(final ManaCost extra) { + for (ManaCostShard shard : extra) { + if (shard == ManaCostShard.X) { + cntX++; + } + else { + increaseShard(shard, 1); + } + } + increaseColorlessMana(extra.getGenericCost()); + } + + public final void determineManaCostDifference(final ManaCost subThisManaCost) { + for (ManaCostShard shard : subThisManaCost) { + if (shard == ManaCostShard.X) { + cntX--; + } + else if (unpaidShards.containsKey(shard)) { + decreaseShard(shard, 1); + } + else { + decreaseColorlessMana(1); + } + } + decreaseColorlessMana(subThisManaCost.getGenericCost()); + } + + /** + * To string. + * + * @param addX + * the add x + * @return the string + */ + public final String toString(final boolean addX) { + // Boolean addX used to add Xs into the returned value + final StringBuilder sb = new StringBuilder(); + + if (addX) { + for (int i = 0; i < this.getXcounter(); i++) { + sb.append("{X}"); + } + } + + int nGeneric = getColorlessManaAmount(); + if (nGeneric > 0) { + if (nGeneric <= 20) { + sb.append("{" + nGeneric + "}"); + } + else { //if no mana symbol exists for colorless amount, use combination of symbols for each digit + String genericStr = String.valueOf(nGeneric); + for (int i = 0; i < genericStr.length(); i++) { + sb.append("{" + genericStr.charAt(i) + "}"); + } + } + } + + for (Entry s : unpaidShards.entrySet()) { + if (s.getKey() == ManaCostShard.COLORLESS) { + continue; + } + for (int i = 0; i < s.getValue(); i++) { + sb.append(s.getKey().toString()); + } + } + + final String str = sb.toString(); + + if (str.equals("")) { + return "0"; + } + + return str; + } + + /** {@inheritDoc} */ + @Override + public final String toString() { + return this.toString(true); + } + + /** + *

+ * getConvertedManaCost. + *

+ * + * @return a int. + */ + public final int getConvertedManaCost() { + int cmc = 0; + + for (final Entry s : this.unpaidShards.entrySet()) { + cmc += s.getKey().getCmc() * s.getValue(); + } + return cmc; + } + + public ManaCost toManaCost() { + return new ManaCost(new ManaCostBeingPaidIterator()); + } + + public final int getXcounter() { + return cntX; + } + + public final List getUnpaidShards() { + List result = new ArrayList(); + for (Entry kv : unpaidShards.entrySet()) { + for (int i = kv.getValue().intValue(); i > 0; i--) { + result.add(kv.getKey()); + } + } + for (int i = cntX; i > 0; i--) { + result.add(ManaCostShard.X); + } + return result; + } + + /** + *

+ * removeColorlessMana. + *

+ * + * @since 1.0.15 + */ + public final void removeColorlessMana() { + unpaidShards.remove(ManaCostShard.COLORLESS); + } + + public final void applySpellCostChange(final SpellAbility sa, boolean test) { + final Game game = sa.getActivatingPlayer().getGame(); + // Beached + final Card originalCard = sa.getSourceCard(); + final SpellAbility spell = sa; + + if (sa.isXCost() && !originalCard.isCopiedSpell()) { + originalCard.setXManaCostPaid(0); + } + + if (sa.isTrigger()) { + return; + } + + if (spell.isSpell()) { + if (spell.isDelve()) { + final Player pc = originalCard.getController(); + final List mutableGrave = new ArrayList(pc.getCardsIn(ZoneType.Graveyard)); + final List toExile = pc.getController().chooseCardsToDelve(this.getColorlessManaAmount(), mutableGrave); + for (final Card c : toExile) { + decreaseColorlessMana(1); + if (!test) { + pc.getGame().getAction().exile(c); + } + } + } + else if (spell.getSourceCard().hasKeyword("Convoke")) { + adjustCostByConvoke(sa); + } + } // isSpell + + List cardsOnBattlefield = Lists.newArrayList(game.getCardsIn(ZoneType.Battlefield)); + cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Stack)); + cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Command)); + if (!cardsOnBattlefield.contains(originalCard)) { + cardsOnBattlefield.add(originalCard); + } + final ArrayList raiseAbilities = new ArrayList(); + final ArrayList reduceAbilities = new ArrayList(); + final ArrayList setAbilities = new ArrayList(); + + // Sort abilities to apply them in proper order + for (Card c : cardsOnBattlefield) { + final ArrayList staticAbilities = c.getStaticAbilities(); + for (final StaticAbility stAb : staticAbilities) { + if (stAb.getMapParams().get("Mode").equals("RaiseCost")) { + raiseAbilities.add(stAb); + } + else if (stAb.getMapParams().get("Mode").equals("ReduceCost")) { + reduceAbilities.add(stAb); + } + else if (stAb.getMapParams().get("Mode").equals("SetCost")) { + setAbilities.add(stAb); + } + } + } + // Raise cost + for (final StaticAbility stAb : raiseAbilities) { + stAb.applyAbility("RaiseCost", spell, this); + } + + // Reduce cost + for (final StaticAbility stAb : reduceAbilities) { + stAb.applyAbility("ReduceCost", spell, this); + } + if (spell.isSpell() && spell.isOffering()) { // cost reduction from offerings + adjustCostByOffering(sa, spell); + } + + // Set cost (only used by Trinisphere) is applied last + for (final StaticAbility stAb : setAbilities) { + stAb.applyAbility("SetCost", spell, this); + } + } // GetSpellCostChange + + private void adjustCostByConvoke(final SpellAbility sa) { + + List untappedCreats = CardLists.filter(sa.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); + untappedCreats = CardLists.filter(untappedCreats, CardPredicates.Presets.UNTAPPED); + + Map convokedCards = sa.getActivatingPlayer().getController().chooseCardsForConvoke(sa, this.toManaCost(), untappedCreats); + + // Convoked creats are tapped here with triggers suppressed, + // Then again when payment is done(In InputPayManaCost.done()) with suppression cleared. + // This is to make sure that triggers go off at the right time + // AND that you can't use mana tapabilities of convoked creatures to pay the convoked cost. + for (final Entry conv : convokedCards.entrySet()) { + sa.addTappedForConvoke(conv.getKey()); + this.decreaseShard(conv.getValue(), 1); + conv.getKey().setTapped(true); + } + } + + private void adjustCostByOffering(final SpellAbility sa, final SpellAbility spell) { + String offeringType = ""; + for (String kw : sa.getSourceCard().getKeyword()) { + if (kw.endsWith(" offering")) { + offeringType = kw.split(" ")[0]; + break; + } + } + + Card toSac = null; + List canOffer = CardLists.filter(spell.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), + CardPredicates.isType(offeringType)); + + final List toSacList = sa.getSourceCard().getController().getController().choosePermanentsToSacrifice(spell, 0, 1, canOffer, + offeringType); + + if (!toSacList.isEmpty()) { + toSac = toSacList.get(0); + } + else { + return; + } + + determineManaCostDifference(toSac.getManaCost()); + + sa.setSacrificedAsOffering(toSac); + toSac.setUsedToPay(true); //stop it from interfering with mana input + } + + public String getSourceRestriction() { + return sourceRestriction; + } + + public Iterable getDistinctShards() { + return unpaidShards.keySet(); + } + + public int getUnpaidShards(ManaCostShard key) { + return unpaidShards.count(key); + } +} diff --git a/forge-game/src/main/java/forge/game/mana/ManaPool.java b/forge-gui/src/main/java/forge/game/mana/ManaPool.java similarity index 96% rename from forge-game/src/main/java/forge/game/mana/ManaPool.java rename to forge-gui/src/main/java/forge/game/mana/ManaPool.java index 4331b55630c..dfa87fdbb8e 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaPool.java +++ b/forge-gui/src/main/java/forge/game/mana/ManaPool.java @@ -1,418 +1,418 @@ -/* - * 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 . - */ -package forge.game.mana; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Lists; -import com.google.common.collect.Multimap; -import forge.card.MagicColor; -import forge.card.mana.ManaCostShard; -import forge.game.GlobalRuleChange; -import forge.game.event.EventValueChangeType; -import forge.game.event.GameEventManaPool; -import forge.game.event.GameEventZone; -import forge.game.phase.PhaseType; -import forge.game.player.Player; -import forge.game.spellability.AbilityManaPart; -import forge.game.spellability.SpellAbility; -import forge.game.zone.ZoneType; - -/** - *

- * ManaPool class. - *

- * - * @author Forge - * @version $Id: ManaPool.java 23908 2013-12-07 18:29:49Z drdev $ - */ -public class ManaPool { - - private final Multimap floatingMana = ArrayListMultimap.create(); - - /** Constant map. */ - private final Player owner; - - /** - *

- * Constructor for ManaPool. - *

- * - * @param player - * a {@link forge.game.player.Player} object. - */ - public ManaPool(final Player player) { - owner = player; - } - - public final int getAmountOfColor(final byte color) { - Collection ofColor = floatingMana.get(color); - return ofColor == null ? 0 : ofColor.size(); - } - - private void addMana(final Mana mana) { - floatingMana.put(mana.getColorCode(), mana); - owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Added, mana)); - } - - /** - *

- * addManaToFloating. - *

- * - * @param manaList - * a {@link java.util.ArrayList} object. - */ - public final void add(final Iterable manaList) { - for (final Mana m : manaList) { - this.addMana(m); - } - - // check state effects replaced by checkStaticAbilities - //owner.getGame().getAction().checkStaticAbilities(); - } - - /** - *

- * clearPool. - * - * @return - the amount of mana removed this way - *

- */ - public final int clearPool(boolean isEndOfPhase) { - // isEndOfPhase parameter: true = end of phase, false = mana drain effect - if (this.floatingMana.isEmpty()) { return 0; } - - if (isEndOfPhase && owner.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manapoolsDontEmpty)) { - return 0; - } - - int numRemoved = 0; - boolean keepGreenMana = isEndOfPhase && this.owner.hasKeyword("Green mana doesn't empty from your mana pool as steps and phases end."); - - List keys = Lists.newArrayList(floatingMana.keySet()); - if (keepGreenMana) { - keys.remove(Byte.valueOf(MagicColor.GREEN)); - } - - for (Byte b : keys) { - if (isEndOfPhase && !owner.getGame().getPhaseHandler().is(PhaseType.CLEANUP)) { - final List pMana = new ArrayList(); - for (final Mana mana : this.floatingMana.get(b)) { - if (mana.getManaAbility()!= null && mana.getManaAbility().isPersistentMana()) { - pMana.add(mana); - } - } - numRemoved += floatingMana.get(b).size() - pMana.size(); - floatingMana.get(b).clear(); - floatingMana.putAll(b, pMana); - } - else { - numRemoved += floatingMana.get(b).size(); - floatingMana.get(b).clear(); - } - } - owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Cleared, null)); - return numRemoved; - } - - /** - *

- * getManaFrom. - *

- * - * @param pool - * a {@link java.util.ArrayList} object. - * @param manaStr - * a {@link java.lang.String} object. - * @param saBeingPaidFor - * a {@link forge.game.spellability.SpellAbility} object. - * @return a {@link forge.game.mana.Mana} object. - */ - private Mana getMana(final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction) { - final List> weightedOptions = selectManaToPayFor(shard, saBeingPaidFor, restriction); - - // Exclude border case - if (weightedOptions.isEmpty()) { - return null; // There is no matching mana in the pool - } - - // select equal weight possibilities - List manaChoices = new ArrayList(); - int bestWeight = Integer.MIN_VALUE; - for (Pair option : weightedOptions) { - int thisWeight = option.getRight(); - Mana thisMana = option.getLeft(); - - if (thisWeight > bestWeight) { - manaChoices.clear(); - bestWeight = thisWeight; - } - - if (thisWeight == bestWeight) { - // add only distinct Mana-s - boolean haveDuplicate = false; - for (Mana m : manaChoices) { - if (m.equals(thisMana)) { - haveDuplicate = true; - break; - } - } - if (!haveDuplicate) { - manaChoices.add(thisMana); - } - } - } - - // got an only one best option? - if (manaChoices.size() == 1) { - return manaChoices.get(0); - } - - // Let them choose then - return owner.getController().chooseManaFromPool(manaChoices); - } - - private List> selectManaToPayFor(final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction) { - final List> weightedOptions = new ArrayList>(); - for (final Byte manaKey : this.floatingMana.keySet()) { - if (!shard.canBePaidWithManaOfColor(manaKey.byteValue())) { - continue; - } - - for (final Mana thisMana : this.floatingMana.get(manaKey)) { - if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsManaRestrictions(saBeingPaidFor)) { - continue; - } - - boolean canPay = shard.canBePaidWithManaOfColor(thisMana.getColorCode()); - if (!canPay || (shard.isSnow() && !thisMana.isSnow())) { - continue; - } - - if (StringUtils.isNotBlank(restriction) && !thisMana.getSourceCard().isType(restriction)) { - continue; - } - - // prefer colorless mana to spend - int weight = thisMana.isColorless() ? 5 : 0; - - // prefer restricted mana to spend - if (thisMana.isRestricted()) { - weight += 2; - } - - // Spend non-snow mana first - if (!thisMana.isSnow()) { - weight += 1; - } - - weightedOptions.add(Pair.of(thisMana, weight)); - } - } - return weightedOptions; - } - - /** - *

- * removeManaFrom. - *

- * - * @param pool - * a {@link java.util.ArrayList} object. - * @param choice - * a {@link forge.game.mana.Mana} object. - */ - private void removeMana(final Mana mana) { - Collection cm = floatingMana.get(mana.getColorCode()); - if (cm.remove(mana)) { - owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Removed, mana)); - } - } - - public final void payManaFromPool(final SpellAbility saBeingPaidFor, final ManaCostBeingPaid manaCost, final ManaCostShard manaShard) { - if (manaCost.isPaid()) { - return; - } - - // get a mana of this type from floating, bail if none available - final Mana mana = this.getMana(manaShard, saBeingPaidFor, manaCost.getSourceRestriction()); - if (mana == null) { - return; // no matching mana in the pool - } - else { - tryPayCostWithMana(saBeingPaidFor, manaCost, mana); - } - } - - /** - *

- * subtractManaFromAbility. - *

- * - * @param saPaidFor - * a {@link forge.game.spellability.SpellAbility} object. - * @param manaCost - * a {@link forge.game.mana.ManaCostBeingPaid} object. - * @param saPayment - * a {@link forge.card.spellability.AbilityMana} object. - * @return a {@link forge.game.mana.ManaCostBeingPaid} object. - */ - public final void payManaFromAbility(final SpellAbility saPaidFor, ManaCostBeingPaid manaCost, final SpellAbility saPayment) { - // Mana restriction must be checked before this method is called - - final List paidAbs = saPaidFor.getPayingManaAbilities(); - AbilityManaPart abManaPart = saPayment.getManaPartRecursive(); - - paidAbs.add(saPayment); // assumes some part on the mana produced by the ability will get used - for (final Mana mana : abManaPart.getLastManaProduced()) { - tryPayCostWithMana(saPaidFor, manaCost, mana); - } - } - - private void tryPayCostWithMana(final SpellAbility sa, ManaCostBeingPaid manaCost, final Mana mana) { - if (manaCost.isNeeded(mana)) { - manaCost.payMana(mana); - sa.getPayingMana().add(mana); - this.removeMana(mana); - if (mana.addsNoCounterMagic(sa) && sa.getSourceCard() != null) { - sa.getSourceCard().setCanCounter(false); - } - if (sa.isSpell() && sa.getSourceCard() != null) { - if (sa.getSourceCard().isCreature() && mana.addsKeywords(sa)) { - final long timestamp = sa.getSourceCard().getGame().getNextTimestamp(); - sa.getSourceCard().addChangedCardKeywords(Arrays.asList(mana.getAddedKeywords().split(" & ")), new ArrayList(), false, timestamp); - } - if (mana.addsCounters(sa)) { - mana.getManaAbility().createETBCounters(sa.getSourceCard()); - } - } - } - } - - /** - *

- * totalMana. - *

- * - * @return a int. - */ - public final int totalMana() { - return floatingMana.values().size(); - } - - /** - *

- * clearPay. - *

- * - * @param ability - * a {@link forge.game.spellability.SpellAbility} object. - * @param refund - * a boolean. - */ - public final void clearManaPaid(final SpellAbility ability, final boolean refund) { - final List manaPaid = ability.getPayingMana(); - ability.getPayingManaAbilities().clear(); - // move non-undoable paying mana back to floating - if (refund) { - if (ability.getSourceCard() != null) { - ability.getSourceCard().setCanCounter(true); - } - for (final Mana m : manaPaid) { - this.addMana(m); - } - } - manaPaid.clear(); - } - - //Account for mana part of ability when undoing it - public boolean accountFor(final AbilityManaPart ma) { - if (ma == null) { - return false; - } - if (this.floatingMana.isEmpty()) { - return false; - } - - final ArrayList removeFloating = new ArrayList(); - - boolean manaNotAccountedFor = false; - // loop over mana produced by mana ability - for (Mana mana : ma.getLastManaProduced()) { - Collection poolLane = this.floatingMana.get(mana.getColorCode()); - - if (poolLane != null && poolLane.contains(mana)) { - removeFloating.add(mana); - } - else { - manaNotAccountedFor = true; - break; - } - } - - // When is it legitimate for all the mana not to be accountable? - // Does this condition really indicate an bug in Forge? - if (manaNotAccountedFor) { - return false; - } - - for (int k = 0; k < removeFloating.size(); k++) { - this.removeMana(removeFloating.get(k)); - } - return true; - } - - public final void refundManaPaid(final SpellAbility sa) { - // Send all mana back to your mana pool, before accounting for it. - final List manaPaid = sa.getPayingMana(); - - // move non-undoable paying mana back to floating - if (sa.getSourceCard() != null) { - sa.getSourceCard().setCanCounter(true); - } - for (final Mana m : manaPaid) { - this.addMana(m); - } - manaPaid.clear(); - - List payingAbilities = sa.getPayingManaAbilities(); - for (final SpellAbility am : payingAbilities) { - // undo paying abilities if we can - am.undo(); - } - - for (final SpellAbility am : payingAbilities) { - // Recursively refund abilities that were used. - this.refundManaPaid(am); - } - - payingAbilities.clear(); - - // update battlefield of activating player - to redraw cards used to pay mana as untapped - Player p = sa.getActivatingPlayer(); - p.getGame().fireEvent(new GameEventZone(ZoneType.Battlefield, p, EventValueChangeType.ComplexUpdate, 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 . + */ +package forge.game.mana; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import forge.card.MagicColor; +import forge.card.mana.ManaCostShard; +import forge.game.GlobalRuleChange; +import forge.game.event.EventValueChangeType; +import forge.game.event.GameEventManaPool; +import forge.game.event.GameEventZone; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.spellability.AbilityManaPart; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; + +/** + *

+ * ManaPool class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class ManaPool { + + private final Multimap floatingMana = ArrayListMultimap.create(); + + /** Constant map. */ + private final Player owner; + + /** + *

+ * Constructor for ManaPool. + *

+ * + * @param player + * a {@link forge.game.player.Player} object. + */ + public ManaPool(final Player player) { + owner = player; + } + + public final int getAmountOfColor(final byte color) { + Collection ofColor = floatingMana.get(color); + return ofColor == null ? 0 : ofColor.size(); + } + + private void addMana(final Mana mana) { + floatingMana.put(mana.getColorCode(), mana); + owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Added, mana)); + } + + /** + *

+ * addManaToFloating. + *

+ * + * @param manaList + * a {@link java.util.ArrayList} object. + */ + public final void add(final Iterable manaList) { + for (final Mana m : manaList) { + this.addMana(m); + } + + // check state effects replaced by checkStaticAbilities + //owner.getGame().getAction().checkStaticAbilities(); + } + + /** + *

+ * clearPool. + * + * @return - the amount of mana removed this way + *

+ */ + public final int clearPool(boolean isEndOfPhase) { + // isEndOfPhase parameter: true = end of phase, false = mana drain effect + if (this.floatingMana.isEmpty()) { return 0; } + + if (isEndOfPhase && owner.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manapoolsDontEmpty)) { + return 0; + } + + int numRemoved = 0; + boolean keepGreenMana = isEndOfPhase && this.owner.hasKeyword("Green mana doesn't empty from your mana pool as steps and phases end."); + + List keys = Lists.newArrayList(floatingMana.keySet()); + if (keepGreenMana) { + keys.remove(Byte.valueOf(MagicColor.GREEN)); + } + + for (Byte b : keys) { + if (isEndOfPhase && !owner.getGame().getPhaseHandler().is(PhaseType.CLEANUP)) { + final List pMana = new ArrayList(); + for (final Mana mana : this.floatingMana.get(b)) { + if (mana.getManaAbility()!= null && mana.getManaAbility().isPersistentMana()) { + pMana.add(mana); + } + } + numRemoved += floatingMana.get(b).size() - pMana.size(); + floatingMana.get(b).clear(); + floatingMana.putAll(b, pMana); + } + else { + numRemoved += floatingMana.get(b).size(); + floatingMana.get(b).clear(); + } + } + owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Cleared, null)); + return numRemoved; + } + + /** + *

+ * getManaFrom. + *

+ * + * @param pool + * a {@link java.util.ArrayList} object. + * @param manaStr + * a {@link java.lang.String} object. + * @param saBeingPaidFor + * a {@link forge.game.spellability.SpellAbility} object. + * @return a {@link forge.game.mana.Mana} object. + */ + private Mana getMana(final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction) { + final List> weightedOptions = selectManaToPayFor(shard, saBeingPaidFor, restriction); + + // Exclude border case + if (weightedOptions.isEmpty()) { + return null; // There is no matching mana in the pool + } + + // select equal weight possibilities + List manaChoices = new ArrayList(); + int bestWeight = Integer.MIN_VALUE; + for (Pair option : weightedOptions) { + int thisWeight = option.getRight(); + Mana thisMana = option.getLeft(); + + if (thisWeight > bestWeight) { + manaChoices.clear(); + bestWeight = thisWeight; + } + + if (thisWeight == bestWeight) { + // add only distinct Mana-s + boolean haveDuplicate = false; + for (Mana m : manaChoices) { + if (m.equals(thisMana)) { + haveDuplicate = true; + break; + } + } + if (!haveDuplicate) { + manaChoices.add(thisMana); + } + } + } + + // got an only one best option? + if (manaChoices.size() == 1) { + return manaChoices.get(0); + } + + // Let them choose then + return owner.getController().chooseManaFromPool(manaChoices); + } + + private List> selectManaToPayFor(final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction) { + final List> weightedOptions = new ArrayList>(); + for (final Byte manaKey : this.floatingMana.keySet()) { + if (!shard.canBePaidWithManaOfColor(manaKey.byteValue())) { + continue; + } + + for (final Mana thisMana : this.floatingMana.get(manaKey)) { + if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsManaRestrictions(saBeingPaidFor)) { + continue; + } + + boolean canPay = shard.canBePaidWithManaOfColor(thisMana.getColorCode()); + if (!canPay || (shard.isSnow() && !thisMana.isSnow())) { + continue; + } + + if (StringUtils.isNotBlank(restriction) && !thisMana.getSourceCard().isType(restriction)) { + continue; + } + + // prefer colorless mana to spend + int weight = thisMana.isColorless() ? 5 : 0; + + // prefer restricted mana to spend + if (thisMana.isRestricted()) { + weight += 2; + } + + // Spend non-snow mana first + if (!thisMana.isSnow()) { + weight += 1; + } + + weightedOptions.add(Pair.of(thisMana, weight)); + } + } + return weightedOptions; + } + + /** + *

+ * removeManaFrom. + *

+ * + * @param pool + * a {@link java.util.ArrayList} object. + * @param choice + * a {@link forge.game.mana.Mana} object. + */ + private void removeMana(final Mana mana) { + Collection cm = floatingMana.get(mana.getColorCode()); + if (cm.remove(mana)) { + owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Removed, mana)); + } + } + + public final void payManaFromPool(final SpellAbility saBeingPaidFor, final ManaCostBeingPaid manaCost, final ManaCostShard manaShard) { + if (manaCost.isPaid()) { + return; + } + + // get a mana of this type from floating, bail if none available + final Mana mana = this.getMana(manaShard, saBeingPaidFor, manaCost.getSourceRestriction()); + if (mana == null) { + return; // no matching mana in the pool + } + else { + tryPayCostWithMana(saBeingPaidFor, manaCost, mana); + } + } + + /** + *

+ * subtractManaFromAbility. + *

+ * + * @param saPaidFor + * a {@link forge.game.spellability.SpellAbility} object. + * @param manaCost + * a {@link forge.game.mana.ManaCostBeingPaid} object. + * @param saPayment + * a {@link forge.card.spellability.AbilityMana} object. + * @return a {@link forge.game.mana.ManaCostBeingPaid} object. + */ + public final void payManaFromAbility(final SpellAbility saPaidFor, ManaCostBeingPaid manaCost, final SpellAbility saPayment) { + // Mana restriction must be checked before this method is called + + final List paidAbs = saPaidFor.getPayingManaAbilities(); + AbilityManaPart abManaPart = saPayment.getManaPartRecursive(); + + paidAbs.add(saPayment); // assumes some part on the mana produced by the ability will get used + for (final Mana mana : abManaPart.getLastManaProduced()) { + tryPayCostWithMana(saPaidFor, manaCost, mana); + } + } + + private void tryPayCostWithMana(final SpellAbility sa, ManaCostBeingPaid manaCost, final Mana mana) { + if (manaCost.isNeeded(mana)) { + manaCost.payMana(mana); + sa.getPayingMana().add(mana); + this.removeMana(mana); + if (mana.addsNoCounterMagic(sa) && sa.getSourceCard() != null) { + sa.getSourceCard().setCanCounter(false); + } + if (sa.isSpell() && sa.getSourceCard() != null) { + if (sa.getSourceCard().isCreature() && mana.addsKeywords(sa)) { + final long timestamp = sa.getSourceCard().getGame().getNextTimestamp(); + sa.getSourceCard().addChangedCardKeywords(Arrays.asList(mana.getAddedKeywords().split(" & ")), new ArrayList(), false, timestamp); + } + if (mana.addsCounters(sa)) { + mana.getManaAbility().createETBCounters(sa.getSourceCard()); + } + } + } + } + + /** + *

+ * totalMana. + *

+ * + * @return a int. + */ + public final int totalMana() { + return floatingMana.values().size(); + } + + /** + *

+ * clearPay. + *

+ * + * @param ability + * a {@link forge.game.spellability.SpellAbility} object. + * @param refund + * a boolean. + */ + public final void clearManaPaid(final SpellAbility ability, final boolean refund) { + final List manaPaid = ability.getPayingMana(); + ability.getPayingManaAbilities().clear(); + // move non-undoable paying mana back to floating + if (refund) { + if (ability.getSourceCard() != null) { + ability.getSourceCard().setCanCounter(true); + } + for (final Mana m : manaPaid) { + this.addMana(m); + } + } + manaPaid.clear(); + } + + //Account for mana part of ability when undoing it + public boolean accountFor(final AbilityManaPart ma) { + if (ma == null) { + return false; + } + if (this.floatingMana.isEmpty()) { + return false; + } + + final ArrayList removeFloating = new ArrayList(); + + boolean manaNotAccountedFor = false; + // loop over mana produced by mana ability + for (Mana mana : ma.getLastManaProduced()) { + Collection poolLane = this.floatingMana.get(mana.getColorCode()); + + if (poolLane != null && poolLane.contains(mana)) { + removeFloating.add(mana); + } + else { + manaNotAccountedFor = true; + break; + } + } + + // When is it legitimate for all the mana not to be accountable? + // Does this condition really indicate an bug in Forge? + if (manaNotAccountedFor) { + return false; + } + + for (int k = 0; k < removeFloating.size(); k++) { + this.removeMana(removeFloating.get(k)); + } + return true; + } + + public final void refundManaPaid(final SpellAbility sa) { + // Send all mana back to your mana pool, before accounting for it. + final List manaPaid = sa.getPayingMana(); + + // move non-undoable paying mana back to floating + if (sa.getSourceCard() != null) { + sa.getSourceCard().setCanCounter(true); + } + for (final Mana m : manaPaid) { + this.addMana(m); + } + manaPaid.clear(); + + List payingAbilities = sa.getPayingManaAbilities(); + for (final SpellAbility am : payingAbilities) { + // undo paying abilities if we can + am.undo(); + } + + for (final SpellAbility am : payingAbilities) { + // Recursively refund abilities that were used. + this.refundManaPaid(am); + } + + payingAbilities.clear(); + + // update battlefield of activating player - to redraw cards used to pay mana as untapped + Player p = sa.getActivatingPlayer(); + p.getGame().fireEvent(new GameEventZone(ZoneType.Battlefield, p, EventValueChangeType.ComplexUpdate, null)); + } +} diff --git a/forge-game/src/main/java/forge/game/mana/package-info.java b/forge-gui/src/main/java/forge/game/mana/package-info.java similarity index 94% rename from forge-game/src/main/java/forge/game/mana/package-info.java rename to forge-gui/src/main/java/forge/game/mana/package-info.java index 5af2fd562a2..5ad8c6a2f8a 100644 --- a/forge-game/src/main/java/forge/game/mana/package-info.java +++ b/forge-gui/src/main/java/forge/game/mana/package-info.java @@ -1,3 +1,3 @@ -/** Forge Card Game. */ -package forge.game.mana; - +/** Forge Card Game. */ +package forge.game.mana; + diff --git a/forge-gui/src/main/java/forge/game/package-info.java b/forge-gui/src/main/java/forge/game/package-info.java new file mode 100644 index 00000000000..c1a6d89a2c5 --- /dev/null +++ b/forge-gui/src/main/java/forge/game/package-info.java @@ -0,0 +1,3 @@ +/** Forge Card Game. */ +package forge.game; + diff --git a/forge-game/src/main/java/forge/game/phase/EndOfTurn.java b/forge-gui/src/main/java/forge/game/phase/EndOfTurn.java similarity index 96% rename from forge-game/src/main/java/forge/game/phase/EndOfTurn.java rename to forge-gui/src/main/java/forge/game/phase/EndOfTurn.java index 9a4e2240a8e..62b72d54785 100644 --- a/forge-game/src/main/java/forge/game/phase/EndOfTurn.java +++ b/forge-gui/src/main/java/forge/game/phase/EndOfTurn.java @@ -1,161 +1,161 @@ -/* - * 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 . - */ -package forge.game.phase; - -import forge.card.mana.ManaCost; -import forge.game.Game; -import forge.game.ability.AbilityFactory; -import forge.game.card.Card; -import forge.game.player.GameLossReason; -import forge.game.player.Player; -import forge.game.spellability.Ability; -import forge.game.spellability.SpellAbility; -import forge.game.zone.ZoneType; - - -/** - *

- * Handles "until end of turn" effects and "at end of turn" triggers. - *

- * - * @author Forge - * @version $Id: EndOfTurn.java 24193 2014-01-09 13:46:14Z swordshine $ - */ -public class EndOfTurn extends Phase { - /** Constant serialVersionUID=-3656715295379727275L. */ - private static final long serialVersionUID = -3656715295379727275L; - - protected final Game game; - public EndOfTurn(final Game game) { - super(PhaseType.END_OF_TURN); - this.game = game; - } - /** - *

- * Handles all the hardcoded events that happen "at end of turn". - *

- */ - @Override - public final void executeAt() { - // reset mustAttackEntity for me - game.getPhaseHandler().getPlayerTurn().setMustAttackEntity(null); - - for (final Card c : game.getCardsIn(ZoneType.Battlefield)) { - if (!c.isFaceDown() && c.hasKeyword("At the beginning of the end step, sacrifice CARDNAME.")) { - final Card card = c; - String sb = "Sacrifice CARDNAME."; - String effect = "AB$ Sacrifice | Cost$ 0 | SacValid$ Self"; - - SpellAbility ability = AbilityFactory.getAbility(effect, card); - ability.setActivatingPlayer(card.getController()); - ability.setDescription(sb); - ability.setStackDescription(sb); - ability.setTrigger(true); - final int amount = card.getKeywordAmount("At the beginning of the end step, sacrifice CARDNAME."); - for (int i = 0; i < amount; i++) { - game.getStack().addSimultaneousStackEntry(ability); - } - // Trigger once: sacrifice at the beginning of the next end step - c.removeAllExtrinsicKeyword("At the beginning of the end step, sacrifice CARDNAME."); - c.removeAllExtrinsicKeyword("HIDDEN At the beginning of the end step, sacrifice CARDNAME."); - } - if (!c.isFaceDown() && c.hasKeyword("At the beginning of the end step, exile CARDNAME.")) { - final Card card = c; - String sb = "Exile CARDNAME."; - String effect = "AB$ ChangeZone | Cost$ 0 | Defined$ Self | Origin$ Battlefield | Destination$ Exile"; - - SpellAbility ability = AbilityFactory.getAbility(effect, card); - ability.setActivatingPlayer(card.getController()); - ability.setDescription(sb); - ability.setStackDescription(sb); - ability.setTrigger(true); - final int amount = card.getKeywordAmount("At the beginning of the end step, exile CARDNAME."); - for (int i = 0; i < amount; i++) { - game.getStack().addSimultaneousStackEntry(ability); - } - // Trigger once: exile at the beginning of the next end step - c.removeAllExtrinsicKeyword("At the beginning of the end step, exile CARDNAME."); - c.removeAllExtrinsicKeyword("HIDDEN At the beginning of the end step, exile CARDNAME."); - - } - if (!c.isFaceDown() && c.hasKeyword("At the beginning of the end step, destroy CARDNAME.")) { - final Card card = c; - String sb = "Destroy CARDNAME."; - String effect = "AB$ Destroy | Cost$ 0 | Defined$ Self"; - - SpellAbility ability = AbilityFactory.getAbility(effect, card); - ability.setActivatingPlayer(card.getController()); - ability.setDescription(sb); - ability.setStackDescription(sb); - ability.setTrigger(true); - final int amount = card.getKeywordAmount("At the beginning of the end step, destroy CARDNAME."); - for (int i = 0; i < amount; i++) { - game.getStack().addSimultaneousStackEntry(ability); - } - // Trigger once: destroy at the beginning of the next end step - c.removeAllExtrinsicKeyword("At the beginning of the end step, destroy CARDNAME."); - c.removeAllExtrinsicKeyword("HIDDEN At the beginning of the end step, destroy CARDNAME."); - } - // Berserk is using this, so don't check isFaceDown() - if (c.hasKeyword("At the beginning of the next end step, destroy CARDNAME if it attacked this turn.")) { - if (c.getDamageHistory().getCreatureAttackedThisTurn()) { - final Card card = c; - final SpellAbility sac = new Ability(card, ManaCost.ZERO) { - @Override - public void resolve() { - final Card current = game.getCardState(card); - if (current.isInPlay()) { - game.getAction().destroy(current, this); - } - } - }; - final StringBuilder sb = new StringBuilder(); - sb.append("Destroy ").append(card); - sac.setStackDescription(sb.toString()); - sac.setDescription(sb.toString()); - - game.getStack().addSimultaneousStackEntry(sac); - - } else { - c.removeAllExtrinsicKeyword("At the beginning of the next end step, " - + "destroy CARDNAME if it attacked this turn."); - } - } - - } - Player activePlayer = game.getPhaseHandler().getPlayerTurn(); - if (activePlayer.hasKeyword("At the beginning of this turn's end step, you lose the game.")) { - final Card source = new Card(game.nextCardId()); - final SpellAbility change = new Ability(source, ManaCost.ZERO) { - @Override - public void resolve() { - this.getActivatingPlayer().loseConditionMet(GameLossReason.SpellEffect, ""); - } - }; - change.setStackDescription("At the beginning of this turn's end step, you lose the game."); - change.setDescription("At the beginning of this turn's end step, you lose the game."); - change.setActivatingPlayer(activePlayer); - - game.getStack().addSimultaneousStackEntry(change); - } - - this.execute(this.at); - - } // executeAt() - -} // end class EndOfTurn +/* + * 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 . + */ +package forge.game.phase; + +import forge.card.mana.ManaCost; +import forge.game.Game; +import forge.game.ability.AbilityFactory; +import forge.game.card.Card; +import forge.game.player.GameLossReason; +import forge.game.player.Player; +import forge.game.spellability.Ability; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; + + +/** + *

+ * Handles "until end of turn" effects and "at end of turn" triggers. + *

+ * + * @author Forge + * @version $Id$ + */ +public class EndOfTurn extends Phase { + /** Constant serialVersionUID=-3656715295379727275L. */ + private static final long serialVersionUID = -3656715295379727275L; + + protected final Game game; + public EndOfTurn(final Game game) { + super(PhaseType.END_OF_TURN); + this.game = game; + } + /** + *

+ * Handles all the hardcoded events that happen "at end of turn". + *

+ */ + @Override + public final void executeAt() { + // reset mustAttackEntity for me + game.getPhaseHandler().getPlayerTurn().setMustAttackEntity(null); + + for (final Card c : game.getCardsIn(ZoneType.Battlefield)) { + if (!c.isFaceDown() && c.hasKeyword("At the beginning of the end step, sacrifice CARDNAME.")) { + final Card card = c; + String sb = "Sacrifice CARDNAME."; + String effect = "AB$ Sacrifice | Cost$ 0 | SacValid$ Self"; + + SpellAbility ability = AbilityFactory.getAbility(effect, card); + ability.setActivatingPlayer(card.getController()); + ability.setDescription(sb); + ability.setStackDescription(sb); + ability.setTrigger(true); + final int amount = card.getKeywordAmount("At the beginning of the end step, sacrifice CARDNAME."); + for (int i = 0; i < amount; i++) { + game.getStack().addSimultaneousStackEntry(ability); + } + // Trigger once: sacrifice at the beginning of the next end step + c.removeAllExtrinsicKeyword("At the beginning of the end step, sacrifice CARDNAME."); + c.removeAllExtrinsicKeyword("HIDDEN At the beginning of the end step, sacrifice CARDNAME."); + } + if (!c.isFaceDown() && c.hasKeyword("At the beginning of the end step, exile CARDNAME.")) { + final Card card = c; + String sb = "Exile CARDNAME."; + String effect = "AB$ ChangeZone | Cost$ 0 | Defined$ Self | Origin$ Battlefield | Destination$ Exile"; + + SpellAbility ability = AbilityFactory.getAbility(effect, card); + ability.setActivatingPlayer(card.getController()); + ability.setDescription(sb); + ability.setStackDescription(sb); + ability.setTrigger(true); + final int amount = card.getKeywordAmount("At the beginning of the end step, exile CARDNAME."); + for (int i = 0; i < amount; i++) { + game.getStack().addSimultaneousStackEntry(ability); + } + // Trigger once: exile at the beginning of the next end step + c.removeAllExtrinsicKeyword("At the beginning of the end step, exile CARDNAME."); + c.removeAllExtrinsicKeyword("HIDDEN At the beginning of the end step, exile CARDNAME."); + + } + if (!c.isFaceDown() && c.hasKeyword("At the beginning of the end step, destroy CARDNAME.")) { + final Card card = c; + String sb = "Destroy CARDNAME."; + String effect = "AB$ Destroy | Cost$ 0 | Defined$ Self"; + + SpellAbility ability = AbilityFactory.getAbility(effect, card); + ability.setActivatingPlayer(card.getController()); + ability.setDescription(sb); + ability.setStackDescription(sb); + ability.setTrigger(true); + final int amount = card.getKeywordAmount("At the beginning of the end step, destroy CARDNAME."); + for (int i = 0; i < amount; i++) { + game.getStack().addSimultaneousStackEntry(ability); + } + // Trigger once: destroy at the beginning of the next end step + c.removeAllExtrinsicKeyword("At the beginning of the end step, destroy CARDNAME."); + c.removeAllExtrinsicKeyword("HIDDEN At the beginning of the end step, destroy CARDNAME."); + } + // Berserk is using this, so don't check isFaceDown() + if (c.hasKeyword("At the beginning of the next end step, destroy CARDNAME if it attacked this turn.")) { + if (c.getDamageHistory().getCreatureAttackedThisTurn()) { + final Card card = c; + final SpellAbility sac = new Ability(card, ManaCost.ZERO) { + @Override + public void resolve() { + final Card current = game.getCardState(card); + if (current.isInPlay()) { + game.getAction().destroy(current, this); + } + } + }; + final StringBuilder sb = new StringBuilder(); + sb.append("Destroy ").append(card); + sac.setStackDescription(sb.toString()); + sac.setDescription(sb.toString()); + + game.getStack().addSimultaneousStackEntry(sac); + + } else { + c.removeAllExtrinsicKeyword("At the beginning of the next end step, " + + "destroy CARDNAME if it attacked this turn."); + } + } + + } + Player activePlayer = game.getPhaseHandler().getPlayerTurn(); + if (activePlayer.hasKeyword("At the beginning of this turn's end step, you lose the game.")) { + final Card source = new Card(game.nextCardId()); + final SpellAbility change = new Ability(source, ManaCost.ZERO) { + @Override + public void resolve() { + this.getActivatingPlayer().loseConditionMet(GameLossReason.SpellEffect, ""); + } + }; + change.setStackDescription("At the beginning of this turn's end step, you lose the game."); + change.setDescription("At the beginning of this turn's end step, you lose the game."); + change.setActivatingPlayer(activePlayer); + + game.getStack().addSimultaneousStackEntry(change); + } + + this.execute(this.at); + + } // executeAt() + +} // end class EndOfTurn diff --git a/forge-game/src/main/java/forge/game/phase/ExtraTurn.java b/forge-gui/src/main/java/forge/game/phase/ExtraTurn.java similarity index 100% rename from forge-game/src/main/java/forge/game/phase/ExtraTurn.java rename to forge-gui/src/main/java/forge/game/phase/ExtraTurn.java diff --git a/forge-game/src/main/java/forge/game/phase/Phase.java b/forge-gui/src/main/java/forge/game/phase/Phase.java similarity index 94% rename from forge-game/src/main/java/forge/game/phase/Phase.java rename to forge-gui/src/main/java/forge/game/phase/Phase.java index 356fc6e573b..eb49a90c41f 100644 --- a/forge-game/src/main/java/forge/game/phase/Phase.java +++ b/forge-gui/src/main/java/forge/game/phase/Phase.java @@ -1,141 +1,141 @@ -/* - * 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 . - */ -package forge.game.phase; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import com.google.common.collect.Lists; - -import forge.Command; -import forge.game.player.Player; - - -/** - *

- * Phase class. - *

- * - * @author Forge - * @version $Id: Phase.java 22263 2013-06-24 10:54:46Z Max mtg $ - */ -public class Phase implements java.io.Serializable { - - private static final long serialVersionUID = 4665309652476851977L; - - protected final PhaseType type; // mostly decorative field - it's never used - - public Phase(PhaseType type) { - this.type = type; - } - - /** The at. */ - protected final List at = new ArrayList(); - /** - *

- * Add a hardcoded trigger that will execute "at ". - *

- * - * @param c - * a {@link forge.Command} object. - */ - public final void addAt(final Command c) { - this.at.add(0, c); - } - - /** - *

- * Executes any hardcoded triggers that happen "at ". - *

- */ - public void executeAt() { - this.execute(this.at); - } - - /** The until. */ - private final List until = new ArrayList(); - - /** - *

- * Add a Command that will terminate an effect with "until ". - *

- * - * @param c - * a {@link forge.Command} object. - */ - public final void addUntil(final Command c) { - this.until.add(0, c); - } - - /** - *

- * Executes the termination of effects that apply "until ". - *

- */ - public final void executeUntil() { - this.execute(this.until); - } - - /** The until map. */ - private final HashMap> untilMap = new HashMap>(); - - /** - *

- * Add a Command that will terminate an effect with "until next ". - * Use cleanup phase to terminate an effect with "until next turn" - */ - public final void addUntil(Player p, final Command c) { - if (this.untilMap.containsKey(p)) { - this.untilMap.get(p).add(0, c); - } else { - this.untilMap.put(p, Lists.newArrayList(c)); - } - } - - /** - *

- * Executes the termination of effects that apply "until next ". - *

- * - * @param p - * the player the execute until for - */ - public final void executeUntil(final Player p) { - if (this.untilMap.containsKey(p)) { - this.execute(this.untilMap.get(p)); - } - } - - /** - *

- * execute. - *

- * - * @param c - * a {@link forge.CommandList} object. - */ - protected void execute(final List c) { - final int length = c.size(); - - for (int i = 0; i < length; i++) { - c.remove(0).run(); - } - } - -} //end class Phase +/* + * 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 . + */ +package forge.game.phase; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import com.google.common.collect.Lists; + +import forge.Command; +import forge.game.player.Player; + + +/** + *

+ * Phase class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class Phase implements java.io.Serializable { + + private static final long serialVersionUID = 4665309652476851977L; + + protected final PhaseType type; // mostly decorative field - it's never used + + public Phase(PhaseType type) { + this.type = type; + } + + /** The at. */ + protected final List at = new ArrayList(); + /** + *

+ * Add a hardcoded trigger that will execute "at ". + *

+ * + * @param c + * a {@link forge.Command} object. + */ + public final void addAt(final Command c) { + this.at.add(0, c); + } + + /** + *

+ * Executes any hardcoded triggers that happen "at ". + *

+ */ + public void executeAt() { + this.execute(this.at); + } + + /** The until. */ + private final List until = new ArrayList(); + + /** + *

+ * Add a Command that will terminate an effect with "until ". + *

+ * + * @param c + * a {@link forge.Command} object. + */ + public final void addUntil(final Command c) { + this.until.add(0, c); + } + + /** + *

+ * Executes the termination of effects that apply "until ". + *

+ */ + public final void executeUntil() { + this.execute(this.until); + } + + /** The until map. */ + private final HashMap> untilMap = new HashMap>(); + + /** + *

+ * Add a Command that will terminate an effect with "until next ". + * Use cleanup phase to terminate an effect with "until next turn" + */ + public final void addUntil(Player p, final Command c) { + if (this.untilMap.containsKey(p)) { + this.untilMap.get(p).add(0, c); + } else { + this.untilMap.put(p, Lists.newArrayList(c)); + } + } + + /** + *

+ * Executes the termination of effects that apply "until next ". + *

+ * + * @param p + * the player the execute until for + */ + public final void executeUntil(final Player p) { + if (this.untilMap.containsKey(p)) { + this.execute(this.untilMap.get(p)); + } + } + + /** + *

+ * execute. + *

+ * + * @param c + * a {@link forge.CommandList} object. + */ + protected void execute(final List c) { + final int length = c.size(); + + for (int i = 0; i < length; i++) { + c.remove(0).run(); + } + } + +} //end class Phase diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-gui/src/main/java/forge/game/phase/PhaseHandler.java similarity index 98% rename from forge-game/src/main/java/forge/game/phase/PhaseHandler.java rename to forge-gui/src/main/java/forge/game/phase/PhaseHandler.java index ea7d054be32..47cf432edfa 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-gui/src/main/java/forge/game/phase/PhaseHandler.java @@ -22,13 +22,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; - import org.apache.commons.lang3.time.StopWatch; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; -import forge.PreferencesBridge; +import forge.FThreads; +import forge.Singletons; import forge.card.mana.ManaCost; import forge.game.GameEntity; import forge.game.GameStage; @@ -59,6 +59,7 @@ import forge.game.spellability.SpellAbility; import forge.game.staticability.StaticAbility; import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; +import forge.properties.ForgePreferences.FPref; import forge.util.CollectionSuppliers; import forge.util.maps.HashMapOfLists; import forge.util.maps.MapOfLists; @@ -434,7 +435,7 @@ public class PhaseHandler implements java.io.Serializable { for (Player p : game.getPlayers()) { int burn = p.getManaPool().clearPool(true); - boolean dealDamage = PreferencesBridge.Instance.isManaBurnEnabled(); + boolean dealDamage = Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_MANABURN); if (dealDamage) { p.loseLife(burn); @@ -710,7 +711,8 @@ public class PhaseHandler implements java.io.Serializable { } } - boolean hasPaid = blockCost.getTotalMana().isZero() && blockCost.isOnlyManaCost() && (!hasBlockCost || PreferencesBridge.Instance.areBlocksFree()); // true if needless to pay + boolean hasPaid = blockCost.getTotalMana().isZero() && blockCost.isOnlyManaCost() && (!hasBlockCost + || Singletons.getModel().getPreferences().getPrefBoolean(FPref.MATCHPREF_PROMPT_FREE_BLOCKS)); // true if needless to pay if (!hasPaid) { hasPaid = blocker.getController().getController().payManaOptional(blocker, blockCost, null, "Pay cost to declare " + blocker + " a blocker. ", ManaPaymentPurpose.DeclareBlocker); @@ -959,6 +961,7 @@ public class PhaseHandler implements java.io.Serializable { private final static boolean DEBUG_PHASES = false; public void startFirstTurn(Player goesFirst) { + FThreads.assertExecutedByEdt(false); StopWatch sw = new StopWatch(); if (phase != null) { @@ -973,10 +976,10 @@ public class PhaseHandler implements java.io.Serializable { givePriorityToPlayer = false; while (!game.isGameOver()) { // loop only while is playing -// if (DEBUG_PHASES) { -// System.out.println("\t\tStack: " + game.getStack()); -// System.out.print(FThreads.prependThreadId(debugPrintState(givePriorityToPlayer))); -// } + if (DEBUG_PHASES) { + System.out.println("\t\tStack: " + game.getStack()); + System.out.print(FThreads.prependThreadId(debugPrintState(givePriorityToPlayer))); + } if (givePriorityToPlayer) { if (DEBUG_PHASES) { diff --git a/forge-game/src/main/java/forge/game/phase/PhaseType.java b/forge-gui/src/main/java/forge/game/phase/PhaseType.java similarity index 100% rename from forge-game/src/main/java/forge/game/phase/PhaseType.java rename to forge-gui/src/main/java/forge/game/phase/PhaseType.java diff --git a/forge-game/src/main/java/forge/game/phase/Untap.java b/forge-gui/src/main/java/forge/game/phase/Untap.java similarity index 100% rename from forge-game/src/main/java/forge/game/phase/Untap.java rename to forge-gui/src/main/java/forge/game/phase/Untap.java diff --git a/forge-game/src/main/java/forge/game/phase/Upkeep.java b/forge-gui/src/main/java/forge/game/phase/Upkeep.java similarity index 96% rename from forge-game/src/main/java/forge/game/phase/Upkeep.java rename to forge-gui/src/main/java/forge/game/phase/Upkeep.java index c3542529e55..dee83d99cc5 100644 --- a/forge-game/src/main/java/forge/game/phase/Upkeep.java +++ b/forge-gui/src/main/java/forge/game/phase/Upkeep.java @@ -1,184 +1,184 @@ -/* - * 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 itinksidd 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 . - */ -package forge.game.phase; - -import java.util.HashMap; -import java.util.List; -import com.google.common.base.Predicate; - -import forge.card.mana.ManaCost; -import forge.game.Game; -import forge.game.ability.AbilityFactory; -import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CardLists; -import forge.game.card.CounterType; -import forge.game.cost.Cost; -import forge.game.player.Player; -import forge.game.player.PlayerController.ManaPaymentPurpose; -import forge.game.spellability.Ability; -import forge.game.spellability.SpellAbility; -import forge.game.trigger.TriggerType; -import forge.game.zone.ZoneType; - -/** - *

- * The Upkeep class handles ending effects with "until your next upkeep" and - * "until next upkeep". - * - * It also handles hardcoded triggers "At the beginning of upkeep". - *

- * - * @author Forge - * @version $Id: Upkeep.java 24359 2014-01-19 11:16:54Z Sloth $ - */ -public class Upkeep extends Phase { - private static final long serialVersionUID = 6906459482978819354L; - - protected final Game game; - public Upkeep(final Game game) { - super(PhaseType.UPKEEP); - this.game = game; - } - - /** - *

- * Handles all the hardcoded events that happen at the beginning of each - * Upkeep Phase. - * - * This will freeze the Stack at the start, and unfreeze the Stack at the - * end. - *

- */ - @Override - public final void executeAt() { - - game.getStack().freezeStack(); - - Upkeep.upkeepUpkeepCost(game); // sacrifice unless upkeep cost is paid - Upkeep.upkeepEcho(game); - - game.getStack().unfreezeStack(); - } - - // UPKEEP CARDS: - - /** - *

- * upkeepEcho. - *

- */ - private static void upkeepEcho(final Game game) { - List list = game.getPhaseHandler().getPlayerTurn().getCardsIn(ZoneType.Battlefield); - list = CardLists.filter(list, new Predicate() { - @Override - public boolean apply(final Card c) { - return c.hasStartOfKeyword("(Echo unpaid)"); - } - }); - - for (int i = 0; i < list.size(); i++) { - final Card c = list.get(i); - if (c.hasStartOfKeyword("(Echo unpaid)")) { - final StringBuilder sb = new StringBuilder(); - sb.append("Echo for ").append(c).append("\n"); - String ref = "X".equals(c.getEchoCost()) ? " | References$ X" : ""; - String effect = "AB$ Sacrifice | Cost$ 0 | SacValid$ Self | " - + "UnlessPayer$ You | UnlessCost$ " + c.getEchoCost() - + ref; - - SpellAbility sacAbility = AbilityFactory.getAbility(effect, c); - sacAbility.setTrigger(true); - sacAbility.setActivatingPlayer(c.getController()); - sacAbility.setStackDescription(sb.toString()); - sacAbility.setDescription(sb.toString()); - - game.getStack().addSimultaneousStackEntry(sacAbility); - - c.removeAllExtrinsicKeyword("(Echo unpaid)"); - } - } - } // echo - - /** - *

- * upkeepUpkeepCost. - *

- */ - private static void upkeepUpkeepCost(final Game game) { - - final List list = game.getPhaseHandler().getPlayerTurn().getCardsIn(ZoneType.Battlefield); - - for (int i = 0; i < list.size(); i++) { - final Card c = list.get(i); - final Player controller = c.getController(); - for (String ability : c.getKeyword()) { - - // sacrifice - if (ability.startsWith("At the beginning of your upkeep, sacrifice")) { - - final StringBuilder sb = new StringBuilder("Sacrifice upkeep for " + c); - final String[] k = ability.split(" pay "); - final String cost = k[1].replaceAll("[{]", "").replaceAll("[}]", " "); - - String effect = "AB$ Sacrifice | Cost$ 0 | SacValid$ Self" - + "| UnlessPayer$ You | UnlessCost$ " + cost; - - SpellAbility upkeepAbility = AbilityFactory.getAbility(effect, c); - upkeepAbility.setActivatingPlayer(controller); - upkeepAbility.setStackDescription(sb.toString()); - upkeepAbility.setDescription(sb.toString()); - upkeepAbility.setTrigger(true); - - game.getStack().addSimultaneousStackEntry(upkeepAbility); - } // sacrifice - - // Cumulative upkeep - if (ability.startsWith("Cumulative upkeep")) { - - final StringBuilder sb = new StringBuilder(); - final String[] k = ability.split(":"); - sb.append("Cumulative upkeep for " + c); - - final Ability upkeepAbility = new Ability(c, ManaCost.ZERO) { - @Override - public void resolve() { - c.addCounter(CounterType.AGE, 1, true); - String cost = CardFactoryUtil.multiplyCost(k[1], c.getCounters(CounterType.AGE)); - final Cost upkeepCost = new Cost(cost, true); - boolean isPaid = controller.getController().payManaOptional(c, upkeepCost, this, sb.toString(), ManaPaymentPurpose.CumulativeUpkeep); - final HashMap runParams = new HashMap(); - runParams.put("CumulativeUpkeepPaid", (Boolean) isPaid); - runParams.put("Card", this.getSourceCard()); - game.getTriggerHandler().runTrigger(TriggerType.PayCumulativeUpkeep, runParams, false); - if(!isPaid) - game.getAction().sacrifice(c, null); - } - }; - sb.append("\n"); - upkeepAbility.setActivatingPlayer(controller); - upkeepAbility.setStackDescription(sb.toString()); - upkeepAbility.setDescription(sb.toString()); - - game.getStack().addSimultaneousStackEntry(upkeepAbility); - } // Cumulative upkeep - } - - } // for - } // upkeepCost -} // end class Upkeep +/* + * 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 itinksidd 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 . + */ +package forge.game.phase; + +import java.util.HashMap; +import java.util.List; +import com.google.common.base.Predicate; + +import forge.card.mana.ManaCost; +import forge.game.Game; +import forge.game.ability.AbilityFactory; +import forge.game.card.Card; +import forge.game.card.CardFactoryUtil; +import forge.game.card.CardLists; +import forge.game.card.CounterType; +import forge.game.cost.Cost; +import forge.game.player.Player; +import forge.game.player.PlayerController.ManaPaymentPurpose; +import forge.game.spellability.Ability; +import forge.game.spellability.SpellAbility; +import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; + +/** + *

+ * The Upkeep class handles ending effects with "until your next upkeep" and + * "until next upkeep". + * + * It also handles hardcoded triggers "At the beginning of upkeep". + *

+ * + * @author Forge + * @version $Id$ + */ +public class Upkeep extends Phase { + private static final long serialVersionUID = 6906459482978819354L; + + protected final Game game; + public Upkeep(final Game game) { + super(PhaseType.UPKEEP); + this.game = game; + } + + /** + *

+ * Handles all the hardcoded events that happen at the beginning of each + * Upkeep Phase. + * + * This will freeze the Stack at the start, and unfreeze the Stack at the + * end. + *

+ */ + @Override + public final void executeAt() { + + game.getStack().freezeStack(); + + Upkeep.upkeepUpkeepCost(game); // sacrifice unless upkeep cost is paid + Upkeep.upkeepEcho(game); + + game.getStack().unfreezeStack(); + } + + // UPKEEP CARDS: + + /** + *

+ * upkeepEcho. + *

+ */ + private static void upkeepEcho(final Game game) { + List list = game.getPhaseHandler().getPlayerTurn().getCardsIn(ZoneType.Battlefield); + list = CardLists.filter(list, new Predicate() { + @Override + public boolean apply(final Card c) { + return c.hasStartOfKeyword("(Echo unpaid)"); + } + }); + + for (int i = 0; i < list.size(); i++) { + final Card c = list.get(i); + if (c.hasStartOfKeyword("(Echo unpaid)")) { + final StringBuilder sb = new StringBuilder(); + sb.append("Echo for ").append(c).append("\n"); + String ref = "X".equals(c.getEchoCost()) ? " | References$ X" : ""; + String effect = "AB$ Sacrifice | Cost$ 0 | SacValid$ Self | " + + "UnlessPayer$ You | UnlessCost$ " + c.getEchoCost() + + ref; + + SpellAbility sacAbility = AbilityFactory.getAbility(effect, c); + sacAbility.setTrigger(true); + sacAbility.setActivatingPlayer(c.getController()); + sacAbility.setStackDescription(sb.toString()); + sacAbility.setDescription(sb.toString()); + + game.getStack().addSimultaneousStackEntry(sacAbility); + + c.removeAllExtrinsicKeyword("(Echo unpaid)"); + } + } + } // echo + + /** + *

+ * upkeepUpkeepCost. + *

+ */ + private static void upkeepUpkeepCost(final Game game) { + + final List list = game.getPhaseHandler().getPlayerTurn().getCardsIn(ZoneType.Battlefield); + + for (int i = 0; i < list.size(); i++) { + final Card c = list.get(i); + final Player controller = c.getController(); + for (String ability : c.getKeyword()) { + + // sacrifice + if (ability.startsWith("At the beginning of your upkeep, sacrifice")) { + + final StringBuilder sb = new StringBuilder("Sacrifice upkeep for " + c); + final String[] k = ability.split(" pay "); + final String cost = k[1].replaceAll("[{]", "").replaceAll("[}]", " "); + + String effect = "AB$ Sacrifice | Cost$ 0 | SacValid$ Self" + + "| UnlessPayer$ You | UnlessCost$ " + cost; + + SpellAbility upkeepAbility = AbilityFactory.getAbility(effect, c); + upkeepAbility.setActivatingPlayer(controller); + upkeepAbility.setStackDescription(sb.toString()); + upkeepAbility.setDescription(sb.toString()); + upkeepAbility.setTrigger(true); + + game.getStack().addSimultaneousStackEntry(upkeepAbility); + } // sacrifice + + // Cumulative upkeep + if (ability.startsWith("Cumulative upkeep")) { + + final StringBuilder sb = new StringBuilder(); + final String[] k = ability.split(":"); + sb.append("Cumulative upkeep for " + c); + + final Ability upkeepAbility = new Ability(c, ManaCost.ZERO) { + @Override + public void resolve() { + c.addCounter(CounterType.AGE, 1, true); + String cost = CardFactoryUtil.multiplyCost(k[1], c.getCounters(CounterType.AGE)); + final Cost upkeepCost = new Cost(cost, true); + boolean isPaid = controller.getController().payManaOptional(c, upkeepCost, this, sb.toString(), ManaPaymentPurpose.CumulativeUpkeep); + final HashMap runParams = new HashMap(); + runParams.put("CumulativeUpkeepPaid", (Boolean) isPaid); + runParams.put("Card", this.getSourceCard()); + game.getTriggerHandler().runTrigger(TriggerType.PayCumulativeUpkeep, runParams, false); + if(!isPaid) + game.getAction().sacrifice(c, null); + } + }; + sb.append("\n"); + upkeepAbility.setActivatingPlayer(controller); + upkeepAbility.setStackDescription(sb.toString()); + upkeepAbility.setDescription(sb.toString()); + + game.getStack().addSimultaneousStackEntry(upkeepAbility); + } // Cumulative upkeep + } + + } // for + } // upkeepCost +} // end class Upkeep diff --git a/forge-game/src/main/java/forge/game/phase/package-info.java b/forge-gui/src/main/java/forge/game/phase/package-info.java similarity index 94% rename from forge-game/src/main/java/forge/game/phase/package-info.java rename to forge-gui/src/main/java/forge/game/phase/package-info.java index 935a3bae7d9..c7367dbf9ec 100644 --- a/forge-game/src/main/java/forge/game/phase/package-info.java +++ b/forge-gui/src/main/java/forge/game/phase/package-info.java @@ -1,3 +1,3 @@ -/** Forge Card Game. */ -package forge.game.phase; - +/** Forge Card Game. */ +package forge.game.phase; + diff --git a/forge-game/src/main/java/forge/game/player/GameLossReason.java b/forge-gui/src/main/java/forge/game/player/GameLossReason.java similarity index 100% rename from forge-game/src/main/java/forge/game/player/GameLossReason.java rename to forge-gui/src/main/java/forge/game/player/GameLossReason.java diff --git a/forge-game/src/main/java/forge/game/player/IHasIcon.java b/forge-gui/src/main/java/forge/game/player/IHasIcon.java similarity index 95% rename from forge-game/src/main/java/forge/game/player/IHasIcon.java rename to forge-gui/src/main/java/forge/game/player/IHasIcon.java index 0e973bbc55e..a0674dd26fd 100644 --- a/forge-game/src/main/java/forge/game/player/IHasIcon.java +++ b/forge-gui/src/main/java/forge/game/player/IHasIcon.java @@ -1,6 +1,6 @@ -package forge.game.player; - -public interface IHasIcon { - String getIconImageKey(); - void setIconImageKey(String iconImageKey); -} +package forge.game.player; + +public interface IHasIcon { + String getIconImageKey(); + void setIconImageKey(String iconImageKey); +} diff --git a/forge-game/src/main/java/forge/game/player/LobbyPlayer.java b/forge-gui/src/main/java/forge/game/player/LobbyPlayer.java similarity index 94% rename from forge-game/src/main/java/forge/game/player/LobbyPlayer.java rename to forge-gui/src/main/java/forge/game/player/LobbyPlayer.java index 626331bc0aa..28546e67dea 100644 --- a/forge-game/src/main/java/forge/game/player/LobbyPlayer.java +++ b/forge-gui/src/main/java/forge/game/player/LobbyPlayer.java @@ -9,7 +9,7 @@ import forge.game.Game; * */ public abstract class LobbyPlayer implements IHasIcon { - public enum PlayerType { + protected enum PlayerType { HUMAN, COMPUTER, REMOTE diff --git a/forge-game/src/main/java/forge/game/player/LobbyPlayerAi.java b/forge-gui/src/main/java/forge/game/player/LobbyPlayerAi.java similarity index 86% rename from forge-game/src/main/java/forge/game/player/LobbyPlayerAi.java rename to forge-gui/src/main/java/forge/game/player/LobbyPlayerAi.java index 9821c5fc8be..5c1b2b4bc02 100644 --- a/forge-game/src/main/java/forge/game/player/LobbyPlayerAi.java +++ b/forge-gui/src/main/java/forge/game/player/LobbyPlayerAi.java @@ -1,8 +1,9 @@ package forge.game.player; -import forge.PreferencesBridge; +import forge.Singletons; import forge.ai.AiProfileUtil; import forge.game.Game; +import forge.properties.ForgePreferences.FPref; public class LobbyPlayerAi extends LobbyPlayer { public LobbyPlayerAi(String name) { @@ -34,7 +35,7 @@ public class LobbyPlayerAi extends LobbyPlayer { Player ai = new Player(getName(), game); ai.setFirstController(createControllerFor(ai)); - String currentAiProfile = PreferencesBridge.Instance.getCurrentAiProfile(); + String currentAiProfile = Singletons.getModel().getPreferences().getPref(FPref.UI_CURRENT_AI_PROFILE); String lastProfileChosen = game.getMatch().getPlayedGames().isEmpty() ? currentAiProfile : getAiProfile(); // TODO: implement specific AI profiles for quest mode. diff --git a/forge-gui/src/main/java/forge/net/LobbyPlayerRemote.java b/forge-gui/src/main/java/forge/game/player/LobbyPlayerRemote.java similarity index 87% rename from forge-gui/src/main/java/forge/net/LobbyPlayerRemote.java rename to forge-gui/src/main/java/forge/game/player/LobbyPlayerRemote.java index d8f97ef68af..2907e357593 100644 --- a/forge-gui/src/main/java/forge/net/LobbyPlayerRemote.java +++ b/forge-gui/src/main/java/forge/game/player/LobbyPlayerRemote.java @@ -1,10 +1,6 @@ -package forge.net; +package forge.game.player; import forge.game.Game; -import forge.game.player.LobbyPlayer; -import forge.game.player.Player; -import forge.game.player.PlayerController; - import forge.net.client.INetClient; import forge.net.protocol.toclient.ChatPacketClt; diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-gui/src/main/java/forge/game/player/Player.java similarity index 96% rename from forge-game/src/main/java/forge/game/player/Player.java rename to forge-gui/src/main/java/forge/game/player/Player.java index 6559f898e50..f1d43874c0e 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-gui/src/main/java/forge/game/player/Player.java @@ -1,3302 +1,3305 @@ -/* - * 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 . - */ -package forge.game.player; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.TreeMap; - -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - -import forge.card.MagicColor; -import forge.card.mana.ManaCost; -import forge.game.Game; -import forge.game.GameActionUtil; -import forge.game.GameEntity; -import forge.game.GameStage; -import forge.game.GameType; -import forge.game.GlobalRuleChange; -import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; -import forge.game.card.Card; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; -import forge.game.card.CardPredicates.Presets; -import forge.game.event.GameEventLandPlayed; -import forge.game.event.GameEventMulligan; -import forge.game.event.GameEventPlayerControl; -import forge.game.event.GameEventPlayerDamaged; -import forge.game.event.GameEventPlayerLivesChanged; -import forge.game.event.GameEventPlayerPoisoned; -import forge.game.event.GameEventShuffle; -import forge.game.mana.ManaPool; -import forge.game.phase.PhaseHandler; -import forge.game.phase.PhaseType; -import forge.game.replacement.ReplacementEffect; -import forge.game.replacement.ReplacementHandler; -import forge.game.replacement.ReplacementResult; -import forge.game.spellability.Ability; -import forge.game.spellability.SpellAbility; -import forge.game.staticability.StaticAbility; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerHandler; -import forge.game.trigger.TriggerType; -import forge.game.zone.PlayerZone; -import forge.game.zone.PlayerZoneBattlefield; -import forge.game.zone.Zone; -import forge.game.zone.ZoneType; -import forge.item.IPaperCard; -import forge.util.Lang; -import forge.util.MyRandom; - -/** - *

- * Abstract Player class. - *

- * - * @author Forge - * @version $Id: Player.java 24380 2014-01-20 14:32:16Z swordshine $ - */ -public class Player extends GameEntity implements Comparable { - private final Map commanderDamage = new HashMap(); - - /** The poison counters. */ - private int poisonCounters = 0; - - /** The life. */ - private int life = 20; - - /** The life this player started the game with. */ - private int startingLife = 20; - - /** The assigned damage. */ - private final Map assignedDamage = new HashMap(); - - /** The life lost this turn. */ - private int lifeLostThisTurn = 0; - - /** The life Gained this turn. */ - private int lifeGainedThisTurn = 0; - - /** The num power surge lands. */ - private int numPowerSurgeLands; - - /** The number of times this player has searched his library. */ - private int numLibrarySearchedOwn = 0; - - /** The prowl. */ - private ArrayList prowl = new ArrayList(); - - /** The num lands played. */ - private int numLandsPlayed = 0; - - /** The max hand size. */ - private int maxHandSize = 7; - - /** Starting hand size. */ - private int startingHandSize = 7; - - /** The unlimited hand size. */ - private boolean unlimitedHandSize = false; - - /** The last drawn card. */ - private Card lastDrawnCard = null; - - /** The named card. */ - private String namedCard = ""; - - /** The num drawn this turn. */ - private int numDrawnThisTurn = 0; - private int numDrawnThisDrawStep = 0; - - /** The num discarded this turn. */ - private int numDiscardedThisTurn = 0; - - /** A list of tokens not in play, but on their way. - * This list is kept in order to not break ETB-replacement - * on tokens. */ - private List inboundTokens = new ArrayList(); - - /** The keywords. */ - private ArrayList keywords = new ArrayList(); - - /** The mana pool. */ - private ManaPool manaPool = new ManaPool(this); - - /** The must attack entity. */ - private GameEntity mustAttackEntity = null; - - /** The attackedWithCreatureThisTurn. */ - private boolean attackedWithCreatureThisTurn = false; - - /** The playerAttackCountThisTurn. */ - private int attackersDeclaredThisTurn = 0; - - /** The zones. */ - private final Map zones = new EnumMap(ZoneType.class); - - private List currentPlanes = new ArrayList(); - - private PlayerStatistics stats = new PlayerStatistics(); - protected PlayerController controller; - protected PlayerController controllerCreator = null; - - private int teamNumber = -1; - - private Card activeScheme = null; - - private Card commander = null; - - /** The Constant ALL_ZONES. */ - public static final List ALL_ZONES = Collections.unmodifiableList(Arrays.asList(ZoneType.Battlefield, - ZoneType.Library, ZoneType.Graveyard, ZoneType.Hand, ZoneType.Exile, ZoneType.Command, ZoneType.Ante, - ZoneType.Sideboard, ZoneType.PlanarDeck,ZoneType.SchemeDeck)); - - protected final Game game; - - private boolean triedToDrawFromEmptyLibrary = false; - - private boolean isPlayingExtraTrun = false; - - public boolean canCheatPlayUnlimitedLands = false; - - private List lostOwnership = new ArrayList(); - private List gainedOwnership = new ArrayList(); - - public final PlayerOutcome getOutcome() { - return stats.getOutcome(); - } - - /** - *

- * Constructor for Player. - *

- * - * @param myName - * a {@link java.lang.String} object. - * @param myLife - * a int. - * @param myPoisonCounters - * a int. - */ - public Player(String name, Game game0) { - game = game0; - for (final ZoneType z : Player.ALL_ZONES) { - final PlayerZone toPut = z == ZoneType.Battlefield ? new PlayerZoneBattlefield(z, this) : new PlayerZone(z, this); - this.zones.put(z, toPut); - } - - this.setName(chooseName(name)); - } - - private String chooseName(String originalName) { - String nameCandidate = originalName; - for( int i = 2; i <= 8; i++) { // several tries, not matter how many - boolean haveDuplicates = false; - for( Player p : game.getPlayers()) { - if( p.getName().equals(nameCandidate)) { - haveDuplicates = true; - break; - } - } - if(!haveDuplicates) - return nameCandidate; - nameCandidate = Lang.getOrdinal(i) + " " + originalName; - } - return nameCandidate; - } - - @Override - public Game getGame() { // I'll probably regret about this - return game; - } - - public final PlayerStatistics getStats() { - return stats; - } - - public final void setTeam(int iTeam) { - teamNumber = iTeam; - } - public final int getTeam() { - return teamNumber; - } - - @Deprecated - public boolean isHuman() { return getLobbyPlayer().isHuman(); } - - public boolean isArchenemy() { - - //Only the archenemy has schemes. - return getZone(ZoneType.SchemeDeck).size() > 0; - } - - public void setSchemeInMotion() { - for (final Player p : game.getPlayers()) { - if (p.hasKeyword("Schemes can't be set in motion this turn.")) { - return; - } - } - - // Replacement effects - final HashMap repRunParams = new HashMap(); - repRunParams.put("Event", "SetInMotion"); - repRunParams.put("Affected", this); - - if (game.getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) { - return; - } - - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - activeScheme = getZone(ZoneType.SchemeDeck).get(0); - // gameAction moveTo ? - getZone(ZoneType.SchemeDeck).remove(activeScheme); - this.getZone(ZoneType.Command).add(activeScheme); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); - - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("Scheme", activeScheme); - game.getTriggerHandler().runTrigger(TriggerType.SetInMotion, runParams, false); - } - - /** - *

- * getOpponent. Used by current-generation AI. - *

- * - * @return a {@link forge.game.player.Player} object. - */ - public final Player getOpponent() { - for (Player p : game.getPlayers()) { - if (p.isOpponentOf(this)) - return p; - } - throw new IllegalStateException("No opponents left ingame for " + this); - } - - /** - * - * returns all opponents. - * Should keep player relations somewhere in the match structure - * @return - */ - public final List getOpponents() { - List result = new ArrayList(); - for (Player p : game.getPlayers()) { - if (p.isOpponentOf(this)) - result.add(p); - } - return result; - } - - /** - * returns allied players. - * Should keep player relations somewhere in the match structure - * @return - */ - public final List getAllies() { - List result = new ArrayList(); - for (Player p : game.getPlayers()) { - if (!p.isOpponentOf(this)) - result.add(p); - } - return result; - } - - /** - * returns all other players. - * Should keep player relations somewhere in the match structure - * @return - */ - public final List getAllOtherPlayers() { - List result = new ArrayList(game.getPlayers()); - result.remove(this); - return result; - } - - /** - * returns the weakest opponent (based on life totals). - * Should keep player relations somewhere in the match structure - * @return - */ - public final Player getWeakestOpponent() { - List opponnets = this.getOpponents(); - Player weakest = opponnets.get(0); - for (int i = 1; i < opponnets.size(); i++) { - if (weakest.getLife() > opponnets.get(i).getLife()) { - weakest = opponnets.get(i); - } - } - return weakest; - } - - public boolean isOpponentOf(Player other) { - return other != this && other != null && ( other.teamNumber < 0 || other.teamNumber != this.teamNumber ); - } - - - - - // //////////////////////// - // - // methods for manipulating life - // - // //////////////////////// - - /** - *

- * Setter for the field life. - *

- * - * @param newLife - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean setLife(final int newLife, final Card source) { - boolean change = false; - // rule 118.5 - if (this.life > newLife) { - change = (this.loseLife(this.life - newLife) > 0); - } else if (newLife > this.life) { - change = this.gainLife(newLife - this.life, source); - } else { - // life == newLife - change = false; - } - return change; - } - - /** - * Sets the starting life for a game. Should only be called from - * newGame()'s. - * - * @param startLife - * a int. - */ - public final void setStartingLife(final int startLife) { - this.startingLife = startLife; - this.life = startLife; - } - - /** - *

- * Getter for the field life. - *

- * - * @return a int. - */ - public final int getLife() { - return this.life; - } - - /** - *

- * Getter for the field startingLife. - *

- * - * @return a int. - */ - public final int getStartingLife() { - return this.startingLife; - } - - /** - *

- * gainLife. - *

- * - * @param toGain - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean gainLife(final int toGain, final Card source) { - - // Run any applicable replacement effects. - final HashMap repParams = new HashMap(); - repParams.put("Event", "GainLife"); - repParams.put("Affected", this); - repParams.put("LifeGained", toGain); - repParams.put("Source", source); - if (game.getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { - return false; - } - - boolean newLifeSet = false; - if (!this.canGainLife()) { - return false; - } - final int lifeGain = toGain; - - if (lifeGain > 0) { - int oldLife = life; - this.life += lifeGain; - newLifeSet = true; - this.lifeGainedThisTurn += lifeGain; - - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("Player", this); - runParams.put("LifeAmount", lifeGain); - game.getTriggerHandler().runTrigger(TriggerType.LifeGained, runParams, false); - - game.fireEvent(new GameEventPlayerLivesChanged(this, oldLife, life)); - } else { - System.out.println("Player - trying to gain negative or 0 life"); - } - - return newLifeSet; - } - - /** - *

- * canGainLife. - *

- * - * @return a boolean. - */ - public final boolean canGainLife() { - if (this.hasKeyword("You can't gain life.") || this.hasKeyword("Your life total can't change.")) { - return false; - } - return true; - } - - /** - *

- * loseLife. - *

- * - * @param toLose - * a int. - * @return an int. - */ - public final int loseLife(final int toLose) { - int lifeLost = 0; - if (!this.canLoseLife()) { - return 0; - } - if (toLose > 0) { - int oldLife = life; - this.life -= toLose; - lifeLost = toLose; - game.fireEvent(new GameEventPlayerLivesChanged(this, oldLife, life)); - } else if (toLose == 0) { - // Rule 118.4 - // this is for players being able to pay 0 life - // nothing to do - } else { - System.out.println("Player - trying to lose negative life"); - return 0; - } - - this.lifeLostThisTurn += toLose; - - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("Player", this); - runParams.put("LifeAmount", toLose); - game.getTriggerHandler().runTrigger(TriggerType.LifeLost, runParams, false); - - return lifeLost; - } - - /** - *

- * canLoseLife. - *

- * - * @return a boolean. - */ - public final boolean canLoseLife() { - if (this.hasKeyword("Your life total can't change.")) { - return false; - } - return true; - } - - /** - *

- * canPayLife. - *

- * - * @param lifePayment - * a int. - * @return a boolean. - */ - public final boolean canPayLife(final int lifePayment) { - if (this.life < lifePayment) { - return false; - } - if ((lifePayment > 0) && this.hasKeyword("Your life total can't change.")) { - return false; - } - return true; - } - - /** - *

- * payLife. - *

- * - * @param lifePayment - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean payLife(final int lifePayment, final Card source) { - if (!this.canPayLife(lifePayment)) { - return false; - } - // rule 118.8 - if (this.life >= lifePayment) { - return (this.loseLife(lifePayment) > 0); - } - - return false; - } - - // //////////////////////// - // - // methods for handling damage - // - // //////////////////////// - - // This function handles damage after replacement and prevention effects are - // applied - /** - *

- * addDamageAfterPrevention. - *

- * - * @param damage - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @param isCombat - * a boolean. - * @return whether or not damage was dealt - */ - @Override - public final boolean addDamageAfterPrevention(final int amount, final Card source, final boolean isCombat) { - if (amount <= 0) { - return false; - } - //String additionalLog = ""; - source.addDealtDamageToPlayerThisTurn(this.getName(), amount); - - boolean infect = source.hasKeyword("Infect") - || this.hasKeyword("All damage is dealt to you as though its source had infect."); - - if (infect) { - this.addPoisonCounters(amount, source); - } else { - // Worship does not reduce the damage dealt but changes the effect - // of the damage - if (this.hasKeyword("Damage that would reduce your life total to less than 1 reduces it to 1 instead.") - && this.life <= amount) { - this.loseLife(Math.min(amount, this.life - 1)); - } else { - // rule 118.2. Damage dealt to a player normally causes that - // player to lose that much life. - this.loseLife(amount); - } - } - - if(source.isCommander() && isCombat) - { - if(!commanderDamage.containsKey(source)) - { - commanderDamage.put(source, amount); - } - else - { - commanderDamage.put(source,commanderDamage.get(source) + amount); - } - } - - this.assignedDamage.put(source, amount); - if (source.hasKeyword("Lifelink")) { - source.getController().gainLife(amount, source); - } - source.getDamageHistory().registerDamage(this); - - if (isCombat) { - final ArrayList types = source.getType(); - for (final String type : types) { - source.getController().addProwlType(type); - } - } - - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("DamageSource", source); - runParams.put("DamageTarget", this); - runParams.put("DamageAmount", amount); - runParams.put("IsCombatDamage", isCombat); - game.getTriggerHandler().runTrigger(TriggerType.DamageDone, runParams, false); - - game.fireEvent(new GameEventPlayerDamaged(this, source, amount, isCombat, infect)); - - return true; - } - - // This should be also usable by the AI to forecast an effect (so it must - // not change the game state) - /** - *

- * staticDamagePrevention. - *

- * - * @param damage - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @param isCombat - * a boolean. - * @return a int. - */ - @Override - public final int staticDamagePrevention(final int damage, final Card source, final boolean isCombat, final boolean isTest) { - - if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) { - return damage; - } - - if (isCombat && game.getPhaseHandler().isPreventCombatDamageThisTurn()) { - return 0; - } - - if (this.hasProtectionFrom(source)) { - return 0; - } - - int restDamage = damage; - - for (String kw : source.getKeyword()) { - if (isCombat) { - if (kw.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")) { - return 0; - } - if (kw.equals("Prevent all combat damage that would be dealt by CARDNAME.")) { - return 0; - } - } - if (kw.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) { - return 0; - } - if (kw.equals("Prevent all damage that would be dealt by CARDNAME.")) { - return 0; - } - } - - // Prevent Damage static abilities - for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { - final ArrayList staticAbilities = ca.getStaticAbilities(); - for (final StaticAbility stAb : staticAbilities) { - restDamage = stAb.applyAbility("PreventDamage", source, this, restDamage, isCombat, isTest); - } - } - - if (restDamage > 0) { - return restDamage; - } else { - return 0; - } - } - - // This is usable by the AI to forecast an effect (so it must - // not change the game state) - // 2012/01/02: No longer used in calculating the finalized damage, but - // retained for damageprediction. -Hellfish - /** - *

- * staticReplaceDamage. - *

- * - * @param damage - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @param isCombat - * a boolean. - * @return a int. - */ - @Override - public final int staticReplaceDamage(final int damage, final Card source, final boolean isCombat) { - - int restDamage = damage; - - if (this.hasKeyword("Damage that would reduce your life total to less than 1 reduces it to 1 instead.")) { - restDamage = Math.min(restDamage, this.life - 1); - } - - for (Card c : game.getCardsIn(ZoneType.Battlefield)) { - if (c.getName().equals("Sulfuric Vapors")) { - if (source.isSpell() && source.isRed()) { - restDamage += 1; - } - } else if (c.getName().equals("Pyromancer's Swath")) { - if (c.getController().equals(source.getController()) && (source.isInstant() || source.isSorcery())) { - restDamage += 2; - } - } else if (c.getName().equals("Pyromancer's Gauntlet")) { - if (c.getController().equals(source.getController()) && source.isRed() - && (source.isInstant() || source.isSorcery() || source.isPlaneswalker())) { - restDamage += 2; - } - } else if (c.getName().equals("Furnace of Rath")) { - restDamage += restDamage; - } else if (c.getName().equals("Gratuitous Violence")) { - if (c.getController().equals(source.getController()) && source.isCreature()) { - restDamage += restDamage; - } - } else if (c.getName().equals("Fire Servant")) { - if (c.getController().equals(source.getController()) && source.isRed() - && (source.isInstant() || source.isSorcery())) { - restDamage *= 2; - } - } else if (c.getName().equals("Curse of Bloodletting")) { - if (c.getEnchanting().equals(this)) { - restDamage *= 2; - } - } else if (c.getName().equals("Gisela, Blade of Goldnight")) { - if (!c.getController().equals(this)) { - restDamage *= 2; - } - } else if (c.getName().equals("Inquisitor's Flail")) { - if (isCombat && c.getEquippingCard() != null && c.getEquippingCard().equals(source)) { - restDamage *= 2; - } - } else if (c.getName().equals("Ghosts of the Innocent")) { - restDamage = restDamage / 2; - } else if (c.getName().equals("Benevolent Unicorn")) { - if (source.isSpell()) { - restDamage -= 1; - } - } else if (c.getName().equals("Divine Presence")) { - if (restDamage > 3) { - restDamage = 3; - } - } else if (c.getName().equals("Forethought Amulet")) { - if (c.getController().equals(this) && (source.isInstant() || source.isSorcery()) - && restDamage > 2) { - restDamage = 2; - } - } else if (c.getName().equals("Elderscale Wurm")) { - if (c.getController().equals(this) && this.getLife() - restDamage < 7) { - restDamage = this.getLife() - 7; - if (restDamage < 0) { - restDamage = 0; - } - } - } - } - - return restDamage; - } - - /** - *

- * replaceDamage. - *

- * - * @param damage - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @param isCombat - * a boolean. - * @return a int. - */ - @Override - public final int replaceDamage(final int damage, final Card source, final boolean isCombat) { - - // Replacement effects - final HashMap repParams = new HashMap(); - repParams.put("Event", "DamageDone"); - repParams.put("Affected", this); - repParams.put("DamageSource", source); - repParams.put("DamageAmount", damage); - repParams.put("IsCombat", isCombat); - - if (game.getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { - return 0; - } - - return damage; - } - - /** - *

- * preventDamage. - *

- * - * @param damage - * a int. - * @param source - * a {@link forge.game.card.Card} object. - * @param isCombat - * a boolean. - * @return a int. - */ - @Override - public final int preventDamage(final int damage, final Card source, final boolean isCombat) { - - if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) - || source.hasKeyword("Damage that would be dealt by CARDNAME can't be prevented.")) { - return damage; - } - - int restDamage = damage; - - boolean DEBUGShieldsWithEffects = false; - while (!this.getPreventNextDamageWithEffect().isEmpty() && restDamage != 0) { - TreeMap> shieldMap = this.getPreventNextDamageWithEffect(); - List preventionEffectSources = new ArrayList(shieldMap.keySet()); - Card shieldSource = preventionEffectSources.get(0); - if (preventionEffectSources.size() > 1) { - Map choiceMap = new TreeMap(); - List choices = new ArrayList(); - for (final Card key : preventionEffectSources) { - String effDesc = shieldMap.get(key).get("EffectString"); - int descIndex = effDesc.indexOf("SpellDescription"); - effDesc = effDesc.substring(descIndex + 18); - String shieldDescription = key.toString() + " - " + shieldMap.get(shieldSource).get("ShieldAmount") - + " shields - " + effDesc; - choices.add(shieldDescription); - choiceMap.put(shieldDescription, key); - } - shieldSource = this.getController().chooseProtectionShield(this, choices, choiceMap); - } - if (DEBUGShieldsWithEffects) { - System.out.println("Prevention shield source: " + shieldSource); - } - - int shieldAmount = Integer.valueOf(shieldMap.get(shieldSource).get("ShieldAmount")); - int dmgToBePrevented = Math.min(restDamage, shieldAmount); - if (DEBUGShieldsWithEffects) { - System.out.println("Selected source initial shield amount: " + shieldAmount); - System.out.println("Incoming damage: " + restDamage); - System.out.println("Damage to be prevented: " + dmgToBePrevented); - } - - //Set up ability - SpellAbility shieldSA = null; - String effectAbString = shieldMap.get(shieldSource).get("EffectString"); - effectAbString = effectAbString.replace("PreventedDamage", Integer.toString(dmgToBePrevented)); - effectAbString = effectAbString.replace("ShieldEffectTarget", shieldMap.get(shieldSource).get("ShieldEffectTarget")); - if (DEBUGShieldsWithEffects) { - System.out.println("Final shield ability string: " + effectAbString); - } - shieldSA = AbilityFactory.getAbility(effectAbString, shieldSource); - if (shieldSA.usesTargeting()) { - System.err.println(shieldSource + " - Targeting for prevention shield's effect should be done with initial spell"); - } - - boolean apiIsEffect = (shieldSA.getApi() == ApiType.Effect); - List cardsInCommand = null; - if (apiIsEffect) { - cardsInCommand = this.getGame().getCardsIn(ZoneType.Command); - } - - this.getController().playSpellAbilityNoStack(shieldSA, true); - if (apiIsEffect) { - List newCardsInCommand = this.getGame().getCardsIn(ZoneType.Command); - newCardsInCommand.removeAll(cardsInCommand); - newCardsInCommand.get(0).setSVar("PreventedDamage", "Number$" + Integer.toString(dmgToBePrevented)); - } - this.subtractPreventNextDamageWithEffect(shieldSource, restDamage); - restDamage = restDamage - dmgToBePrevented; - - if (DEBUGShieldsWithEffects) { - System.out.println("Remaining shields: " - + (shieldMap.containsKey(shieldSource) ? shieldMap.get(shieldSource).get("ShieldAmount") : "all shields used")); - System.out.println("Remaining damage: " + restDamage); - } - } - - final HashMap repParams = new HashMap(); - repParams.put("Event", "DamageDone"); - repParams.put("Affected", this); - repParams.put("DamageSource", source); - repParams.put("DamageAmount", damage); - repParams.put("IsCombat", isCombat); - repParams.put("Prevention", true); - - if (game.getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { - return 0; - } - - restDamage = this.staticDamagePrevention(restDamage, source, isCombat, false); - - if (restDamage >= this.getPreventNextDamage()) { - restDamage = restDamage - this.getPreventNextDamage(); - this.setPreventNextDamage(0); - } else { - this.setPreventNextDamage(this.getPreventNextDamage() - restDamage); - restDamage = 0; - } - - return restDamage; - } - - /** - *

- * Setter for the field assignedDamage. - *

- */ - public final void clearAssignedDamage() { - this.assignedDamage.clear(); - } - - /** - *

- * Getter for the field assignedDamage. - *

- * - * @return a int. - */ - public final int getAssignedDamage() { - int num = 0; - for (final Integer value : this.assignedDamage.values()) { - num += value; - } - return num; - } - - public final Iterable getAssignedDamageSources() { - return assignedDamage.keySet(); - } - - /** - *

- * Getter for the field assignedDamage. - *

- * - * @param type - * a string. - * - * @return a int. - */ - public final int getAssignedDamage(final String type) { - final Map valueMap = new HashMap(); - for (final Card c : this.assignedDamage.keySet()) { - if (c.isType(type)) { - valueMap.put(c, this.assignedDamage.get(c)); - } - } - int num = 0; - for (final Integer value : valueMap.values()) { - num += value; - } - return num; - } - - /** - *

- * addCombatDamage. - *

- * - * @param damage - * a int. - * @param source - * a {@link forge.game.card.Card} object. - */ - public final boolean addCombatDamage(final int damage, final Card source) { - - int damageToDo = damage; - - damageToDo = this.replaceDamage(damageToDo, source, true); - damageToDo = this.preventDamage(damageToDo, source, true); - - this.addDamageAfterPrevention(damageToDo, source, true); // damage - // prevention - // is already - // checked - - if (damageToDo > 0) { - GameActionUtil.executeCombatDamageToPlayerEffects(this, source, damageToDo); - return true; - } - return false; - } - - // //////////////////////// - // - // methods for handling Poison counters - // - // //////////////////////// - - /** - *

- * addPoisonCounters. - *

- * - * @param num - * a int. - * @param source - * the source - */ - public final void addPoisonCounters(final int num, final Card source) { - if (!this.hasKeyword("You can't get poison counters")) { - setPoisonCounters(poisonCounters + num, source); - } - } - - /** - *

- * Setter for the field poisonCounters. - *

- * - * @param num - * a int. - * @param source - */ - public final void setPoisonCounters(final int num, Card source) { - int oldPoison = poisonCounters; - this.poisonCounters = num; - game.fireEvent(new GameEventPlayerPoisoned(this, source, oldPoison, num)); - } - - /** - *

- * Getter for the field poisonCounters. - *

- * - * @return a int. - */ - public final int getPoisonCounters() { - return this.poisonCounters; - } - - /** - * Gets the keywords. - * - * @return the keywords - */ - public final ArrayList getKeywords() { - return this.keywords; - } - - /** - * Adds the keyword. - * - * @param keyword - * the keyword - */ - public final void addKeyword(final String keyword) { - this.keywords.add(keyword); - } - - /** - * Removes the keyword. - * - * @param keyword - * the keyword - */ - public final void removeKeyword(final String keyword) { - this.keywords.remove(keyword); - } - - /* - * (non-Javadoc) - * - * @see forge.GameEntity#hasKeyword(java.lang.String) - */ - /** - * Checks for keyword. - * - * @param keyword - * String - * @return boolean - */ - @Override - public final boolean hasKeyword(final String keyword) { - return this.keywords.contains(keyword); - } - - /** - * Can target. - * - * @param sa - * the sa - * @return a boolean - */ - @Override - public final boolean canBeTargetedBy(final SpellAbility sa) { - if (this.hasKeyword("Shroud") || (!this.equals(sa.getActivatingPlayer()) && this.hasKeyword("Hexproof")) - || this.hasProtectionFrom(sa.getSourceCard())) { - return false; - } - - return true; - } - - /* - * (non-Javadoc) - * - * @see forge.GameEntity#hasProtectionFrom(forge.Card) - */ - @Override - public boolean hasProtectionFrom(final Card source) { - if (this.getKeywords() != null) { - final ArrayList list = this.getKeywords(); - - String kw = ""; - for (int i = 0; i < list.size(); i++) { - kw = list.get(i); - - if (kw.equals("Protection from white") && source.isWhite()) { - return true; - } - if (kw.equals("Protection from blue") && source.isBlue()) { - return true; - } - if (kw.equals("Protection from black") && source.isBlack()) { - return true; - } - if (kw.equals("Protection from red") && source.isRed()) { - return true; - } - if (kw.equals("Protection from green") && source.isGreen()) { - return true; - } - - if (kw.startsWith("Protection:")) { // uses isValid - final String characteristic = kw.split(":")[1]; - final String[] characteristics = characteristic.split(","); - if (source.isValid(characteristics, this, null)) { - return true; - } - } - - } - } - return false; - } - - /** - *

- * canPlaySpells. - *

- * - * @return a boolean. - */ - public final boolean canCastSpells() { - return !this.keywords.contains("Can't cast spells"); - } - - /** - *

- * canPlayAbilities. - *

- * - * @return a boolean. - */ - public final boolean canActivateAbilities() { - return !this.keywords.contains("Can't activate abilities"); - } - - // ////////////////////////////// - // / - // / replaces Singletons.getModel().getGameAction().draw* methods - // / - // ////////////////////////////// - - /** - *

- * canDraw - *

- * . - * - * @return true if a player can draw a card, false otherwise - */ - public final boolean canDraw() { - if (this.hasKeyword("You can't draw cards.")) { - return false; - } - if (this.hasKeyword("You can't draw more than one card each turn.")) { - return this.numDrawnThisTurn < 1; - } - return true; - } - - /** - *

- * drawCard. - *

- * - * @return a List of cards actually drawn - */ - public final List drawCard() { - return this.drawCards(1); - } - - - public boolean canMulligan() { - return !getZone(ZoneType.Hand).isEmpty(); - } - - /** - *

- * drawCards. - *

- * - * @param n - * a int. - * @return a List of cards actually drawn - */ - public final List drawCards(final int n) { - final List drawn = new ArrayList(); - - for (int i = 0; i < n; i++) { - -// // TODO: multiple replacements need to be selected by the controller -// List dredgers = this.getDredge(); -// if (!dredgers.isEmpty()) { -// Card toDredge = getController().chooseCardToDredge(dredgers); -// int dredgeNumber = toDredge == null ? Integer.MAX_VALUE : getDredgeNumber(toDredge); -// if ( dredgeNumber <= getZone(ZoneType.Library).size()) { -// game.getAction().moveToHand(toDredge); -// -// for (int iD = 0; iD < dredgeNumber; iD++) { -// final Card c2 = getZone(ZoneType.Library).get(0); -// game.getAction().moveToGraveyard(c2); -// } -// continue; -// } -// } - - if (!this.canDraw()) { - return drawn; - } - drawn.addAll(this.doDraw()); - } - return drawn; - } - - /** - *

- * doDraw. - *

- * - * @return a List of cards actually drawn - */ - private List doDraw() { - final List drawn = new ArrayList(); - final PlayerZone library = this.getZone(ZoneType.Library); - - // Replacement effects - final HashMap repRunParams = new HashMap(); - repRunParams.put("Event", "Draw"); - repRunParams.put("Affected", this); - - if (game.getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) { - return drawn; - } - - // ======== Chains of Mephistopheles hardcode. ========= - // This card requires player to either discard a card, and then he may proceed drawing, or mill 1 - then no draw will happen - // It's oracle-worded as a replacement effect ("If a player would draw a card ... discards a card instead") (rule 419.1a) - // Yet, gatherer's rulings read: The effect is cumulative. If there are two of these on the battlefield, each of them will modify each draw - // That means player isn't supposed to choose one replacement effect out of two (generated by Chains Of Mephistopheles), but both happen. - // So it's not a common replacement effect and has to be handled by special code. - - // This is why the code is placed after any other replacement effects could affect the draw event. - List chainsList = null; - for(Card c : game.getCardsIn(ZoneType.Battlefield)) { - if ( c.getName().equals("Chains of Mephistopheles") ) { - if ( null == chainsList ) - chainsList = new ArrayList(); - chainsList.add(c); - } - } - if (chainsList != null && (numDrawnThisDrawStep > 0 || !game.getPhaseHandler().is(PhaseType.DRAW))) { - for(Card c : chainsList) { - // I have to target this player - don't know how to do it. - - - if (getCardsIn(ZoneType.Hand).isEmpty()) { - SpellAbility saMill = AbilityFactory.getAbility(c.getSVar("MillOne"), c); - saMill.setActivatingPlayer(c.getController()); - saMill.getTargets().add(this); - AbilityUtils.resolve(saMill); - - return drawn; // Draw is cancelled - } else { - SpellAbility saDiscard = AbilityFactory.getAbility(c.getSVar("DiscardOne"), c); - saDiscard.setActivatingPlayer(c.getController()); - saDiscard.getTargets().add(this); - AbilityUtils.resolve(saDiscard); - } - } - } - // End of = Chains of Mephistopheles hardcode. ========= - - if (!library.isEmpty()) { - - Card c = library.get(0); - c = game.getAction().moveToHand(c); - drawn.add(c); - - if (this.numDrawnThisTurn == 0) { - boolean reveal = false; - final List cards = this.getCardsIn(ZoneType.Battlefield); - for (final Card card : cards) { - if (card.hasKeyword("Reveal the first card you draw each turn")) { - reveal = true; - break; - } - } - if (reveal) { - game.getAction().reveal(drawn, this, true, "Revealing the first card drawn from "); - } - } - - this.setLastDrawnCard(c); - c.setDrawnThisTurn(true); - this.numDrawnThisTurn++; - if ( game.getPhaseHandler().is(PhaseType.DRAW)) - this.numDrawnThisDrawStep++; - - // Miracle draws - if (this.numDrawnThisTurn == 1 - && game.getPhaseHandler().getTurn() != 1 - && game.getAge() != GameStage.Mulligan) { - drawMiracle(c); - } - - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("Card", c); - runParams.put("Number", this.numDrawnThisTurn); - runParams.put("Player", this); - game.getTriggerHandler().runTrigger(TriggerType.Drawn, runParams, false); - } - else // Lose by milling is always on. Give AI many cards it cannot play if you want it not to undertake actions - this.triedToDrawFromEmptyLibrary = true; - - return drawn; - } - - /** - * Returns PlayerZone corresponding to the given zone of game. - * - * @param zone - * the zone - * @return the zone - */ - public final PlayerZone getZone(final ZoneType zone) { - return this.zones.get(zone); - } - - public final List getCardsIn(final ZoneType zoneType) { - return getCardsIn(zoneType, true); - } - - /** - * gets a list of all cards in the requested zone. This function makes a - * List from Card[]. - * - * @param zone - * the zone - * @return a List with all the cards currently in requested zone - */ - public final List getCardsIn(final ZoneType zoneType, boolean filterOutPhasedOut) { - List result; - if (zoneType == ZoneType.Stack) { - result = new ArrayList(); - for (Card c : game.getStackZone().getCards()) { - if (c.getOwner().equals(this)) { - result.add(c); - } - } - } else { - PlayerZone zone = this.getZone(zoneType); - result = zone == null ? null : zone.getCards(filterOutPhasedOut); - } - return result; - } - - /** - * Gets the cards include phasing in. - * - * @param zone - * the zone - * @return the cards include phasing in - */ - public final List getCardsIncludePhasingIn(final ZoneType zone) { - return this.getCardsIn(zone, false); - } - - /** - * gets a list of first N cards in the requested zone. This function makes a - * List from Card[]. - * - * @param zone - * the zone - * @param n - * the n - * @return a List with all the cards currently in requested zone - */ - public final List getCardsIn(final ZoneType zone, final int n) { - return Lists.newArrayList(Iterables.limit(this.getCardsIn(zone), n)); - } - - /** - * gets a list of all cards in a given player's requested zones. - * - * @param zones - * the zones - * @return a List with all the cards currently in requested zones - */ - public final List getCardsIn(final Iterable zones) { - final List result = new ArrayList(); - for (final ZoneType z : zones) { - result.addAll(getCardsIn(z)); - } - return result; - } - public final List getCardsIn(final ZoneType[] zones) { - final List result = new ArrayList(); - for (final ZoneType z : zones) { - result.addAll(getCardsIn(z)); - } - return result; - } - - /** - * gets a list of all cards with requested cardName in a given player's - * requested zone. This function makes a List from Card[]. - * - * @param zone - * the zone - * @param cardName - * the card name - * @return a List with all the cards currently in that player's library - */ - public final List getCardsIn(final ZoneType zone, final String cardName) { - return CardLists.filter(this.getCardsIn(zone), CardPredicates.nameEquals(cardName)); - } - - - public List getCardsActivableInExternalZones() { - final List cl = new ArrayList(); - - cl.addAll(this.getZone(ZoneType.Graveyard).getCardsPlayerCanActivate(this)); - cl.addAll(this.getZone(ZoneType.Exile).getCardsPlayerCanActivate(this)); - cl.addAll(this.getZone(ZoneType.Library).getCardsPlayerCanActivate(this)); - cl.addAll(this.getZone(ZoneType.Command).getCardsPlayerCanActivate(this)); - - //External activatables from all opponents - for (final Player opponent : this.getOpponents()) { - cl.addAll(opponent.getZone(ZoneType.Exile).getCardsPlayerCanActivate(this)); - cl.addAll(opponent.getZone(ZoneType.Graveyard).getCardsPlayerCanActivate(this)); - cl.addAll(opponent.getZone(ZoneType.Library).getCardsPlayerCanActivate(this)); - if (opponent.hasKeyword("Play with your hand revealed.")) { - cl.addAll(opponent.getZone(ZoneType.Hand).getCardsPlayerCanActivate(this)); - } - } - - return cl; - } - - /** - * Gets the all cards. - * - * @return the all cards - */ - public final List getAllCards() { - List allExcStack = this.getCardsIn(Player.ALL_ZONES); - allExcStack.addAll(getCardsIn(ZoneType.Stack)); - allExcStack.addAll(inboundTokens); - return allExcStack; - } - - /** - *

- * getDredge. - *

- * - * @return a {@link forge.CardList} object. - */ - protected final List getDredge() { - final List dredge = new ArrayList(); - int cntLibrary = this.getCardsIn(ZoneType.Library).size(); - for (final Card c : this.getCardsIn(ZoneType.Graveyard)) { - int nDr = getDredgeNumber(c); - if (nDr > 0 && cntLibrary >= nDr) { - dredge.add(c); - } - } - return dredge; - } // hasDredge() - - /** - *

- * getDredgeNumber. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a int. - */ - protected final int getDredgeNumber(final Card c) { - for (String s : c.getKeyword()) { - if (s.startsWith("Dredge")) { - return Integer.parseInt("" + s.charAt(s.length() - 1)); - } - } - return 0; - // throw new RuntimeException("Input_Draw : getDredgeNumber() card doesn't have dredge - " + c.getName()); - } // getDredgeNumber() - - /** - *

- * resetNumDrawnThisTurn. - *

- */ - public final void resetNumDrawnThisTurn() { - this.numDrawnThisTurn = 0; - this.numDrawnThisDrawStep = 0; - } - - /** - *

- * Getter for the field numDrawnThisTurn. - *

- * - * @return a int. - */ - public final int getNumDrawnThisTurn() { - return this.numDrawnThisTurn; - } - - /** - *

- * Getter for the field numDrawnThisTurnDrawStep. - *

- * - * @return a int. - */ - public final int numDrawnThisDrawStep() { - return this.numDrawnThisDrawStep; - } - - // ////////////////////////////// - // / - // / replaces Singletons.getModel().getGameAction().discard* methods - // / - // ////////////////////////////// - - /** - *

- * discard. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a {@link forge.CardList} object. - */ - public final boolean discard(final Card c, final SpellAbility sa) { - // TODO: This line should be moved inside CostPayment somehow - /*if (sa != null) { - sa.addCostToHashList(c, "Discarded"); - }*/ - final Card source = sa != null ? sa.getSourceCard() : null; - - // Replacement effects - final HashMap repRunParams = new HashMap(); - repRunParams.put("Event", "Discard"); - repRunParams.put("Card", c); - repRunParams.put("Source", source); - repRunParams.put("Affected", this); - - if (game.getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) { - return false; - } - - boolean discardToTopOfLibrary = null != sa && sa.hasParam("DiscardToTopOfLibrary"); - boolean discardMadness = sa != null && sa.hasParam("Madness"); - - if (discardToTopOfLibrary) { - game.getAction().moveToLibrary(c, 0); - // Play the Discard sound - } else if (discardMadness) { - game.getAction().exile(c); - } else { - game.getAction().moveToGraveyard(c); - // Play the Discard sound - } - this.numDiscardedThisTurn++; - // Run triggers - Card cause = null; - if (sa != null) { - cause = sa.getSourceCard(); - } - final HashMap runParams = new HashMap(); - runParams.put("Player", this); - runParams.put("Card", c); - runParams.put("Cause", cause); - runParams.put("IsMadness", (Boolean) discardMadness); - game.getTriggerHandler().runTrigger(TriggerType.Discarded, runParams, false); - return true; - - } // end doDiscard - - /** - *

- * Getter for the field numDiscardedThisTurn. - *

- * - * @return a int. - */ - public final int getNumDiscardedThisTurn() { - return this.numDiscardedThisTurn; - } - - /** - *

- * Getter for the field numDiscardedThisTurn. - *

- * - * @return a int. - */ - public final void resetNumDiscardedThisTurn() { - this.numDiscardedThisTurn = 0; - } - - /** - *

- * mill. - *

- * - * @param n - * a int. - * @return the card list - */ - public final List mill(final int n) { - return this.mill(n, ZoneType.Graveyard, false); - } - - /** - *

- * mill. - *

- * - * @param n - * a int. - * @param zone - * a {@link java.lang.String} object. - * @param bottom - * a boolean. - * @return the card list - */ - public final List mill(final int n, final ZoneType zone, final boolean bottom) { - final List lib = new ArrayList(this.getCardsIn(ZoneType.Library)); - final List milled = new ArrayList(); - - final int max = Math.min(n, lib.size()); - - final ZoneType destination = this.getZone(zone).getZoneType(); - - for (int i = 0; i < max; i++) { - if (bottom) { - milled.add(game.getAction().moveTo(destination, lib.get(lib.size() - 1))); - } else { - milled.add(game.getAction().moveTo(destination, lib.get(i))); - } - } - - return milled; - } - - // ////////////////////////////// - /** - *

- * shuffle. - *

- */ - public final void shuffle(final SpellAbility sa) { - final List list = Lists.newArrayList(this.getCardsIn(ZoneType.Library)); - - if (list.size() <= 1) { - return; - } - - // overdone but wanted to make sure it was really random - final Random random = MyRandom.getRandom(); - Collections.shuffle(list, random); - Collections.shuffle(list, random); - Collections.shuffle(list, random); - Collections.shuffle(list, random); - Collections.shuffle(list, random); - Collections.shuffle(list, random); - - int s = list.size(); - for (int i = 0; i < s; i++) { - list.add(random.nextInt(s - 1), list.remove(random.nextInt(s))); - } - - Collections.shuffle(list, random); - Collections.shuffle(list, random); - Collections.shuffle(list, random); - Collections.shuffle(list, random); - Collections.shuffle(list, random); - Collections.shuffle(list, random); - - this.getZone(ZoneType.Library).setCards(getController().cheatShuffle(list)); - - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("Player", this); - runParams.put("Source", sa); - game.getTriggerHandler().runTrigger(TriggerType.Shuffled, runParams, false); - - // Play the shuffle sound - game.fireEvent(new GameEventShuffle(this)); - } // shuffle - // ////////////////////////////// - - // ////////////////////////////// - - - // ///////////////////////////// - - /** - *

- * playLand. - *

- * - * @param land - * a {@link forge.game.card.Card} object. - */ - public final boolean playLand(final Card land, final boolean ignoreZoneAndTiming) { - if (this.canPlayLand(land, ignoreZoneAndTiming)) { - land.setController(this, 0); - game.getAction().moveTo(this.getZone(ZoneType.Battlefield), land); - - // play a sound - game.fireEvent(new GameEventLandPlayed(this, land)); - - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("Card", land); - game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, runParams, false); - game.getStack().unfreezeStack(); - this.numLandsPlayed++; - return true; - } - - game.getStack().unfreezeStack(); - return false; - } - - /** - *

- * canPlayLand. - *

- * - * @return a boolean. - */ - public final boolean canPlayLand(final Card land) { - return canPlayLand(land, false); - } - /** - *

- * canPlayLand. - *

- * - * @return a boolean. - */ - public final boolean canPlayLand(Card land, final boolean ignoreZoneAndTiming) { - - if (!ignoreZoneAndTiming && !this.canCastSorcery()) { - return false; - } - - // CantBeCast static abilities - for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { - final ArrayList staticAbilities = ca.getStaticAbilities(); - for (final StaticAbility stAb : staticAbilities) { - if (stAb.applyAbility("CantPlayLand", land, this)) { - return false; - } - } - } - - if( land != null && !ignoreZoneAndTiming) { - if (land.getOwner() != this && !land.hasKeyword("May be played by your opponent")) - return false; - - final Zone zone = game.getZoneOf(land); - if(zone.is(ZoneType.Battlefield) || (!zone.is(ZoneType.Hand) && !land.hasStartOfKeyword("May be played"))) { - return false; - } - } - - // **** Check for land play limit per turn **** - // Dev Mode - if (this.canCheatPlayUnlimitedLands || this.isCardInPlay("Fastbond") || this.isCardInCommand("Naya")) { - return true; - } - - // check for adjusted max lands play per turn - int adjMax = 1; - for (String keyword : this.getKeywords()) { - if (keyword.startsWith("AdjustLandPlays")) { - final String[] k = keyword.split(":"); - adjMax += Integer.valueOf(k[1]); - } - } - if (this.numLandsPlayed < adjMax) { - return true; - } - - return false; - } - - /** - * Gets the mana pool. - * - * @return the mana pool - */ - public final ManaPool getManaPool() { - return this.manaPool; - } - - // ///////////////////////////// - // // - // // properties about the player and his/her cards/game status - // // - // ///////////////////////////// - - /** - *

- * Getter for the field numPowerSurgeLands. - *

- * - * @return a int. - */ - public final int getNumPowerSurgeLands() { - return this.numPowerSurgeLands; - } - - /** - *

- * Setter for the field numPowerSurgeLands. - *

- * - * @param n - * a int. - * @return a int. - */ - public final int setNumPowerSurgeLands(final int n) { - this.numPowerSurgeLands = n; - return this.numPowerSurgeLands; - } - - /** - *

- * Getter for the field lastDrawnCard. - *

- * - * @return a {@link forge.game.card.Card} object. - */ - public final Card getLastDrawnCard() { - return this.lastDrawnCard; - } - - /** - *

- * Setter for the field lastDrawnCard. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a {@link forge.game.card.Card} object. - */ - private final Card setLastDrawnCard(final Card c) { - this.lastDrawnCard = c; - return this.lastDrawnCard; - } - - /** - *

- * Getter for the field namedCard. - *

- * - * @return a String. - */ - public final String getNamedCard() { - return this.namedCard; - } - - /** - *

- * Setter for the field namedCard. - *

- * - * @param s - * a {@link forge.game.card.Card} object. - * @return - * @return a {@link forge.game.card.Card} object. - */ - public final void setNamedCard(final String s) { - this.namedCard = s; - } - /** - *

- * getTurn. - *

- * - * @return a int. - */ - public final int getTurn() { - return this.stats.getTurnsPlayed(); - } - - /** - *

- * incrementTurn. - *

- */ - public final void incrementTurn() { - this.stats.nextTurn(); - } - - /** - *

- * getAttackedWithCreatureThisTurn. - *

- * - * @return a boolean. - */ - public final boolean getAttackedWithCreatureThisTurn() { - return this.attackedWithCreatureThisTurn; - } - - /** - *

- * Setter for the field attackedWithCreatureThisTurn. - *

- * - * @param b - * a boolean. - */ - public final void setAttackedWithCreatureThisTurn(final boolean b) { - this.attackedWithCreatureThisTurn = b; - } - - /** - *

- * Gets the number of attackers declared by Player this turn. - *

- * - * @return a boolean. - */ - public final int getAttackersDeclaredThisTurn() { - return this.attackersDeclaredThisTurn; - } - - /** - *

- * Increase number of attackers declared by Player this turn. - *

- * - */ - public final void incrementAttackersDeclaredThisTurn() { - this.attackersDeclaredThisTurn++; - } - - /** - *

- * Resets number of attackers declared by Player this turn. - *

- * - */ - public final void resetAttackersDeclaredThisTurn() { - this.attackersDeclaredThisTurn = 0; - } - - // Game win/loss - - /** - *

- * altWinConditionMet. - *

- * - * @param sourceName - * the source name - */ - public final void altWinBySpellEffect(final String sourceName) { - if (this.cantWin()) { - System.out.println("Tried to win, but currently can't."); - return; - } - this.setOutcome(PlayerOutcome.altWin(sourceName)); - - } - - /** - *

- * altLoseConditionMet. - *

- * - * @param state - * the state - * @param spellName - * the spell name - * @return a boolean. - */ - public final boolean loseConditionMet(final GameLossReason state, final String spellName) { - if (state != GameLossReason.OpponentWon) { - if (this.cantLose()) { - System.out.println("Tried to lose, but currently can't."); - return false; - } - - // Replacement effects - final HashMap runParams = new HashMap(); - runParams.put("Affected", this); - runParams.put("Event", "GameLoss"); - - if (game.getReplacementHandler().run(runParams) != ReplacementResult.NotReplaced) { - return false; - } - } - - setOutcome(PlayerOutcome.loss(state, spellName)); - return true; - } - - /** - * Concede. - */ - public final void concede() { // No cantLose checks - just lose - setOutcome(PlayerOutcome.concede()); - } - - /** - *

- * cantLose. - *

- * - * @return a boolean. - */ - public final boolean cantLose() { - if (this.getOutcome() != null && this.getOutcome().lossState == GameLossReason.Conceded) { - return false; - } - - return (this.hasKeyword("You can't lose the game.") || this.getOpponent().hasKeyword("You can't win the game.")); - } - - /** - *

- * cantLoseForZeroOrLessLife. - *

- * - * @return a boolean. - */ - public final boolean cantLoseForZeroOrLessLife() { - return (this.hasKeyword("You don't lose the game for having 0 or less life.")); - } - - /** - *

- * cantWin. - *

- * - * @return a boolean. - */ - public final boolean cantWin() { - boolean isAnyOppLoseProof = false; - for (Player p : game.getPlayers()) { - if (p == this || p.getOutcome() != null) { - - continue; // except self and already dead - } - isAnyOppLoseProof |= p.hasKeyword("You can't lose the game."); - } - return this.hasKeyword("You can't win the game.") || isAnyOppLoseProof; - } - - /** - *

- * hasLost. - *

- * - * @return a boolean. - */ - public final boolean checkLoseCondition() { - - // Just in case player already lost - if (this.getOutcome() != null) { - return this.getOutcome().lossState != null; - } - - // Rule 704.5a - If a player has 0 or less life, he or she loses the game. - final boolean hasNoLife = this.getLife() <= 0; - if (hasNoLife && !this.cantLoseForZeroOrLessLife()) { - return this.loseConditionMet(GameLossReason.LifeReachedZero, null); - } - - // Rule 704.5b - If a player attempted to draw a card from a library with no cards in it - // since the last time state-based actions were checked, he or she loses the game. - if (triedToDrawFromEmptyLibrary) { - triedToDrawFromEmptyLibrary = false; // one-shot check - return this.loseConditionMet(GameLossReason.Milled, null); - } - - // Rule 704.5c - If a player has ten or more poison counters, he or she loses the game. - if (this.poisonCounters >= 10) { - return this.loseConditionMet(GameLossReason.Poisoned, null); - } - - if(game.getType() == GameType.Commander) - { - Map cmdDmg = getCommanderDamage(); - for(Card c : cmdDmg.keySet()) - { - if(cmdDmg.get(c) >= 21) - return this.loseConditionMet(GameLossReason.CommanderDamage, null); - } - } - - return false; - } - - public final boolean hasLost() { - return this.getOutcome() != null && this.getOutcome().lossState != null; - } - - public final boolean hasWon() { - if (this.cantWin()) { - return false; - } - // in multiplayer game one player's win is replaced by all other's lose (rule 103.4h) - // so if someone cannot lose, the game appears to continue - - return this.getOutcome() != null && this.getOutcome().lossState == null; - } - - /** - *

- * hasMetalcraft. - *

- * - * @return a boolean. - */ - public final boolean hasMetalcraft() { - final List list = CardLists.filter(this.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.ARTIFACTS); - return list.size() >= 3; - } - - /** - *

- * hasThreshold. - *

- * - * @return a boolean. - */ - public final boolean hasThreshold() { - return this.getZone(ZoneType.Graveyard).size() >= 7; - } - - /** - *

- * hasHellbent. - *

- * - * @return a boolean. - */ - public final boolean hasHellbent() { - return this.getZone(ZoneType.Hand).isEmpty(); - } - - /** - *

- * hasLandfall. - *

- * - * @return a boolean. - */ - public final boolean hasLandfall() { - final List list = this.getZone(ZoneType.Battlefield).getCardsAddedThisTurn(null); - return Iterables.any(list, CardPredicates.Presets.LANDS); - } - - /** - *

- * hasBloodthirst. - *

- * - * @return a boolean. - */ - public final boolean hasBloodthirst() { - for (Player opp : this.getOpponents()) { - if (opp.getAssignedDamage() > 0) { - return true; - } - } - return false; - } - - /** - *

- * getBloodthirstAmount. - *

- * - * @return a int. - */ - public final int getBloodthirstAmount() { - int blood = 0; - for (Player opp : this.getOpponents()) { - blood += opp.getAssignedDamage(); - } - return blood; - } - - /** - *

- * hasProwl. - *

- * - * @param type - * the type - * @return a boolean. - */ - public final boolean hasProwl(final String type) { - if (this.prowl.contains("AllCreatureTypes")) { - return true; - } - return this.prowl.contains(type); - } - - /** - * Adds the prowl type. - * - * @param type - * the type - */ - public final void addProwlType(final String type) { - this.prowl.add(type); - } - - /** - * Reset prowl. - */ - public final void resetProwl() { - this.prowl = new ArrayList(); - } - - public final void setLibrarySearched(final int l) { - this.numLibrarySearchedOwn = l; - } - - public final int getLibrarySearched() { - return this.numLibrarySearchedOwn; - } - public final void incLibrarySearched() { - this.numLibrarySearchedOwn++; - } - - /* - * (non-Javadoc) - * - * @see forge.GameEntity#isValid(java.lang.String, forge.Player, forge.Card) - */ - @Override - public final boolean isValid(final String restriction, final Player sourceController, final Card source) { - - final String[] incR = restriction.split("\\."); - - if (incR[0].equals("Opponent")) { - if (this.equals(sourceController) || !this.isOpponentOf(sourceController)) { - return false; - } - } else if (incR[0].equals("You")) { - if (!this.equals(sourceController)) { - return false; - } - } else if (incR[0].equals("EnchantedController")) { - final GameEntity enchanted = source.getEnchanting(); - if ((enchanted == null) || !(enchanted instanceof Card)) { - return false; - } - final Card enchantedCard = (Card) enchanted; - if (!this.equals(enchantedCard.getController())) { - return false; - } - } else { - if (!incR[0].equals("Player")) { - return false; - } - } - - if (incR.length > 1) { - final String excR = incR[1]; - final String[] exR = excR.split("\\+"); // Exclusive Restrictions - // are ... - for (int j = 0; j < exR.length; j++) { - if (!this.hasProperty(exR[j], sourceController, source)) { - return false; - } - } - } - - return true; - } - - /* - * (non-Javadoc) - * - * @see forge.GameEntity#hasProperty(java.lang.String, forge.Player, - * forge.Card) - */ - @Override - public final boolean hasProperty(final String property, final Player sourceController, final Card source) { - if (property.equals("You")) { - if (!this.equals(sourceController)) { - return false; - } - } else if (property.equals("Opponent")) { - if (this.equals(sourceController) || !this.isOpponentOf(sourceController)) { - return false; - } - } else if (property.equals("OpponentToActive")) { - final Player active = game.getPhaseHandler().getPlayerTurn(); - if (this.equals(active) || !this.isOpponentOf(active)) { - return false; - } - } else if (property.equals("Other")) { - if (this.equals(sourceController)) { - return false; - } - } else if (property.equals("wasDealtDamageBySourceThisGame")) { - if (!source.getDamageHistory().getThisGameDamaged().contains(this)) { - return false; - } - } else if (property.equals("wasDealtDamageBySourceThisTurn")) { - if (!source.getDamageHistory().getThisTurnDamaged().contains(this)) { - return false; - } - } else if (property.equals("attackedBySourceThisCombat")) { - if (game.getCombat() == null || !this.equals(game.getCombat().getDefenderPlayerByAttacker(source))) { - return false; - } - } else if (property.startsWith("wasDealtDamageThisTurn")) { - if (this.assignedDamage.isEmpty()) { - return false; - } - } else if (property.startsWith("LostLifeThisTurn")) { - if (this.lifeLostThisTurn <= 0) { - return false; - } - } else if (property.startsWith("DeclaredAttackerThisTurn")) { - if (this.attackersDeclaredThisTurn <= 0) { - return false; - } - } else if (property.equals("IsRemembered")) { - if (!source.getRemembered().contains(this)) { - return false; - } - } else if (property.equals("IsNotRemembered")) { - if (source.getRemembered().contains(this)) { - return false; - } - } else if (property.startsWith("EnchantedBy")) { - if (!this.getEnchantedBy().contains(source)) { - return false; - } - } else if (property.startsWith("Chosen")) { - if (source.getChosenPlayer() == null || !source.getChosenPlayer().equals(this)) { - return false; - } - } else if (property.startsWith("LifeEquals_")) { - int life = AbilityUtils.calculateAmount(source, property.substring(11), null); - if (this.getLife() != life) { - return false; - } - } else if (property.startsWith("withMore")) { - final String cardType = property.split("sThan")[0].substring(8); - final Player controller = "Active".equals(property.split("sThan")[1]) ? game.getPhaseHandler().getPlayerTurn() : sourceController; - final List oppList = CardLists.filter(this.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(cardType)); - final List yourList = CardLists.filter(controller.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(cardType)); - if (oppList.size() <= yourList.size()) { - return false; - } - } else if (property.startsWith("withAtLeast")) { - final String cardType = property.split("More")[1].split("sThan")[0]; - final int amount = Integer.parseInt(property.substring(11, 12)); - final Player controller = "Active".equals(property.split("sThan")[1]) ? game.getPhaseHandler().getPlayerTurn() : sourceController; - final List oppList = CardLists.filter(this.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(cardType)); - final List yourList = CardLists.filter(controller.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(cardType)); - System.out.println(yourList.size()); - if (oppList.size() < yourList.size() + amount) { - return false; - } - } else if (property.startsWith("hasMore")) { - final Player controller = "Active".equals(property.split("Than")[1]) ? game.getPhaseHandler().getPlayerTurn() : sourceController; - if (property.substring(7).startsWith("Life") && this.getLife() <= controller.getLife()) { - return false; - } else if (property.substring(7).startsWith("CardsInHand") - && this.getCardsIn(ZoneType.Hand).size() <= controller.getCardsIn(ZoneType.Hand).size()) { - return false; - } - } else if (property.startsWith("hasFewer")) { - final Player controller = "Active".equals(property.split("Than")[1]) ? game.getPhaseHandler().getPlayerTurn() : sourceController; - if (property.substring(8).startsWith("CreaturesInYard")) { - final List oppList = CardLists.filter(this.getCardsIn(ZoneType.Graveyard), Presets.CREATURES); - final List yourList = CardLists.filter(controller.getCardsIn(ZoneType.Graveyard), Presets.CREATURES); - if (oppList.size() >= yourList.size()) { - return false; - } - } - } else if (property.startsWith("withMost")) { - if (property.substring(8).equals("Life")) { - int highestLife = this.getLife(); // Negative base just in case a few Lich's are running around - Player healthiest = this; - for (final Player p : game.getPlayers()) { - if (p.getLife() > highestLife) { - highestLife = p.getLife(); - healthiest = p; - } - } - if (!this.equals(healthiest)) { - return false; - } - } - else if (property.substring(8).equals("CardsInHand")) { - int largestHand = 0; - Player withLargestHand = null; - for (final Player p : game.getPlayers()) { - if (p.getCardsIn(ZoneType.Hand).size() > largestHand) { - largestHand = p.getCardsIn(ZoneType.Hand).size(); - withLargestHand = p; - } - } - if (!this.equals(withLargestHand)) { - return false; - } - } - else if (property.substring(8).startsWith("Type")) { - String type = property.split("Type")[1]; - boolean checkOnly = false; - if (type.endsWith("Only")) { - checkOnly = true; - type = type.replace("Only", ""); - } - int typeNum = 0; - List controlmost = new ArrayList(); - for (final Player p : game.getPlayers()) { - final int num = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), type).size(); - if (num > typeNum) { - typeNum = num; - controlmost.clear(); - } - if (num == typeNum) { - controlmost.add(p); - } - } - if (checkOnly && controlmost.size() != 1) { - return false; - } - if (!controlmost.contains(this)) { - return false; - } - } - } else if (property.startsWith("withLowest")) { - if (property.substring(10).equals("Life")) { - int lowestLife = this.getLife(); - List lowestlifep = new ArrayList(); - for (final Player p : game.getPlayers()) { - if (p.getLife() == lowestLife) { - lowestlifep.add(p); - } else if (p.getLife() < lowestLife) { - lowestLife = p.getLife(); - lowestlifep.clear(); - lowestlifep.add(p); - } - } - if (!lowestlifep.contains(this)) { - return false; - } - } - } - - return true; - } - - /** - *

- * getMaxHandSize. - *

- * - * @return a int. - */ - public final int getMaxHandSize() { - return maxHandSize; - } - - /** - *

- * setMaxHandSize. - *

- * - * @param size - * a int. - */ - public final void setMaxHandSize(int size) { - maxHandSize = size; - } - - /** - * @return the unlimitedHandSize - */ - public boolean isUnlimitedHandSize() { - return unlimitedHandSize; - } - - /** - * @param unlimitedHandSize0 the unlimitedHandSize to set - */ - public void setUnlimitedHandSize(boolean unlimited) { - this.unlimitedHandSize = unlimited; - } - - /** - *

- * Getter for the field numLandsPlayed. - *

- * - * @return a int. - */ - public final int getNumLandsPlayed() { - return this.numLandsPlayed; - } - - /** - *

- * Setter for the field numLandsPlayed. - *

- * - * @param n - * a int. - */ - public final void setNumLandsPlayed(final int n) { - this.numLandsPlayed = n; - } - - /** - *

- * Getter for the field lifeGainedThisTurn. - *

- * - * @return a int. - */ - public final int getLifeGainedThisTurn() { - return this.lifeGainedThisTurn; - } - - /** - *

- * Setter for the field lifeGainedThisTurn. - *

- * - * @param n - * a int. - */ - public final void setLifeGainedThisTurn(final int n) { - this.lifeGainedThisTurn = n; - } - - /** - *

- * Getter for the field lifeLostThisTurn. - *

- * - * @return a int. - */ - public final int getLifeLostThisTurn() { - return this.lifeLostThisTurn; - } - - /** - *

- * Setter for the field lifeLostThisTurn. - *

- * - * @param n - * a int. - */ - public final void setLifeLostThisTurn(final int n) { - this.lifeLostThisTurn = n; - } - - /** - * a Player or Planeswalker that this Player must attack if able in an - * upcoming combat. This is cleared at the end of each combat. - * - * @param o - * Player or Planeswalker (Card) to attack - * - * @since 1.1.01 - */ - public final void setMustAttackEntity(final GameEntity o) { - this.mustAttackEntity = o; - } - - /** - * get the Player object or Card (Planeswalker) object that this Player must - * attack this combat. - * - * @return the Player or Card (Planeswalker) - * @since 1.1.01 - */ - public final GameEntity getMustAttackEntity() { - return this.mustAttackEntity; - } - - // ////////////////////////////// - // - // generic Object overrides - // - // /////////////////////////////// - - /** {@inheritDoc} */ - @Override - public final boolean equals(final Object o) { - if (o instanceof Player) { - final Player p1 = (Player) o; - return p1.getName().equals(this.getName()); - } else { - return false; - } - } - - @Override - public int compareTo(Player o) { - if (o == null) { - return +1; - } - int subtractedHash = o.hashCode() - this.hashCode(); - if (subtractedHash == 0) { - return 0; - } - - return Math.abs(subtractedHash) / subtractedHash; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return (41 * (41 + this.getName().hashCode())); - } - - public static class Predicates { - - public static final Predicate NOT_LOST = new Predicate() { - @Override - public boolean apply(Player p) { - return p.getOutcome() == null || p.getOutcome().hasWon(); - } - }; - } - - public static class Accessors { - - public static Function FN_GET_NAME = new Function() { - @Override - public String apply(Player input) { - return input.getName(); - } - }; - - } - - /** - * TODO: Write javadoc for this method. - * @return - */ - public final LobbyPlayer getLobbyPlayer() { - return getController().getLobbyPlayer(); - } - - public final LobbyPlayer getOriginalLobbyPlayer() { - return controllerCreator.getLobbyPlayer(); - } - - public final boolean isMindSlaved() { - return controller.getLobbyPlayer() != controllerCreator.getLobbyPlayer(); - } - - public final void releaseControl() { - if ( controller == controllerCreator ) - return; - - controller = controllerCreator; - game.fireEvent(new GameEventPlayerControl(this, getLobbyPlayer(), null)); - } - - public final void setControllingPlayerController(PlayerController pc) { - LobbyPlayer oldController = getLobbyPlayer(); - controller = pc; - game.fireEvent(new GameEventPlayerControl(this, oldController, pc.getLobbyPlayer())); - } - - private void setOutcome(PlayerOutcome outcome) { - stats.setOutcome(outcome); - } - - /** - * TODO: Write javadoc for this method. - */ - public void onGameOver() { - if (null == stats.getOutcome()) { - - setOutcome(PlayerOutcome.win()); // then won! - } - } - - /** - * use to get a list of creatures in play for a given player. - * - * @param player - * the player to get creatures for - * @return a List containing all creatures a given player has in play - */ - public List getCreaturesInPlay() { - return CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.CREATURES); - } - - /** - * use to get a list of all lands a given player has on the battlefield. - * - * @param player - * the player whose lands we want to get - * @return a List containing all lands the given player has in play - */ - public List getLandsInPlay() { - return CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.LANDS); - } - public boolean isCardInPlay(final String cardName) { - return getZone(ZoneType.Battlefield).contains(CardPredicates.nameEquals(cardName)); - } - - public boolean isCardInCommand(final String cardName) { - return getZone(ZoneType.Command).contains(CardPredicates.nameEquals(cardName)); - } - - public List getColoredCardsInPlay(final String color) { - return CardLists.filter(getCardsIn(ZoneType.Battlefield), CardPredicates.isColor(MagicColor.fromName(color))); - } - - public int getTokenDoublersMagnitude() { - int tokenDoublers = 0; - for (String kw : this.getKeywords()) { - if (kw.equals("TokenDoubler")) { - tokenDoublers++; - } - } - return 1 << tokenDoublers; // pow(a,0) = 1; pow(a,1) = a - } - - public void onCleanupPhase() { - for (Card c : getCardsIn(ZoneType.Hand)) { - c.setDrawnThisTurn(false); - } - resetPreventNextDamage(); - resetPreventNextDamageWithEffect(); - resetNumDrawnThisTurn(); - resetNumDiscardedThisTurn(); - setAttackedWithCreatureThisTurn(false); - setNumLandsPlayed(0); - clearAssignedDamage(); - resetAttackersDeclaredThisTurn(); - } - - public boolean canCastSorcery() { - PhaseHandler now = game.getPhaseHandler(); - return now.isPlayerTurn(this) && now.getPhase().isMain() && game.getStack().isEmpty(); - } - - - /** - *

- * couldCastSorcery. - * for conditions the stack must only have the sa being checked - *

- * - * @param player - * a {@link forge.game.player.Player} object. - * @param sa - * a {@link forge.game.player.SpellAbility} object. - * @return a boolean . - */ - public boolean couldCastSorcery(final SpellAbility sa) { - - final Card source = sa.getRootAbility().getSourceCard(); - boolean onlyThis = true; - - for (final Card card : game.getCardsIn(ZoneType.Stack)) { - if (!card.equals(source)) { - onlyThis = false; - //System.out.println("StackCard: " + card + " vs SourceCard: " + source); - } - } - - PhaseHandler now = game.getPhaseHandler(); - //System.out.println("now.isPlayerTurn(player) - " + now.isPlayerTurn(player)); - //System.out.println("now.getPhase().isMain() - " + now.getPhase().isMain()); - //System.out.println("onlyThis - " + onlyThis); - return onlyThis && now.isPlayerTurn(this) && now.getPhase().isMain(); - } - - /** - * TODO: Write javadoc for this method. - * @return - */ - public final PlayerController getController() { - return controller; - } - - public final void setFirstController(PlayerController ctrlr) { - if (controllerCreator != null) { - throw new IllegalStateException("Controller creator already assigned"); - } - controllerCreator = ctrlr; - controller = ctrlr; - } - - /** - * Run a procedure using a different controller - * @param proc - * @param tempController - */ - public void runWithController(Runnable proc, PlayerController tempController) { - PlayerController oldController = controller; - controller = tempController; - try { - proc.run(); - } finally { - controller = oldController; - } - } - - /** - *

- * skipCombat. - *

- * - * @return a boolean. - */ - public boolean isSkippingCombat() { - if (hasKeyword("Skip your next combat phase.")) { - return true; - } - if (hasKeyword("Skip your combat phase.")) { - return true; - } - if (hasKeyword("Skip all combat phases of your next turn.")) { - removeKeyword("Skip all combat phases of your next turn."); - addKeyword("Skip all combat phases of this turn."); - return true; - } - if (hasKeyword("Skip all combat phases of this turn.")) { - return true; - } - return false; - } - - public int getStartingHandSize() { - - return this.startingHandSize; - } - - public void setStartingHandSize(int shs) { - - this.startingHandSize = shs; - } - - /** - * - * Takes the top plane of the planar deck and put it face up in the command zone. - * Then runs triggers. - */ - public void planeswalk() - { - planeswalkTo(Arrays.asList(getZone(ZoneType.PlanarDeck).get(0))); - } - - /** - * Puts the planes in the argument and puts them face up in the command zone. - * Then runs triggers. - * - * @param destinations The planes to planeswalk to. - */ - public void planeswalkTo(final List destinations) - { - System.out.println(this.getName() + ": planeswalk to " + destinations.toString()); - currentPlanes.addAll(destinations); - - for(Card c : currentPlanes) { - getZone(ZoneType.PlanarDeck).remove(c); - getZone(ZoneType.Command).add(c); - } - - //DBG - //System.out.println("CurrentPlanes: " + currentPlanes); - //System.out.println("ActivePlanes: " + game.getActivePlanes()); - //System.out.println("CommandPlanes: " + getZone(ZoneType.Command).getCards()); - - game.setActivePlanes(currentPlanes); - //Run PlaneswalkedTo triggers here. - HashMap runParams = new HashMap(); - runParams.put("Card", currentPlanes); - game.getTriggerHandler().runTrigger(TriggerType.PlaneswalkedTo, runParams,false); - } - - /** - * - * Puts my currently active planes, if any, at the bottom of my planar deck. - */ - public void leaveCurrentPlane() - { - if(!currentPlanes.isEmpty()) - { - //Run PlaneswalkedFrom triggers here. - HashMap runParams = new HashMap(); - runParams.put("Card", new ArrayList(currentPlanes)); - game.getTriggerHandler().runTrigger(TriggerType.PlaneswalkedFrom, runParams,false); - - for(Card c : currentPlanes) { - game.getZoneOf(c).remove(c); - c.clearControllers(); - getZone(ZoneType.PlanarDeck).add(c); - } - currentPlanes.clear(); - } - - //DBG - //System.out.println("CurrentPlanes: " + currentPlanes); - //System.out.println("ActivePlanes: " + game.getActivePlanes()); - //System.out.println("CommandPlanes: " + getZone(ZoneType.Command).getCards()); - } - - /** - * - * Sets up the first plane of a round. - */ - public void initPlane() - { - Card firstPlane = null; - while(true) - { - firstPlane = getZone(ZoneType.PlanarDeck).get(0); - getZone(ZoneType.PlanarDeck).remove(firstPlane); - if(firstPlane.getType().contains("Phenomenon")) - { - getZone(ZoneType.PlanarDeck).add(firstPlane); - } - else - { - currentPlanes.add(firstPlane); - getZone(ZoneType.Command).add(firstPlane); - break; - } - } - - game.setActivePlanes(currentPlanes); - } - - /** - *

- * resetAttackedThisCombat. - *

- * - * @param player - * a {@link forge.game.player.Player} object. - */ - public final void resetAttackedThisCombat() { - // resets the status of attacked/blocked this phase - List list = CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.CREATURES); - - for (int i = 0; i < list.size(); i++) { - final Card c = list.get(i); - if (c.getDamageHistory().getCreatureAttackedThisCombat()) { - c.getDamageHistory().setCreatureAttackedThisCombat(false); - } - if (c.getDamageHistory().getCreatureBlockedThisCombat()) { - c.getDamageHistory().setCreatureBlockedThisCombat(false); - } - - if (c.getDamageHistory().getCreatureGotBlockedThisCombat()) { - c.getDamageHistory().setCreatureGotBlockedThisCombat(false); - } - } - } - - /** - *

- * drawMiracle. - *

- * - * @param card - * a {@link forge.game.card.Card} object. - */ - public final void drawMiracle(final Card card) { - // Whenever a card with miracle is the first card drawn in a turn, - // you may cast it for it's miracle cost - if (card.getMiracleCost() == null) { - return; - } - - final SpellAbility playForMiracleCost = card.getFirstSpellAbility().copy(); - playForMiracleCost.setPayCosts(card.getMiracleCost()); - playForMiracleCost.setStackDescription(card.getName() + " - Cast via Miracle"); - - // TODO Convert this to a Trigger - final Ability miracleTrigger = new MiracleTrigger(card, ManaCost.ZERO, playForMiracleCost); - miracleTrigger.setStackDescription(card.getName() + " - Miracle."); - miracleTrigger.setActivatingPlayer(card.getOwner()); - miracleTrigger.setTrigger(true); - - game.getStack().add(miracleTrigger); - } - - public boolean isSkippingDraw() { - - if (hasKeyword("Skip your next draw step.")) { - removeKeyword("Skip your next draw step."); - return true; - } - - if (hasKeyword("Skip your draw step.")) { - return true; - } - - return false; - } - - public void addInboundToken(Card c) - { - inboundTokens.add(c); - } - - public void removeInboundToken(Card c) - { - inboundTokens.remove(c); - } - - /** - * TODO: Write javadoc for this type. - * - */ - private final class MiracleTrigger extends Ability { - private final SpellAbility miracle; - - /** - * TODO: Write javadoc for Constructor. - * @param sourceCard - * @param manaCost - * @param miracle - */ - private MiracleTrigger(Card sourceCard, ManaCost manaCost, SpellAbility miracle) { - super(sourceCard, manaCost); - this.miracle = miracle; - } - - @Override - public void resolve() { - miracle.setActivatingPlayer(getSourceCard().getOwner()); - // pay miracle cost here. - getSourceCard().getOwner().getController().playMiracle(miracle, getSourceCard()); - } - } - - /** - * TODO: Write javadoc for this method. - */ - public void onMulliganned() { - game.fireEvent(new GameEventMulligan(this)); // quest listener may interfere here - final int newHand = getCardsIn(ZoneType.Hand).size(); - stats.notifyHasMulliganed(); - stats.notifyOpeningHandSize(newHand); - } - - /** - * @return the commander - */ - public Card getCommander() { - return commander; - } - - /** - * @param commander0 the commander to set - */ - public void setCommander(Card commander0) { - this.commander = commander0; - } - - /** - * @return the commanderDamage - */ - public Map getCommanderDamage() { - return commanderDamage; - } - - /** - * @param b isPlayingExtraTurn to set - */ - public void setExtraTurn(boolean b) { - this.isPlayingExtraTrun = b; - } - - /** - * @return a boolean - */ - public boolean isPlayingExtraTurn() { - return isPlayingExtraTrun; - } - - public void initVariantsZones(RegisteredPlayer registeredPlayer) { - - PlayerZone bf = getZone(ZoneType.Battlefield); - Iterable cards = registeredPlayer.getCardsOnBattlefield(); - if (cards != null) { - for (final IPaperCard cp : cards) { - Card c = Card.fromPaperCard(cp, this); - bf.add(c); - c.setSickness(true); - c.setStartsGameInPlay(true); - } - } - - - PlayerZone com = getZone(ZoneType.Command); - // Mainly for avatar, but might find something else here - for (final IPaperCard cp : registeredPlayer.getCardsInCommand()) { - com.add(Card.fromPaperCard(cp, this)); - } - - // Schemes - List sd = new ArrayList(); - for(IPaperCard cp : registeredPlayer.getSchemes()) - sd.add(Card.fromPaperCard(cp, this)); - if ( !sd.isEmpty()) { - for(Card c : sd) { - getZone(ZoneType.SchemeDeck).add(c); - } - getZone(ZoneType.SchemeDeck).shuffle(); - } - - - // Planes - List l = new ArrayList(); - for(IPaperCard cp : registeredPlayer.getPlanes()) - l.add(Card.fromPaperCard(cp, this)); - if ( !l.isEmpty() ) { - for(Card c : l) { - getZone(ZoneType.PlanarDeck).add(c); - } - getZone(ZoneType.PlanarDeck).shuffle(); - } - - // Commander - if(registeredPlayer.getCommander() != null) - { - Card cmd = Card.fromPaperCard(registeredPlayer.getCommander(), this); - cmd.setCommander(true); - com.add(cmd); - setCommander(cmd); - - final Card eff = new Card(getGame().nextCardId()); - eff.setName("Commander effect"); - eff.addType("Effect"); - eff.setToken(true); - eff.setOwner(this); - eff.setColor(cmd.getColor()); - eff.setImmutable(true); - - eff.setSVar("CommanderMoveReplacement", "DB$ ChangeZone | Origin$ Battlefield,Graveyard,Exile,Library | Destination$ Command | Defined$ ReplacedCard"); - eff.setSVar("DBCommanderIncCast","DB$ StoreSVar | SVar$ CommanderCostRaise | Type$ CountSVar | Expression$ CommanderCostRaise/Plus.2"); - eff.setSVar("CommanderCostRaise","Number$0"); - - Trigger t = TriggerHandler.parseTrigger("Mode$ SpellCast | Static$ True | ValidCard$ Card.YouOwn+IsCommander+wasCastFromCommand | Execute$ DBCommanderIncCast", eff, true); - eff.addTrigger(t); - ReplacementEffect r = ReplacementHandler.parseReplacement("Event$ Moved | Destination$ Graveyard,Exile | ValidCard$ Card.IsCommander+YouOwn | Secondary$ True | Optional$ True | OptionalDecider$ You | ReplaceWith$ CommanderMoveReplacement | Description$ If a commander would be put into its owner's graveyard or exile from anywhere, that player may put it into the command zone instead.", eff, true); - eff.addReplacementEffect(r); - eff.addStaticAbility("Mode$ Continuous | EffectZone$ Command | AddKeyword$ May be played | Affected$ Card.YouOwn+IsCommander | AffectedZone$ Command"); - eff.addStaticAbility("Mode$ RaiseCost | EffectZone$ Command | Amount$ CommanderCostRaise | Type$ Spell | ValidCard$ Card.YouOwn+IsCommander+wasCastFromCommand | EffectZone$ All | AffectedZone$ Command,Stack"); - - getZone(ZoneType.Command).add(eff); - } - - } - - public void changeOwnership(Card card) { - // If lost then gained, just clear out of lost. - // If gained then lost, just clear out of gained. - Player oldOwner = card.getOwner(); - - if (this.equals(oldOwner)) { - return; - } - card.setOwner(this); - - if (this.lostOwnership.contains(card)) { - this.lostOwnership.remove(card); - } else { - this.gainedOwnership.add(card); - } - - if (oldOwner.gainedOwnership.contains(card)) { - oldOwner.gainedOwnership.remove(card); - } else { - oldOwner.lostOwnership.add(card); - } - } - - public List getLostOwnership() { - return lostOwnership; - } - - public List getGainedOwnership() { - return gainedOwnership; - } -} +/* + * 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 . + */ +package forge.game.player; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.TreeMap; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import forge.FThreads; +import forge.card.MagicColor; +import forge.card.mana.ManaCost; +import forge.game.Game; +import forge.game.GameActionUtil; +import forge.game.GameEntity; +import forge.game.GameStage; +import forge.game.GameType; +import forge.game.GlobalRuleChange; +import forge.game.ability.AbilityFactory; +import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; +import forge.game.card.Card; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +import forge.game.card.CardPredicates.Presets; +import forge.game.event.GameEventLandPlayed; +import forge.game.event.GameEventMulligan; +import forge.game.event.GameEventPlayerControl; +import forge.game.event.GameEventPlayerDamaged; +import forge.game.event.GameEventPlayerLivesChanged; +import forge.game.event.GameEventPlayerPoisoned; +import forge.game.event.GameEventShuffle; +import forge.game.mana.ManaPool; +import forge.game.phase.PhaseHandler; +import forge.game.phase.PhaseType; +import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementHandler; +import forge.game.replacement.ReplacementResult; +import forge.game.spellability.Ability; +import forge.game.spellability.SpellAbility; +import forge.game.staticability.StaticAbility; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerHandler; +import forge.game.trigger.TriggerType; +import forge.game.zone.PlayerZone; +import forge.game.zone.PlayerZoneBattlefield; +import forge.game.zone.Zone; +import forge.game.zone.ZoneType; +import forge.item.IPaperCard; +import forge.util.Lang; +import forge.util.MyRandom; + +/** + *

+ * Abstract Player class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class Player extends GameEntity implements Comparable { + private final Map commanderDamage = new HashMap(); + + /** The poison counters. */ + private int poisonCounters = 0; + + /** The life. */ + private int life = 20; + + /** The life this player started the game with. */ + private int startingLife = 20; + + /** The assigned damage. */ + private final Map assignedDamage = new HashMap(); + + /** The life lost this turn. */ + private int lifeLostThisTurn = 0; + + /** The life Gained this turn. */ + private int lifeGainedThisTurn = 0; + + /** The num power surge lands. */ + private int numPowerSurgeLands; + + /** The number of times this player has searched his library. */ + private int numLibrarySearchedOwn = 0; + + /** The prowl. */ + private ArrayList prowl = new ArrayList(); + + /** The num lands played. */ + private int numLandsPlayed = 0; + + /** The max hand size. */ + private int maxHandSize = 7; + + /** Starting hand size. */ + private int startingHandSize = 7; + + /** The unlimited hand size. */ + private boolean unlimitedHandSize = false; + + /** The last drawn card. */ + private Card lastDrawnCard = null; + + /** The named card. */ + private String namedCard = ""; + + /** The num drawn this turn. */ + private int numDrawnThisTurn = 0; + private int numDrawnThisDrawStep = 0; + + /** The num discarded this turn. */ + private int numDiscardedThisTurn = 0; + + /** A list of tokens not in play, but on their way. + * This list is kept in order to not break ETB-replacement + * on tokens. */ + private List inboundTokens = new ArrayList(); + + /** The keywords. */ + private ArrayList keywords = new ArrayList(); + + /** The mana pool. */ + private ManaPool manaPool = new ManaPool(this); + + /** The must attack entity. */ + private GameEntity mustAttackEntity = null; + + /** The attackedWithCreatureThisTurn. */ + private boolean attackedWithCreatureThisTurn = false; + + /** The playerAttackCountThisTurn. */ + private int attackersDeclaredThisTurn = 0; + + /** The zones. */ + private final Map zones = new EnumMap(ZoneType.class); + + private List currentPlanes = new ArrayList(); + + private PlayerStatistics stats = new PlayerStatistics(); + protected PlayerController controller; + protected PlayerController controllerCreator = null; + + private int teamNumber = -1; + + private Card activeScheme = null; + + private Card commander = null; + + /** The Constant ALL_ZONES. */ + public static final List ALL_ZONES = Collections.unmodifiableList(Arrays.asList(ZoneType.Battlefield, + ZoneType.Library, ZoneType.Graveyard, ZoneType.Hand, ZoneType.Exile, ZoneType.Command, ZoneType.Ante, + ZoneType.Sideboard, ZoneType.PlanarDeck,ZoneType.SchemeDeck)); + + protected final Game game; + + private boolean triedToDrawFromEmptyLibrary = false; + + private boolean isPlayingExtraTrun = false; + + public boolean canCheatPlayUnlimitedLands = false; + + private List lostOwnership = new ArrayList(); + private List gainedOwnership = new ArrayList(); + + public final PlayerOutcome getOutcome() { + return stats.getOutcome(); + } + + /** + *

+ * Constructor for Player. + *

+ * + * @param myName + * a {@link java.lang.String} object. + * @param myLife + * a int. + * @param myPoisonCounters + * a int. + */ + public Player(String name, Game game0) { + game = game0; + for (final ZoneType z : Player.ALL_ZONES) { + final PlayerZone toPut = z == ZoneType.Battlefield ? new PlayerZoneBattlefield(z, this) : new PlayerZone(z, this); + this.zones.put(z, toPut); + } + + this.setName(chooseName(name)); + } + + private String chooseName(String originalName) { + String nameCandidate = originalName; + for( int i = 2; i <= 8; i++) { // several tries, not matter how many + boolean haveDuplicates = false; + for( Player p : game.getPlayers()) { + if( p.getName().equals(nameCandidate)) { + haveDuplicates = true; + break; + } + } + if(!haveDuplicates) + return nameCandidate; + nameCandidate = Lang.getOrdinal(i) + " " + originalName; + } + return nameCandidate; + } + + @Override + public Game getGame() { // I'll probably regret about this + return game; + } + + public final PlayerStatistics getStats() { + return stats; + } + + public final void setTeam(int iTeam) { + teamNumber = iTeam; + } + public final int getTeam() { + return teamNumber; + } + + @Deprecated + public boolean isHuman() { return getLobbyPlayer().isHuman(); } + + public boolean isArchenemy() { + + //Only the archenemy has schemes. + return getZone(ZoneType.SchemeDeck).size() > 0; + } + + public void setSchemeInMotion() { + for (final Player p : game.getPlayers()) { + if (p.hasKeyword("Schemes can't be set in motion this turn.")) { + return; + } + } + + // Replacement effects + final HashMap repRunParams = new HashMap(); + repRunParams.put("Event", "SetInMotion"); + repRunParams.put("Affected", this); + + if (game.getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) { + return; + } + + game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); + activeScheme = getZone(ZoneType.SchemeDeck).get(0); + // gameAction moveTo ? + getZone(ZoneType.SchemeDeck).remove(activeScheme); + this.getZone(ZoneType.Command).add(activeScheme); + game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Scheme", activeScheme); + game.getTriggerHandler().runTrigger(TriggerType.SetInMotion, runParams, false); + } + + /** + *

+ * getOpponent. Used by current-generation AI. + *

+ * + * @return a {@link forge.game.player.Player} object. + */ + public final Player getOpponent() { + for (Player p : game.getPlayers()) { + if (p.isOpponentOf(this)) + return p; + } + throw new IllegalStateException("No opponents left ingame for " + this); + } + + /** + * + * returns all opponents. + * Should keep player relations somewhere in the match structure + * @return + */ + public final List getOpponents() { + List result = new ArrayList(); + for (Player p : game.getPlayers()) { + if (p.isOpponentOf(this)) + result.add(p); + } + return result; + } + + /** + * returns allied players. + * Should keep player relations somewhere in the match structure + * @return + */ + public final List getAllies() { + List result = new ArrayList(); + for (Player p : game.getPlayers()) { + if (!p.isOpponentOf(this)) + result.add(p); + } + return result; + } + + /** + * returns all other players. + * Should keep player relations somewhere in the match structure + * @return + */ + public final List getAllOtherPlayers() { + List result = new ArrayList(game.getPlayers()); + result.remove(this); + return result; + } + + /** + * returns the weakest opponent (based on life totals). + * Should keep player relations somewhere in the match structure + * @return + */ + public final Player getWeakestOpponent() { + List opponnets = this.getOpponents(); + Player weakest = opponnets.get(0); + for (int i = 1; i < opponnets.size(); i++) { + if (weakest.getLife() > opponnets.get(i).getLife()) { + weakest = opponnets.get(i); + } + } + return weakest; + } + + public boolean isOpponentOf(Player other) { + return other != this && other != null && ( other.teamNumber < 0 || other.teamNumber != this.teamNumber ); + } + + + + + // //////////////////////// + // + // methods for manipulating life + // + // //////////////////////// + + /** + *

+ * Setter for the field life. + *

+ * + * @param newLife + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean setLife(final int newLife, final Card source) { + boolean change = false; + // rule 118.5 + if (this.life > newLife) { + change = (this.loseLife(this.life - newLife) > 0); + } else if (newLife > this.life) { + change = this.gainLife(newLife - this.life, source); + } else { + // life == newLife + change = false; + } + return change; + } + + /** + * Sets the starting life for a game. Should only be called from + * newGame()'s. + * + * @param startLife + * a int. + */ + public final void setStartingLife(final int startLife) { + this.startingLife = startLife; + this.life = startLife; + } + + /** + *

+ * Getter for the field life. + *

+ * + * @return a int. + */ + public final int getLife() { + return this.life; + } + + /** + *

+ * Getter for the field startingLife. + *

+ * + * @return a int. + */ + public final int getStartingLife() { + return this.startingLife; + } + + /** + *

+ * gainLife. + *

+ * + * @param toGain + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean gainLife(final int toGain, final Card source) { + + // Run any applicable replacement effects. + final HashMap repParams = new HashMap(); + repParams.put("Event", "GainLife"); + repParams.put("Affected", this); + repParams.put("LifeGained", toGain); + repParams.put("Source", source); + if (game.getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { + return false; + } + + boolean newLifeSet = false; + if (!this.canGainLife()) { + return false; + } + final int lifeGain = toGain; + + if (lifeGain > 0) { + int oldLife = life; + this.life += lifeGain; + newLifeSet = true; + this.lifeGainedThisTurn += lifeGain; + + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Player", this); + runParams.put("LifeAmount", lifeGain); + game.getTriggerHandler().runTrigger(TriggerType.LifeGained, runParams, false); + + game.fireEvent(new GameEventPlayerLivesChanged(this, oldLife, life)); + } else { + System.out.println("Player - trying to gain negative or 0 life"); + } + + return newLifeSet; + } + + /** + *

+ * canGainLife. + *

+ * + * @return a boolean. + */ + public final boolean canGainLife() { + if (this.hasKeyword("You can't gain life.") || this.hasKeyword("Your life total can't change.")) { + return false; + } + return true; + } + + /** + *

+ * loseLife. + *

+ * + * @param toLose + * a int. + * @return an int. + */ + public final int loseLife(final int toLose) { + int lifeLost = 0; + if (!this.canLoseLife()) { + return 0; + } + if (toLose > 0) { + int oldLife = life; + this.life -= toLose; + lifeLost = toLose; + game.fireEvent(new GameEventPlayerLivesChanged(this, oldLife, life)); + } else if (toLose == 0) { + // Rule 118.4 + // this is for players being able to pay 0 life + // nothing to do + } else { + System.out.println("Player - trying to lose negative life"); + return 0; + } + + this.lifeLostThisTurn += toLose; + + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Player", this); + runParams.put("LifeAmount", toLose); + game.getTriggerHandler().runTrigger(TriggerType.LifeLost, runParams, false); + + return lifeLost; + } + + /** + *

+ * canLoseLife. + *

+ * + * @return a boolean. + */ + public final boolean canLoseLife() { + if (this.hasKeyword("Your life total can't change.")) { + return false; + } + return true; + } + + /** + *

+ * canPayLife. + *

+ * + * @param lifePayment + * a int. + * @return a boolean. + */ + public final boolean canPayLife(final int lifePayment) { + if (this.life < lifePayment) { + return false; + } + if ((lifePayment > 0) && this.hasKeyword("Your life total can't change.")) { + return false; + } + return true; + } + + /** + *

+ * payLife. + *

+ * + * @param lifePayment + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean payLife(final int lifePayment, final Card source) { + if (!this.canPayLife(lifePayment)) { + return false; + } + // rule 118.8 + if (this.life >= lifePayment) { + return (this.loseLife(lifePayment) > 0); + } + + return false; + } + + // //////////////////////// + // + // methods for handling damage + // + // //////////////////////// + + // This function handles damage after replacement and prevention effects are + // applied + /** + *

+ * addDamageAfterPrevention. + *

+ * + * @param damage + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @param isCombat + * a boolean. + * @return whether or not damage was dealt + */ + @Override + public final boolean addDamageAfterPrevention(final int amount, final Card source, final boolean isCombat) { + if (amount <= 0) { + return false; + } + //String additionalLog = ""; + source.addDealtDamageToPlayerThisTurn(this.getName(), amount); + + boolean infect = source.hasKeyword("Infect") + || this.hasKeyword("All damage is dealt to you as though its source had infect."); + + if (infect) { + this.addPoisonCounters(amount, source); + } else { + // Worship does not reduce the damage dealt but changes the effect + // of the damage + if (this.hasKeyword("Damage that would reduce your life total to less than 1 reduces it to 1 instead.") + && this.life <= amount) { + this.loseLife(Math.min(amount, this.life - 1)); + } else { + // rule 118.2. Damage dealt to a player normally causes that + // player to lose that much life. + this.loseLife(amount); + } + } + + if(source.isCommander() && isCombat) + { + if(!commanderDamage.containsKey(source)) + { + commanderDamage.put(source, amount); + } + else + { + commanderDamage.put(source,commanderDamage.get(source) + amount); + } + } + + this.assignedDamage.put(source, amount); + if (source.hasKeyword("Lifelink")) { + source.getController().gainLife(amount, source); + } + source.getDamageHistory().registerDamage(this); + + if (isCombat) { + final ArrayList types = source.getType(); + for (final String type : types) { + source.getController().addProwlType(type); + } + } + + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("DamageSource", source); + runParams.put("DamageTarget", this); + runParams.put("DamageAmount", amount); + runParams.put("IsCombatDamage", isCombat); + game.getTriggerHandler().runTrigger(TriggerType.DamageDone, runParams, false); + + game.fireEvent(new GameEventPlayerDamaged(this, source, amount, isCombat, infect)); + + return true; + } + + // This should be also usable by the AI to forecast an effect (so it must + // not change the game state) + /** + *

+ * staticDamagePrevention. + *

+ * + * @param damage + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @param isCombat + * a boolean. + * @return a int. + */ + @Override + public final int staticDamagePrevention(final int damage, final Card source, final boolean isCombat, final boolean isTest) { + + if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) { + return damage; + } + + if (isCombat && game.getPhaseHandler().isPreventCombatDamageThisTurn()) { + return 0; + } + + if (this.hasProtectionFrom(source)) { + return 0; + } + + int restDamage = damage; + + for (String kw : source.getKeyword()) { + if (isCombat) { + if (kw.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")) { + return 0; + } + if (kw.equals("Prevent all combat damage that would be dealt by CARDNAME.")) { + return 0; + } + } + if (kw.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) { + return 0; + } + if (kw.equals("Prevent all damage that would be dealt by CARDNAME.")) { + return 0; + } + } + + // Prevent Damage static abilities + for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { + final ArrayList staticAbilities = ca.getStaticAbilities(); + for (final StaticAbility stAb : staticAbilities) { + restDamage = stAb.applyAbility("PreventDamage", source, this, restDamage, isCombat, isTest); + } + } + + if (restDamage > 0) { + return restDamage; + } else { + return 0; + } + } + + // This is usable by the AI to forecast an effect (so it must + // not change the game state) + // 2012/01/02: No longer used in calculating the finalized damage, but + // retained for damageprediction. -Hellfish + /** + *

+ * staticReplaceDamage. + *

+ * + * @param damage + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @param isCombat + * a boolean. + * @return a int. + */ + @Override + public final int staticReplaceDamage(final int damage, final Card source, final boolean isCombat) { + + int restDamage = damage; + + if (this.hasKeyword("Damage that would reduce your life total to less than 1 reduces it to 1 instead.")) { + restDamage = Math.min(restDamage, this.life - 1); + } + + for (Card c : game.getCardsIn(ZoneType.Battlefield)) { + if (c.getName().equals("Sulfuric Vapors")) { + if (source.isSpell() && source.isRed()) { + restDamage += 1; + } + } else if (c.getName().equals("Pyromancer's Swath")) { + if (c.getController().equals(source.getController()) && (source.isInstant() || source.isSorcery())) { + restDamage += 2; + } + } else if (c.getName().equals("Pyromancer's Gauntlet")) { + if (c.getController().equals(source.getController()) && source.isRed() + && (source.isInstant() || source.isSorcery() || source.isPlaneswalker())) { + restDamage += 2; + } + } else if (c.getName().equals("Furnace of Rath")) { + restDamage += restDamage; + } else if (c.getName().equals("Gratuitous Violence")) { + if (c.getController().equals(source.getController()) && source.isCreature()) { + restDamage += restDamage; + } + } else if (c.getName().equals("Fire Servant")) { + if (c.getController().equals(source.getController()) && source.isRed() + && (source.isInstant() || source.isSorcery())) { + restDamage *= 2; + } + } else if (c.getName().equals("Curse of Bloodletting")) { + if (c.getEnchanting().equals(this)) { + restDamage *= 2; + } + } else if (c.getName().equals("Gisela, Blade of Goldnight")) { + if (!c.getController().equals(this)) { + restDamage *= 2; + } + } else if (c.getName().equals("Inquisitor's Flail")) { + if (isCombat && c.getEquippingCard() != null && c.getEquippingCard().equals(source)) { + restDamage *= 2; + } + } else if (c.getName().equals("Ghosts of the Innocent")) { + restDamage = restDamage / 2; + } else if (c.getName().equals("Benevolent Unicorn")) { + if (source.isSpell()) { + restDamage -= 1; + } + } else if (c.getName().equals("Divine Presence")) { + if (restDamage > 3) { + restDamage = 3; + } + } else if (c.getName().equals("Forethought Amulet")) { + if (c.getController().equals(this) && (source.isInstant() || source.isSorcery()) + && restDamage > 2) { + restDamage = 2; + } + } else if (c.getName().equals("Elderscale Wurm")) { + if (c.getController().equals(this) && this.getLife() - restDamage < 7) { + restDamage = this.getLife() - 7; + if (restDamage < 0) { + restDamage = 0; + } + } + } + } + + return restDamage; + } + + /** + *

+ * replaceDamage. + *

+ * + * @param damage + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @param isCombat + * a boolean. + * @return a int. + */ + @Override + public final int replaceDamage(final int damage, final Card source, final boolean isCombat) { + + // Replacement effects + final HashMap repParams = new HashMap(); + repParams.put("Event", "DamageDone"); + repParams.put("Affected", this); + repParams.put("DamageSource", source); + repParams.put("DamageAmount", damage); + repParams.put("IsCombat", isCombat); + + if (game.getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { + return 0; + } + + return damage; + } + + /** + *

+ * preventDamage. + *

+ * + * @param damage + * a int. + * @param source + * a {@link forge.game.card.Card} object. + * @param isCombat + * a boolean. + * @return a int. + */ + @Override + public final int preventDamage(final int damage, final Card source, final boolean isCombat) { + + if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) + || source.hasKeyword("Damage that would be dealt by CARDNAME can't be prevented.")) { + return damage; + } + + int restDamage = damage; + + boolean DEBUGShieldsWithEffects = false; + while (!this.getPreventNextDamageWithEffect().isEmpty() && restDamage != 0) { + TreeMap> shieldMap = this.getPreventNextDamageWithEffect(); + List preventionEffectSources = new ArrayList(shieldMap.keySet()); + Card shieldSource = preventionEffectSources.get(0); + if (preventionEffectSources.size() > 1) { + Map choiceMap = new TreeMap(); + List choices = new ArrayList(); + for (final Card key : preventionEffectSources) { + String effDesc = shieldMap.get(key).get("EffectString"); + int descIndex = effDesc.indexOf("SpellDescription"); + effDesc = effDesc.substring(descIndex + 18); + String shieldDescription = key.toString() + " - " + shieldMap.get(shieldSource).get("ShieldAmount") + + " shields - " + effDesc; + choices.add(shieldDescription); + choiceMap.put(shieldDescription, key); + } + shieldSource = this.getController().chooseProtectionShield(this, choices, choiceMap); + } + if (DEBUGShieldsWithEffects) { + System.out.println("Prevention shield source: " + shieldSource); + } + + int shieldAmount = Integer.valueOf(shieldMap.get(shieldSource).get("ShieldAmount")); + int dmgToBePrevented = Math.min(restDamage, shieldAmount); + if (DEBUGShieldsWithEffects) { + System.out.println("Selected source initial shield amount: " + shieldAmount); + System.out.println("Incoming damage: " + restDamage); + System.out.println("Damage to be prevented: " + dmgToBePrevented); + } + + //Set up ability + SpellAbility shieldSA = null; + String effectAbString = shieldMap.get(shieldSource).get("EffectString"); + effectAbString = effectAbString.replace("PreventedDamage", Integer.toString(dmgToBePrevented)); + effectAbString = effectAbString.replace("ShieldEffectTarget", shieldMap.get(shieldSource).get("ShieldEffectTarget")); + if (DEBUGShieldsWithEffects) { + System.out.println("Final shield ability string: " + effectAbString); + } + shieldSA = AbilityFactory.getAbility(effectAbString, shieldSource); + if (shieldSA.usesTargeting()) { + System.err.println(shieldSource + " - Targeting for prevention shield's effect should be done with initial spell"); + } + + boolean apiIsEffect = (shieldSA.getApi() == ApiType.Effect); + List cardsInCommand = null; + if (apiIsEffect) { + cardsInCommand = this.getGame().getCardsIn(ZoneType.Command); + } + + this.getController().playSpellAbilityNoStack(shieldSA, true); + if (apiIsEffect) { + List newCardsInCommand = this.getGame().getCardsIn(ZoneType.Command); + newCardsInCommand.removeAll(cardsInCommand); + newCardsInCommand.get(0).setSVar("PreventedDamage", "Number$" + Integer.toString(dmgToBePrevented)); + } + this.subtractPreventNextDamageWithEffect(shieldSource, restDamage); + restDamage = restDamage - dmgToBePrevented; + + if (DEBUGShieldsWithEffects) { + System.out.println("Remaining shields: " + + (shieldMap.containsKey(shieldSource) ? shieldMap.get(shieldSource).get("ShieldAmount") : "all shields used")); + System.out.println("Remaining damage: " + restDamage); + } + } + + final HashMap repParams = new HashMap(); + repParams.put("Event", "DamageDone"); + repParams.put("Affected", this); + repParams.put("DamageSource", source); + repParams.put("DamageAmount", damage); + repParams.put("IsCombat", isCombat); + repParams.put("Prevention", true); + + if (game.getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { + return 0; + } + + restDamage = this.staticDamagePrevention(restDamage, source, isCombat, false); + + if (restDamage >= this.getPreventNextDamage()) { + restDamage = restDamage - this.getPreventNextDamage(); + this.setPreventNextDamage(0); + } else { + this.setPreventNextDamage(this.getPreventNextDamage() - restDamage); + restDamage = 0; + } + + return restDamage; + } + + /** + *

+ * Setter for the field assignedDamage. + *

+ */ + public final void clearAssignedDamage() { + this.assignedDamage.clear(); + } + + /** + *

+ * Getter for the field assignedDamage. + *

+ * + * @return a int. + */ + public final int getAssignedDamage() { + int num = 0; + for (final Integer value : this.assignedDamage.values()) { + num += value; + } + return num; + } + + public final Iterable getAssignedDamageSources() { + return assignedDamage.keySet(); + } + + /** + *

+ * Getter for the field assignedDamage. + *

+ * + * @param type + * a string. + * + * @return a int. + */ + public final int getAssignedDamage(final String type) { + final Map valueMap = new HashMap(); + for (final Card c : this.assignedDamage.keySet()) { + if (c.isType(type)) { + valueMap.put(c, this.assignedDamage.get(c)); + } + } + int num = 0; + for (final Integer value : valueMap.values()) { + num += value; + } + return num; + } + + /** + *

+ * addCombatDamage. + *

+ * + * @param damage + * a int. + * @param source + * a {@link forge.game.card.Card} object. + */ + public final boolean addCombatDamage(final int damage, final Card source) { + + int damageToDo = damage; + + damageToDo = this.replaceDamage(damageToDo, source, true); + damageToDo = this.preventDamage(damageToDo, source, true); + + this.addDamageAfterPrevention(damageToDo, source, true); // damage + // prevention + // is already + // checked + + if (damageToDo > 0) { + GameActionUtil.executeCombatDamageToPlayerEffects(this, source, damageToDo); + return true; + } + return false; + } + + // //////////////////////// + // + // methods for handling Poison counters + // + // //////////////////////// + + /** + *

+ * addPoisonCounters. + *

+ * + * @param num + * a int. + * @param source + * the source + */ + public final void addPoisonCounters(final int num, final Card source) { + if (!this.hasKeyword("You can't get poison counters")) { + setPoisonCounters(poisonCounters + num, source); + } + } + + /** + *

+ * Setter for the field poisonCounters. + *

+ * + * @param num + * a int. + * @param source + */ + public final void setPoisonCounters(final int num, Card source) { + int oldPoison = poisonCounters; + this.poisonCounters = num; + game.fireEvent(new GameEventPlayerPoisoned(this, source, oldPoison, num)); + } + + /** + *

+ * Getter for the field poisonCounters. + *

+ * + * @return a int. + */ + public final int getPoisonCounters() { + return this.poisonCounters; + } + + /** + * Gets the keywords. + * + * @return the keywords + */ + public final ArrayList getKeywords() { + return this.keywords; + } + + /** + * Adds the keyword. + * + * @param keyword + * the keyword + */ + public final void addKeyword(final String keyword) { + this.keywords.add(keyword); + } + + /** + * Removes the keyword. + * + * @param keyword + * the keyword + */ + public final void removeKeyword(final String keyword) { + this.keywords.remove(keyword); + } + + /* + * (non-Javadoc) + * + * @see forge.GameEntity#hasKeyword(java.lang.String) + */ + /** + * Checks for keyword. + * + * @param keyword + * String + * @return boolean + */ + @Override + public final boolean hasKeyword(final String keyword) { + return this.keywords.contains(keyword); + } + + /** + * Can target. + * + * @param sa + * the sa + * @return a boolean + */ + @Override + public final boolean canBeTargetedBy(final SpellAbility sa) { + if (this.hasKeyword("Shroud") || (!this.equals(sa.getActivatingPlayer()) && this.hasKeyword("Hexproof")) + || this.hasProtectionFrom(sa.getSourceCard())) { + return false; + } + + return true; + } + + /* + * (non-Javadoc) + * + * @see forge.GameEntity#hasProtectionFrom(forge.Card) + */ + @Override + public boolean hasProtectionFrom(final Card source) { + if (this.getKeywords() != null) { + final ArrayList list = this.getKeywords(); + + String kw = ""; + for (int i = 0; i < list.size(); i++) { + kw = list.get(i); + + if (kw.equals("Protection from white") && source.isWhite()) { + return true; + } + if (kw.equals("Protection from blue") && source.isBlue()) { + return true; + } + if (kw.equals("Protection from black") && source.isBlack()) { + return true; + } + if (kw.equals("Protection from red") && source.isRed()) { + return true; + } + if (kw.equals("Protection from green") && source.isGreen()) { + return true; + } + + if (kw.startsWith("Protection:")) { // uses isValid + final String characteristic = kw.split(":")[1]; + final String[] characteristics = characteristic.split(","); + if (source.isValid(characteristics, this, null)) { + return true; + } + } + + } + } + return false; + } + + /** + *

+ * canPlaySpells. + *

+ * + * @return a boolean. + */ + public final boolean canCastSpells() { + return !this.keywords.contains("Can't cast spells"); + } + + /** + *

+ * canPlayAbilities. + *

+ * + * @return a boolean. + */ + public final boolean canActivateAbilities() { + return !this.keywords.contains("Can't activate abilities"); + } + + // ////////////////////////////// + // / + // / replaces Singletons.getModel().getGameAction().draw* methods + // / + // ////////////////////////////// + + /** + *

+ * canDraw + *

+ * . + * + * @return true if a player can draw a card, false otherwise + */ + public final boolean canDraw() { + if (this.hasKeyword("You can't draw cards.")) { + return false; + } + if (this.hasKeyword("You can't draw more than one card each turn.")) { + return this.numDrawnThisTurn < 1; + } + return true; + } + + /** + *

+ * drawCard. + *

+ * + * @return a List of cards actually drawn + */ + public final List drawCard() { + return this.drawCards(1); + } + + + public boolean canMulligan() { + return !getZone(ZoneType.Hand).isEmpty(); + } + + /** + *

+ * drawCards. + *

+ * + * @param n + * a int. + * @return a List of cards actually drawn + */ + public final List drawCards(final int n) { + final List drawn = new ArrayList(); + + for (int i = 0; i < n; i++) { + +// // TODO: multiple replacements need to be selected by the controller +// List dredgers = this.getDredge(); +// if (!dredgers.isEmpty()) { +// Card toDredge = getController().chooseCardToDredge(dredgers); +// int dredgeNumber = toDredge == null ? Integer.MAX_VALUE : getDredgeNumber(toDredge); +// if ( dredgeNumber <= getZone(ZoneType.Library).size()) { +// game.getAction().moveToHand(toDredge); +// +// for (int iD = 0; iD < dredgeNumber; iD++) { +// final Card c2 = getZone(ZoneType.Library).get(0); +// game.getAction().moveToGraveyard(c2); +// } +// continue; +// } +// } + + if (!this.canDraw()) { + return drawn; + } + drawn.addAll(this.doDraw()); + } + return drawn; + } + + /** + *

+ * doDraw. + *

+ * + * @return a List of cards actually drawn + */ + private List doDraw() { + final List drawn = new ArrayList(); + final PlayerZone library = this.getZone(ZoneType.Library); + + // Replacement effects + final HashMap repRunParams = new HashMap(); + repRunParams.put("Event", "Draw"); + repRunParams.put("Affected", this); + + if (game.getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) { + return drawn; + } + + // ======== Chains of Mephistopheles hardcode. ========= + // This card requires player to either discard a card, and then he may proceed drawing, or mill 1 - then no draw will happen + // It's oracle-worded as a replacement effect ("If a player would draw a card ... discards a card instead") (rule 419.1a) + // Yet, gatherer's rulings read: The effect is cumulative. If there are two of these on the battlefield, each of them will modify each draw + // That means player isn't supposed to choose one replacement effect out of two (generated by Chains Of Mephistopheles), but both happen. + // So it's not a common replacement effect and has to be handled by special code. + + // This is why the code is placed after any other replacement effects could affect the draw event. + List chainsList = null; + for(Card c : game.getCardsIn(ZoneType.Battlefield)) { + if ( c.getName().equals("Chains of Mephistopheles") ) { + if ( null == chainsList ) + chainsList = new ArrayList(); + chainsList.add(c); + } + } + if (chainsList != null && (numDrawnThisDrawStep > 0 || !game.getPhaseHandler().is(PhaseType.DRAW))) { + for(Card c : chainsList) { + // I have to target this player - don't know how to do it. + + + if (getCardsIn(ZoneType.Hand).isEmpty()) { + SpellAbility saMill = AbilityFactory.getAbility(c.getSVar("MillOne"), c); + saMill.setActivatingPlayer(c.getController()); + saMill.getTargets().add(this); + AbilityUtils.resolve(saMill); + + return drawn; // Draw is cancelled + } else { + SpellAbility saDiscard = AbilityFactory.getAbility(c.getSVar("DiscardOne"), c); + saDiscard.setActivatingPlayer(c.getController()); + saDiscard.getTargets().add(this); + AbilityUtils.resolve(saDiscard); + } + } + } + // End of = Chains of Mephistopheles hardcode. ========= + + if (!library.isEmpty()) { + + Card c = library.get(0); + c = game.getAction().moveToHand(c); + drawn.add(c); + + if (this.numDrawnThisTurn == 0) { + boolean reveal = false; + final List cards = this.getCardsIn(ZoneType.Battlefield); + for (final Card card : cards) { + if (card.hasKeyword("Reveal the first card you draw each turn")) { + reveal = true; + break; + } + } + if (reveal) { + game.getAction().reveal(drawn, this, true, "Revealing the first card drawn from "); + } + } + + this.setLastDrawnCard(c); + c.setDrawnThisTurn(true); + this.numDrawnThisTurn++; + if ( game.getPhaseHandler().is(PhaseType.DRAW)) + this.numDrawnThisDrawStep++; + + // Miracle draws + if (this.numDrawnThisTurn == 1 + && game.getPhaseHandler().getTurn() != 1 + && game.getAge() != GameStage.Mulligan) { + drawMiracle(c); + } + + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Card", c); + runParams.put("Number", this.numDrawnThisTurn); + runParams.put("Player", this); + game.getTriggerHandler().runTrigger(TriggerType.Drawn, runParams, false); + } + else // Lose by milling is always on. Give AI many cards it cannot play if you want it not to undertake actions + this.triedToDrawFromEmptyLibrary = true; + + return drawn; + } + + /** + * Returns PlayerZone corresponding to the given zone of game. + * + * @param zone + * the zone + * @return the zone + */ + public final PlayerZone getZone(final ZoneType zone) { + return this.zones.get(zone); + } + + public final List getCardsIn(final ZoneType zoneType) { + return getCardsIn(zoneType, true); + } + + /** + * gets a list of all cards in the requested zone. This function makes a + * List from Card[]. + * + * @param zone + * the zone + * @return a List with all the cards currently in requested zone + */ + public final List getCardsIn(final ZoneType zoneType, boolean filterOutPhasedOut) { + List result; + if (zoneType == ZoneType.Stack) { + result = new ArrayList(); + for (Card c : game.getStackZone().getCards()) { + if (c.getOwner().equals(this)) { + result.add(c); + } + } + } else { + PlayerZone zone = this.getZone(zoneType); + result = zone == null ? null : zone.getCards(filterOutPhasedOut); + } + return result; + } + + /** + * Gets the cards include phasing in. + * + * @param zone + * the zone + * @return the cards include phasing in + */ + public final List getCardsIncludePhasingIn(final ZoneType zone) { + return this.getCardsIn(zone, false); + } + + /** + * gets a list of first N cards in the requested zone. This function makes a + * List from Card[]. + * + * @param zone + * the zone + * @param n + * the n + * @return a List with all the cards currently in requested zone + */ + public final List getCardsIn(final ZoneType zone, final int n) { + return Lists.newArrayList(Iterables.limit(this.getCardsIn(zone), n)); + } + + /** + * gets a list of all cards in a given player's requested zones. + * + * @param zones + * the zones + * @return a List with all the cards currently in requested zones + */ + public final List getCardsIn(final Iterable zones) { + final List result = new ArrayList(); + for (final ZoneType z : zones) { + result.addAll(getCardsIn(z)); + } + return result; + } + public final List getCardsIn(final ZoneType[] zones) { + final List result = new ArrayList(); + for (final ZoneType z : zones) { + result.addAll(getCardsIn(z)); + } + return result; + } + + /** + * gets a list of all cards with requested cardName in a given player's + * requested zone. This function makes a List from Card[]. + * + * @param zone + * the zone + * @param cardName + * the card name + * @return a List with all the cards currently in that player's library + */ + public final List getCardsIn(final ZoneType zone, final String cardName) { + return CardLists.filter(this.getCardsIn(zone), CardPredicates.nameEquals(cardName)); + } + + + public List getCardsActivableInExternalZones() { + final List cl = new ArrayList(); + + cl.addAll(this.getZone(ZoneType.Graveyard).getCardsPlayerCanActivate(this)); + cl.addAll(this.getZone(ZoneType.Exile).getCardsPlayerCanActivate(this)); + cl.addAll(this.getZone(ZoneType.Library).getCardsPlayerCanActivate(this)); + cl.addAll(this.getZone(ZoneType.Command).getCardsPlayerCanActivate(this)); + + //External activatables from all opponents + for (final Player opponent : this.getOpponents()) { + cl.addAll(opponent.getZone(ZoneType.Exile).getCardsPlayerCanActivate(this)); + cl.addAll(opponent.getZone(ZoneType.Graveyard).getCardsPlayerCanActivate(this)); + cl.addAll(opponent.getZone(ZoneType.Library).getCardsPlayerCanActivate(this)); + if (opponent.hasKeyword("Play with your hand revealed.")) { + cl.addAll(opponent.getZone(ZoneType.Hand).getCardsPlayerCanActivate(this)); + } + } + + return cl; + } + + /** + * Gets the all cards. + * + * @return the all cards + */ + public final List getAllCards() { + List allExcStack = this.getCardsIn(Player.ALL_ZONES); + allExcStack.addAll(getCardsIn(ZoneType.Stack)); + allExcStack.addAll(inboundTokens); + return allExcStack; + } + + /** + *

+ * getDredge. + *

+ * + * @return a {@link forge.CardList} object. + */ + protected final List getDredge() { + final List dredge = new ArrayList(); + int cntLibrary = this.getCardsIn(ZoneType.Library).size(); + for (final Card c : this.getCardsIn(ZoneType.Graveyard)) { + int nDr = getDredgeNumber(c); + if (nDr > 0 && cntLibrary >= nDr) { + dredge.add(c); + } + } + return dredge; + } // hasDredge() + + /** + *

+ * getDredgeNumber. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a int. + */ + protected final int getDredgeNumber(final Card c) { + for (String s : c.getKeyword()) { + if (s.startsWith("Dredge")) { + return Integer.parseInt("" + s.charAt(s.length() - 1)); + } + } + return 0; + // throw new RuntimeException("Input_Draw : getDredgeNumber() card doesn't have dredge - " + c.getName()); + } // getDredgeNumber() + + /** + *

+ * resetNumDrawnThisTurn. + *

+ */ + public final void resetNumDrawnThisTurn() { + this.numDrawnThisTurn = 0; + this.numDrawnThisDrawStep = 0; + } + + /** + *

+ * Getter for the field numDrawnThisTurn. + *

+ * + * @return a int. + */ + public final int getNumDrawnThisTurn() { + return this.numDrawnThisTurn; + } + + /** + *

+ * Getter for the field numDrawnThisTurnDrawStep. + *

+ * + * @return a int. + */ + public final int numDrawnThisDrawStep() { + return this.numDrawnThisDrawStep; + } + + // ////////////////////////////// + // / + // / replaces Singletons.getModel().getGameAction().discard* methods + // / + // ////////////////////////////// + + /** + *

+ * discard. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a {@link forge.CardList} object. + */ + public final boolean discard(final Card c, final SpellAbility sa) { + FThreads.assertExecutedByEdt(false); + // TODO: This line should be moved inside CostPayment somehow + /*if (sa != null) { + sa.addCostToHashList(c, "Discarded"); + }*/ + final Card source = sa != null ? sa.getSourceCard() : null; + + // Replacement effects + final HashMap repRunParams = new HashMap(); + repRunParams.put("Event", "Discard"); + repRunParams.put("Card", c); + repRunParams.put("Source", source); + repRunParams.put("Affected", this); + + if (game.getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) { + return false; + } + + boolean discardToTopOfLibrary = null != sa && sa.hasParam("DiscardToTopOfLibrary"); + boolean discardMadness = sa != null && sa.hasParam("Madness"); + + if (discardToTopOfLibrary) { + game.getAction().moveToLibrary(c, 0); + // Play the Discard sound + } else if (discardMadness) { + game.getAction().exile(c); + } else { + game.getAction().moveToGraveyard(c); + // Play the Discard sound + } + this.numDiscardedThisTurn++; + // Run triggers + Card cause = null; + if (sa != null) { + cause = sa.getSourceCard(); + } + final HashMap runParams = new HashMap(); + runParams.put("Player", this); + runParams.put("Card", c); + runParams.put("Cause", cause); + runParams.put("IsMadness", (Boolean) discardMadness); + game.getTriggerHandler().runTrigger(TriggerType.Discarded, runParams, false); + return true; + + } // end doDiscard + + /** + *

+ * Getter for the field numDiscardedThisTurn. + *

+ * + * @return a int. + */ + public final int getNumDiscardedThisTurn() { + return this.numDiscardedThisTurn; + } + + /** + *

+ * Getter for the field numDiscardedThisTurn. + *

+ * + * @return a int. + */ + public final void resetNumDiscardedThisTurn() { + this.numDiscardedThisTurn = 0; + } + + /** + *

+ * mill. + *

+ * + * @param n + * a int. + * @return the card list + */ + public final List mill(final int n) { + return this.mill(n, ZoneType.Graveyard, false); + } + + /** + *

+ * mill. + *

+ * + * @param n + * a int. + * @param zone + * a {@link java.lang.String} object. + * @param bottom + * a boolean. + * @return the card list + */ + public final List mill(final int n, final ZoneType zone, final boolean bottom) { + final List lib = new ArrayList(this.getCardsIn(ZoneType.Library)); + final List milled = new ArrayList(); + + final int max = Math.min(n, lib.size()); + + final ZoneType destination = this.getZone(zone).getZoneType(); + + for (int i = 0; i < max; i++) { + if (bottom) { + milled.add(game.getAction().moveTo(destination, lib.get(lib.size() - 1))); + } else { + milled.add(game.getAction().moveTo(destination, lib.get(i))); + } + } + + return milled; + } + + // ////////////////////////////// + /** + *

+ * shuffle. + *

+ */ + public final void shuffle(final SpellAbility sa) { + final List list = Lists.newArrayList(this.getCardsIn(ZoneType.Library)); + + if (list.size() <= 1) { + return; + } + + // overdone but wanted to make sure it was really random + final Random random = MyRandom.getRandom(); + Collections.shuffle(list, random); + Collections.shuffle(list, random); + Collections.shuffle(list, random); + Collections.shuffle(list, random); + Collections.shuffle(list, random); + Collections.shuffle(list, random); + + int s = list.size(); + for (int i = 0; i < s; i++) { + list.add(random.nextInt(s - 1), list.remove(random.nextInt(s))); + } + + Collections.shuffle(list, random); + Collections.shuffle(list, random); + Collections.shuffle(list, random); + Collections.shuffle(list, random); + Collections.shuffle(list, random); + Collections.shuffle(list, random); + + this.getZone(ZoneType.Library).setCards(getController().cheatShuffle(list)); + + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Player", this); + runParams.put("Source", sa); + game.getTriggerHandler().runTrigger(TriggerType.Shuffled, runParams, false); + + // Play the shuffle sound + game.fireEvent(new GameEventShuffle(this)); + } // shuffle + // ////////////////////////////// + + // ////////////////////////////// + + + // ///////////////////////////// + + /** + *

+ * playLand. + *

+ * + * @param land + * a {@link forge.game.card.Card} object. + */ + public final boolean playLand(final Card land, final boolean ignoreZoneAndTiming) { + FThreads.assertExecutedByEdt(false); + if (this.canPlayLand(land, ignoreZoneAndTiming)) { + land.setController(this, 0); + game.getAction().moveTo(this.getZone(ZoneType.Battlefield), land); + + // play a sound + game.fireEvent(new GameEventLandPlayed(this, land)); + + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Card", land); + game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, runParams, false); + game.getStack().unfreezeStack(); + this.numLandsPlayed++; + return true; + } + + game.getStack().unfreezeStack(); + return false; + } + + /** + *

+ * canPlayLand. + *

+ * + * @return a boolean. + */ + public final boolean canPlayLand(final Card land) { + return canPlayLand(land, false); + } + /** + *

+ * canPlayLand. + *

+ * + * @return a boolean. + */ + public final boolean canPlayLand(Card land, final boolean ignoreZoneAndTiming) { + + if (!ignoreZoneAndTiming && !this.canCastSorcery()) { + return false; + } + + // CantBeCast static abilities + for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { + final ArrayList staticAbilities = ca.getStaticAbilities(); + for (final StaticAbility stAb : staticAbilities) { + if (stAb.applyAbility("CantPlayLand", land, this)) { + return false; + } + } + } + + if( land != null && !ignoreZoneAndTiming) { + if (land.getOwner() != this && !land.hasKeyword("May be played by your opponent")) + return false; + + final Zone zone = game.getZoneOf(land); + if(zone.is(ZoneType.Battlefield) || (!zone.is(ZoneType.Hand) && !land.hasStartOfKeyword("May be played"))) { + return false; + } + } + + // **** Check for land play limit per turn **** + // Dev Mode + if (this.canCheatPlayUnlimitedLands || this.isCardInPlay("Fastbond") || this.isCardInCommand("Naya")) { + return true; + } + + // check for adjusted max lands play per turn + int adjMax = 1; + for (String keyword : this.getKeywords()) { + if (keyword.startsWith("AdjustLandPlays")) { + final String[] k = keyword.split(":"); + adjMax += Integer.valueOf(k[1]); + } + } + if (this.numLandsPlayed < adjMax) { + return true; + } + + return false; + } + + /** + * Gets the mana pool. + * + * @return the mana pool + */ + public final ManaPool getManaPool() { + return this.manaPool; + } + + // ///////////////////////////// + // // + // // properties about the player and his/her cards/game status + // // + // ///////////////////////////// + + /** + *

+ * Getter for the field numPowerSurgeLands. + *

+ * + * @return a int. + */ + public final int getNumPowerSurgeLands() { + return this.numPowerSurgeLands; + } + + /** + *

+ * Setter for the field numPowerSurgeLands. + *

+ * + * @param n + * a int. + * @return a int. + */ + public final int setNumPowerSurgeLands(final int n) { + this.numPowerSurgeLands = n; + return this.numPowerSurgeLands; + } + + /** + *

+ * Getter for the field lastDrawnCard. + *

+ * + * @return a {@link forge.game.card.Card} object. + */ + public final Card getLastDrawnCard() { + return this.lastDrawnCard; + } + + /** + *

+ * Setter for the field lastDrawnCard. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a {@link forge.game.card.Card} object. + */ + private final Card setLastDrawnCard(final Card c) { + this.lastDrawnCard = c; + return this.lastDrawnCard; + } + + /** + *

+ * Getter for the field namedCard. + *

+ * + * @return a String. + */ + public final String getNamedCard() { + return this.namedCard; + } + + /** + *

+ * Setter for the field namedCard. + *

+ * + * @param s + * a {@link forge.game.card.Card} object. + * @return + * @return a {@link forge.game.card.Card} object. + */ + public final void setNamedCard(final String s) { + this.namedCard = s; + } + /** + *

+ * getTurn. + *

+ * + * @return a int. + */ + public final int getTurn() { + return this.stats.getTurnsPlayed(); + } + + /** + *

+ * incrementTurn. + *

+ */ + public final void incrementTurn() { + this.stats.nextTurn(); + } + + /** + *

+ * getAttackedWithCreatureThisTurn. + *

+ * + * @return a boolean. + */ + public final boolean getAttackedWithCreatureThisTurn() { + return this.attackedWithCreatureThisTurn; + } + + /** + *

+ * Setter for the field attackedWithCreatureThisTurn. + *

+ * + * @param b + * a boolean. + */ + public final void setAttackedWithCreatureThisTurn(final boolean b) { + this.attackedWithCreatureThisTurn = b; + } + + /** + *

+ * Gets the number of attackers declared by Player this turn. + *

+ * + * @return a boolean. + */ + public final int getAttackersDeclaredThisTurn() { + return this.attackersDeclaredThisTurn; + } + + /** + *

+ * Increase number of attackers declared by Player this turn. + *

+ * + */ + public final void incrementAttackersDeclaredThisTurn() { + this.attackersDeclaredThisTurn++; + } + + /** + *

+ * Resets number of attackers declared by Player this turn. + *

+ * + */ + public final void resetAttackersDeclaredThisTurn() { + this.attackersDeclaredThisTurn = 0; + } + + // Game win/loss + + /** + *

+ * altWinConditionMet. + *

+ * + * @param sourceName + * the source name + */ + public final void altWinBySpellEffect(final String sourceName) { + if (this.cantWin()) { + System.out.println("Tried to win, but currently can't."); + return; + } + this.setOutcome(PlayerOutcome.altWin(sourceName)); + + } + + /** + *

+ * altLoseConditionMet. + *

+ * + * @param state + * the state + * @param spellName + * the spell name + * @return a boolean. + */ + public final boolean loseConditionMet(final GameLossReason state, final String spellName) { + if (state != GameLossReason.OpponentWon) { + if (this.cantLose()) { + System.out.println("Tried to lose, but currently can't."); + return false; + } + + // Replacement effects + final HashMap runParams = new HashMap(); + runParams.put("Affected", this); + runParams.put("Event", "GameLoss"); + + if (game.getReplacementHandler().run(runParams) != ReplacementResult.NotReplaced) { + return false; + } + } + + setOutcome(PlayerOutcome.loss(state, spellName)); + return true; + } + + /** + * Concede. + */ + public final void concede() { // No cantLose checks - just lose + setOutcome(PlayerOutcome.concede()); + } + + /** + *

+ * cantLose. + *

+ * + * @return a boolean. + */ + public final boolean cantLose() { + if (this.getOutcome() != null && this.getOutcome().lossState == GameLossReason.Conceded) { + return false; + } + + return (this.hasKeyword("You can't lose the game.") || this.getOpponent().hasKeyword("You can't win the game.")); + } + + /** + *

+ * cantLoseForZeroOrLessLife. + *

+ * + * @return a boolean. + */ + public final boolean cantLoseForZeroOrLessLife() { + return (this.hasKeyword("You don't lose the game for having 0 or less life.")); + } + + /** + *

+ * cantWin. + *

+ * + * @return a boolean. + */ + public final boolean cantWin() { + boolean isAnyOppLoseProof = false; + for (Player p : game.getPlayers()) { + if (p == this || p.getOutcome() != null) { + + continue; // except self and already dead + } + isAnyOppLoseProof |= p.hasKeyword("You can't lose the game."); + } + return this.hasKeyword("You can't win the game.") || isAnyOppLoseProof; + } + + /** + *

+ * hasLost. + *

+ * + * @return a boolean. + */ + public final boolean checkLoseCondition() { + + // Just in case player already lost + if (this.getOutcome() != null) { + return this.getOutcome().lossState != null; + } + + // Rule 704.5a - If a player has 0 or less life, he or she loses the game. + final boolean hasNoLife = this.getLife() <= 0; + if (hasNoLife && !this.cantLoseForZeroOrLessLife()) { + return this.loseConditionMet(GameLossReason.LifeReachedZero, null); + } + + // Rule 704.5b - If a player attempted to draw a card from a library with no cards in it + // since the last time state-based actions were checked, he or she loses the game. + if (triedToDrawFromEmptyLibrary) { + triedToDrawFromEmptyLibrary = false; // one-shot check + return this.loseConditionMet(GameLossReason.Milled, null); + } + + // Rule 704.5c - If a player has ten or more poison counters, he or she loses the game. + if (this.poisonCounters >= 10) { + return this.loseConditionMet(GameLossReason.Poisoned, null); + } + + if(game.getType() == GameType.Commander) + { + Map cmdDmg = getCommanderDamage(); + for(Card c : cmdDmg.keySet()) + { + if(cmdDmg.get(c) >= 21) + return this.loseConditionMet(GameLossReason.CommanderDamage, null); + } + } + + return false; + } + + public final boolean hasLost() { + return this.getOutcome() != null && this.getOutcome().lossState != null; + } + + public final boolean hasWon() { + if (this.cantWin()) { + return false; + } + // in multiplayer game one player's win is replaced by all other's lose (rule 103.4h) + // so if someone cannot lose, the game appears to continue + + return this.getOutcome() != null && this.getOutcome().lossState == null; + } + + /** + *

+ * hasMetalcraft. + *

+ * + * @return a boolean. + */ + public final boolean hasMetalcraft() { + final List list = CardLists.filter(this.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.ARTIFACTS); + return list.size() >= 3; + } + + /** + *

+ * hasThreshold. + *

+ * + * @return a boolean. + */ + public final boolean hasThreshold() { + return this.getZone(ZoneType.Graveyard).size() >= 7; + } + + /** + *

+ * hasHellbent. + *

+ * + * @return a boolean. + */ + public final boolean hasHellbent() { + return this.getZone(ZoneType.Hand).isEmpty(); + } + + /** + *

+ * hasLandfall. + *

+ * + * @return a boolean. + */ + public final boolean hasLandfall() { + final List list = this.getZone(ZoneType.Battlefield).getCardsAddedThisTurn(null); + return Iterables.any(list, CardPredicates.Presets.LANDS); + } + + /** + *

+ * hasBloodthirst. + *

+ * + * @return a boolean. + */ + public final boolean hasBloodthirst() { + for (Player opp : this.getOpponents()) { + if (opp.getAssignedDamage() > 0) { + return true; + } + } + return false; + } + + /** + *

+ * getBloodthirstAmount. + *

+ * + * @return a int. + */ + public final int getBloodthirstAmount() { + int blood = 0; + for (Player opp : this.getOpponents()) { + blood += opp.getAssignedDamage(); + } + return blood; + } + + /** + *

+ * hasProwl. + *

+ * + * @param type + * the type + * @return a boolean. + */ + public final boolean hasProwl(final String type) { + if (this.prowl.contains("AllCreatureTypes")) { + return true; + } + return this.prowl.contains(type); + } + + /** + * Adds the prowl type. + * + * @param type + * the type + */ + public final void addProwlType(final String type) { + this.prowl.add(type); + } + + /** + * Reset prowl. + */ + public final void resetProwl() { + this.prowl = new ArrayList(); + } + + public final void setLibrarySearched(final int l) { + this.numLibrarySearchedOwn = l; + } + + public final int getLibrarySearched() { + return this.numLibrarySearchedOwn; + } + public final void incLibrarySearched() { + this.numLibrarySearchedOwn++; + } + + /* + * (non-Javadoc) + * + * @see forge.GameEntity#isValid(java.lang.String, forge.Player, forge.Card) + */ + @Override + public final boolean isValid(final String restriction, final Player sourceController, final Card source) { + + final String[] incR = restriction.split("\\."); + + if (incR[0].equals("Opponent")) { + if (this.equals(sourceController) || !this.isOpponentOf(sourceController)) { + return false; + } + } else if (incR[0].equals("You")) { + if (!this.equals(sourceController)) { + return false; + } + } else if (incR[0].equals("EnchantedController")) { + final GameEntity enchanted = source.getEnchanting(); + if ((enchanted == null) || !(enchanted instanceof Card)) { + return false; + } + final Card enchantedCard = (Card) enchanted; + if (!this.equals(enchantedCard.getController())) { + return false; + } + } else { + if (!incR[0].equals("Player")) { + return false; + } + } + + if (incR.length > 1) { + final String excR = incR[1]; + final String[] exR = excR.split("\\+"); // Exclusive Restrictions + // are ... + for (int j = 0; j < exR.length; j++) { + if (!this.hasProperty(exR[j], sourceController, source)) { + return false; + } + } + } + + return true; + } + + /* + * (non-Javadoc) + * + * @see forge.GameEntity#hasProperty(java.lang.String, forge.Player, + * forge.Card) + */ + @Override + public final boolean hasProperty(final String property, final Player sourceController, final Card source) { + if (property.equals("You")) { + if (!this.equals(sourceController)) { + return false; + } + } else if (property.equals("Opponent")) { + if (this.equals(sourceController) || !this.isOpponentOf(sourceController)) { + return false; + } + } else if (property.equals("OpponentToActive")) { + final Player active = game.getPhaseHandler().getPlayerTurn(); + if (this.equals(active) || !this.isOpponentOf(active)) { + return false; + } + } else if (property.equals("Other")) { + if (this.equals(sourceController)) { + return false; + } + } else if (property.equals("wasDealtDamageBySourceThisGame")) { + if (!source.getDamageHistory().getThisGameDamaged().contains(this)) { + return false; + } + } else if (property.equals("wasDealtDamageBySourceThisTurn")) { + if (!source.getDamageHistory().getThisTurnDamaged().contains(this)) { + return false; + } + } else if (property.equals("attackedBySourceThisCombat")) { + if (game.getCombat() == null || !this.equals(game.getCombat().getDefenderPlayerByAttacker(source))) { + return false; + } + } else if (property.startsWith("wasDealtDamageThisTurn")) { + if (this.assignedDamage.isEmpty()) { + return false; + } + } else if (property.startsWith("LostLifeThisTurn")) { + if (this.lifeLostThisTurn <= 0) { + return false; + } + } else if (property.startsWith("DeclaredAttackerThisTurn")) { + if (this.attackersDeclaredThisTurn <= 0) { + return false; + } + } else if (property.equals("IsRemembered")) { + if (!source.getRemembered().contains(this)) { + return false; + } + } else if (property.equals("IsNotRemembered")) { + if (source.getRemembered().contains(this)) { + return false; + } + } else if (property.startsWith("EnchantedBy")) { + if (!this.getEnchantedBy().contains(source)) { + return false; + } + } else if (property.startsWith("Chosen")) { + if (source.getChosenPlayer() == null || !source.getChosenPlayer().equals(this)) { + return false; + } + } else if (property.startsWith("LifeEquals_")) { + int life = AbilityUtils.calculateAmount(source, property.substring(11), null); + if (this.getLife() != life) { + return false; + } + } else if (property.startsWith("withMore")) { + final String cardType = property.split("sThan")[0].substring(8); + final Player controller = "Active".equals(property.split("sThan")[1]) ? game.getPhaseHandler().getPlayerTurn() : sourceController; + final List oppList = CardLists.filter(this.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(cardType)); + final List yourList = CardLists.filter(controller.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(cardType)); + if (oppList.size() <= yourList.size()) { + return false; + } + } else if (property.startsWith("withAtLeast")) { + final String cardType = property.split("More")[1].split("sThan")[0]; + final int amount = Integer.parseInt(property.substring(11, 12)); + final Player controller = "Active".equals(property.split("sThan")[1]) ? game.getPhaseHandler().getPlayerTurn() : sourceController; + final List oppList = CardLists.filter(this.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(cardType)); + final List yourList = CardLists.filter(controller.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(cardType)); + System.out.println(yourList.size()); + if (oppList.size() < yourList.size() + amount) { + return false; + } + } else if (property.startsWith("hasMore")) { + final Player controller = "Active".equals(property.split("Than")[1]) ? game.getPhaseHandler().getPlayerTurn() : sourceController; + if (property.substring(7).startsWith("Life") && this.getLife() <= controller.getLife()) { + return false; + } else if (property.substring(7).startsWith("CardsInHand") + && this.getCardsIn(ZoneType.Hand).size() <= controller.getCardsIn(ZoneType.Hand).size()) { + return false; + } + } else if (property.startsWith("hasFewer")) { + final Player controller = "Active".equals(property.split("Than")[1]) ? game.getPhaseHandler().getPlayerTurn() : sourceController; + if (property.substring(8).startsWith("CreaturesInYard")) { + final List oppList = CardLists.filter(this.getCardsIn(ZoneType.Graveyard), Presets.CREATURES); + final List yourList = CardLists.filter(controller.getCardsIn(ZoneType.Graveyard), Presets.CREATURES); + if (oppList.size() >= yourList.size()) { + return false; + } + } + } else if (property.startsWith("withMost")) { + if (property.substring(8).equals("Life")) { + int highestLife = this.getLife(); // Negative base just in case a few Lich's are running around + Player healthiest = this; + for (final Player p : game.getPlayers()) { + if (p.getLife() > highestLife) { + highestLife = p.getLife(); + healthiest = p; + } + } + if (!this.equals(healthiest)) { + return false; + } + } + else if (property.substring(8).equals("CardsInHand")) { + int largestHand = 0; + Player withLargestHand = null; + for (final Player p : game.getPlayers()) { + if (p.getCardsIn(ZoneType.Hand).size() > largestHand) { + largestHand = p.getCardsIn(ZoneType.Hand).size(); + withLargestHand = p; + } + } + if (!this.equals(withLargestHand)) { + return false; + } + } + else if (property.substring(8).startsWith("Type")) { + String type = property.split("Type")[1]; + boolean checkOnly = false; + if (type.endsWith("Only")) { + checkOnly = true; + type = type.replace("Only", ""); + } + int typeNum = 0; + List controlmost = new ArrayList(); + for (final Player p : game.getPlayers()) { + final int num = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), type).size(); + if (num > typeNum) { + typeNum = num; + controlmost.clear(); + } + if (num == typeNum) { + controlmost.add(p); + } + } + if (checkOnly && controlmost.size() != 1) { + return false; + } + if (!controlmost.contains(this)) { + return false; + } + } + } else if (property.startsWith("withLowest")) { + if (property.substring(10).equals("Life")) { + int lowestLife = this.getLife(); + List lowestlifep = new ArrayList(); + for (final Player p : game.getPlayers()) { + if (p.getLife() == lowestLife) { + lowestlifep.add(p); + } else if (p.getLife() < lowestLife) { + lowestLife = p.getLife(); + lowestlifep.clear(); + lowestlifep.add(p); + } + } + if (!lowestlifep.contains(this)) { + return false; + } + } + } + + return true; + } + + /** + *

+ * getMaxHandSize. + *

+ * + * @return a int. + */ + public final int getMaxHandSize() { + return maxHandSize; + } + + /** + *

+ * setMaxHandSize. + *

+ * + * @param size + * a int. + */ + public final void setMaxHandSize(int size) { + maxHandSize = size; + } + + /** + * @return the unlimitedHandSize + */ + public boolean isUnlimitedHandSize() { + return unlimitedHandSize; + } + + /** + * @param unlimitedHandSize0 the unlimitedHandSize to set + */ + public void setUnlimitedHandSize(boolean unlimited) { + this.unlimitedHandSize = unlimited; + } + + /** + *

+ * Getter for the field numLandsPlayed. + *

+ * + * @return a int. + */ + public final int getNumLandsPlayed() { + return this.numLandsPlayed; + } + + /** + *

+ * Setter for the field numLandsPlayed. + *

+ * + * @param n + * a int. + */ + public final void setNumLandsPlayed(final int n) { + this.numLandsPlayed = n; + } + + /** + *

+ * Getter for the field lifeGainedThisTurn. + *

+ * + * @return a int. + */ + public final int getLifeGainedThisTurn() { + return this.lifeGainedThisTurn; + } + + /** + *

+ * Setter for the field lifeGainedThisTurn. + *

+ * + * @param n + * a int. + */ + public final void setLifeGainedThisTurn(final int n) { + this.lifeGainedThisTurn = n; + } + + /** + *

+ * Getter for the field lifeLostThisTurn. + *

+ * + * @return a int. + */ + public final int getLifeLostThisTurn() { + return this.lifeLostThisTurn; + } + + /** + *

+ * Setter for the field lifeLostThisTurn. + *

+ * + * @param n + * a int. + */ + public final void setLifeLostThisTurn(final int n) { + this.lifeLostThisTurn = n; + } + + /** + * a Player or Planeswalker that this Player must attack if able in an + * upcoming combat. This is cleared at the end of each combat. + * + * @param o + * Player or Planeswalker (Card) to attack + * + * @since 1.1.01 + */ + public final void setMustAttackEntity(final GameEntity o) { + this.mustAttackEntity = o; + } + + /** + * get the Player object or Card (Planeswalker) object that this Player must + * attack this combat. + * + * @return the Player or Card (Planeswalker) + * @since 1.1.01 + */ + public final GameEntity getMustAttackEntity() { + return this.mustAttackEntity; + } + + // ////////////////////////////// + // + // generic Object overrides + // + // /////////////////////////////// + + /** {@inheritDoc} */ + @Override + public final boolean equals(final Object o) { + if (o instanceof Player) { + final Player p1 = (Player) o; + return p1.getName().equals(this.getName()); + } else { + return false; + } + } + + @Override + public int compareTo(Player o) { + if (o == null) { + return +1; + } + int subtractedHash = o.hashCode() - this.hashCode(); + if (subtractedHash == 0) { + return 0; + } + + return Math.abs(subtractedHash) / subtractedHash; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (41 * (41 + this.getName().hashCode())); + } + + public static class Predicates { + + public static final Predicate NOT_LOST = new Predicate() { + @Override + public boolean apply(Player p) { + return p.getOutcome() == null || p.getOutcome().hasWon(); + } + }; + } + + public static class Accessors { + + public static Function FN_GET_NAME = new Function() { + @Override + public String apply(Player input) { + return input.getName(); + } + }; + + } + + /** + * TODO: Write javadoc for this method. + * @return + */ + public final LobbyPlayer getLobbyPlayer() { + return getController().getLobbyPlayer(); + } + + public final LobbyPlayer getOriginalLobbyPlayer() { + return controllerCreator.getLobbyPlayer(); + } + + public final boolean isMindSlaved() { + return controller.getLobbyPlayer() != controllerCreator.getLobbyPlayer(); + } + + public final void releaseControl() { + if ( controller == controllerCreator ) + return; + + controller = controllerCreator; + game.fireEvent(new GameEventPlayerControl(this, getLobbyPlayer(), null)); + } + + public final void setControllingPlayerController(PlayerController pc) { + LobbyPlayer oldController = getLobbyPlayer(); + controller = pc; + game.fireEvent(new GameEventPlayerControl(this, oldController, pc.getLobbyPlayer())); + } + + private void setOutcome(PlayerOutcome outcome) { + stats.setOutcome(outcome); + } + + /** + * TODO: Write javadoc for this method. + */ + public void onGameOver() { + if (null == stats.getOutcome()) { + + setOutcome(PlayerOutcome.win()); // then won! + } + } + + /** + * use to get a list of creatures in play for a given player. + * + * @param player + * the player to get creatures for + * @return a List containing all creatures a given player has in play + */ + public List getCreaturesInPlay() { + return CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.CREATURES); + } + + /** + * use to get a list of all lands a given player has on the battlefield. + * + * @param player + * the player whose lands we want to get + * @return a List containing all lands the given player has in play + */ + public List getLandsInPlay() { + return CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.LANDS); + } + public boolean isCardInPlay(final String cardName) { + return getZone(ZoneType.Battlefield).contains(CardPredicates.nameEquals(cardName)); + } + + public boolean isCardInCommand(final String cardName) { + return getZone(ZoneType.Command).contains(CardPredicates.nameEquals(cardName)); + } + + public List getColoredCardsInPlay(final String color) { + return CardLists.filter(getCardsIn(ZoneType.Battlefield), CardPredicates.isColor(MagicColor.fromName(color))); + } + + public int getTokenDoublersMagnitude() { + int tokenDoublers = 0; + for (String kw : this.getKeywords()) { + if (kw.equals("TokenDoubler")) { + tokenDoublers++; + } + } + return 1 << tokenDoublers; // pow(a,0) = 1; pow(a,1) = a + } + + public void onCleanupPhase() { + for (Card c : getCardsIn(ZoneType.Hand)) { + c.setDrawnThisTurn(false); + } + resetPreventNextDamage(); + resetPreventNextDamageWithEffect(); + resetNumDrawnThisTurn(); + resetNumDiscardedThisTurn(); + setAttackedWithCreatureThisTurn(false); + setNumLandsPlayed(0); + clearAssignedDamage(); + resetAttackersDeclaredThisTurn(); + } + + public boolean canCastSorcery() { + PhaseHandler now = game.getPhaseHandler(); + return now.isPlayerTurn(this) && now.getPhase().isMain() && game.getStack().isEmpty(); + } + + + /** + *

+ * couldCastSorcery. + * for conditions the stack must only have the sa being checked + *

+ * + * @param player + * a {@link forge.game.player.Player} object. + * @param sa + * a {@link forge.game.player.SpellAbility} object. + * @return a boolean . + */ + public boolean couldCastSorcery(final SpellAbility sa) { + + final Card source = sa.getRootAbility().getSourceCard(); + boolean onlyThis = true; + + for (final Card card : game.getCardsIn(ZoneType.Stack)) { + if (!card.equals(source)) { + onlyThis = false; + //System.out.println("StackCard: " + card + " vs SourceCard: " + source); + } + } + + PhaseHandler now = game.getPhaseHandler(); + //System.out.println("now.isPlayerTurn(player) - " + now.isPlayerTurn(player)); + //System.out.println("now.getPhase().isMain() - " + now.getPhase().isMain()); + //System.out.println("onlyThis - " + onlyThis); + return onlyThis && now.isPlayerTurn(this) && now.getPhase().isMain(); + } + + /** + * TODO: Write javadoc for this method. + * @return + */ + public final PlayerController getController() { + return controller; + } + + public final void setFirstController(PlayerController ctrlr) { + if (controllerCreator != null) { + throw new IllegalStateException("Controller creator already assigned"); + } + controllerCreator = ctrlr; + controller = ctrlr; + } + + /** + * Run a procedure using a different controller + * @param proc + * @param tempController + */ + public void runWithController(Runnable proc, PlayerController tempController) { + PlayerController oldController = controller; + controller = tempController; + try { + proc.run(); + } finally { + controller = oldController; + } + } + + /** + *

+ * skipCombat. + *

+ * + * @return a boolean. + */ + public boolean isSkippingCombat() { + if (hasKeyword("Skip your next combat phase.")) { + return true; + } + if (hasKeyword("Skip your combat phase.")) { + return true; + } + if (hasKeyword("Skip all combat phases of your next turn.")) { + removeKeyword("Skip all combat phases of your next turn."); + addKeyword("Skip all combat phases of this turn."); + return true; + } + if (hasKeyword("Skip all combat phases of this turn.")) { + return true; + } + return false; + } + + public int getStartingHandSize() { + + return this.startingHandSize; + } + + public void setStartingHandSize(int shs) { + + this.startingHandSize = shs; + } + + /** + * + * Takes the top plane of the planar deck and put it face up in the command zone. + * Then runs triggers. + */ + public void planeswalk() + { + planeswalkTo(Arrays.asList(getZone(ZoneType.PlanarDeck).get(0))); + } + + /** + * Puts the planes in the argument and puts them face up in the command zone. + * Then runs triggers. + * + * @param destinations The planes to planeswalk to. + */ + public void planeswalkTo(final List destinations) + { + System.out.println(this.getName() + ": planeswalk to " + destinations.toString()); + currentPlanes.addAll(destinations); + + for(Card c : currentPlanes) { + getZone(ZoneType.PlanarDeck).remove(c); + getZone(ZoneType.Command).add(c); + } + + //DBG + //System.out.println("CurrentPlanes: " + currentPlanes); + //System.out.println("ActivePlanes: " + game.getActivePlanes()); + //System.out.println("CommandPlanes: " + getZone(ZoneType.Command).getCards()); + + game.setActivePlanes(currentPlanes); + //Run PlaneswalkedTo triggers here. + HashMap runParams = new HashMap(); + runParams.put("Card", currentPlanes); + game.getTriggerHandler().runTrigger(TriggerType.PlaneswalkedTo, runParams,false); + } + + /** + * + * Puts my currently active planes, if any, at the bottom of my planar deck. + */ + public void leaveCurrentPlane() + { + if(!currentPlanes.isEmpty()) + { + //Run PlaneswalkedFrom triggers here. + HashMap runParams = new HashMap(); + runParams.put("Card", new ArrayList(currentPlanes)); + game.getTriggerHandler().runTrigger(TriggerType.PlaneswalkedFrom, runParams,false); + + for(Card c : currentPlanes) { + game.getZoneOf(c).remove(c); + c.clearControllers(); + getZone(ZoneType.PlanarDeck).add(c); + } + currentPlanes.clear(); + } + + //DBG + //System.out.println("CurrentPlanes: " + currentPlanes); + //System.out.println("ActivePlanes: " + game.getActivePlanes()); + //System.out.println("CommandPlanes: " + getZone(ZoneType.Command).getCards()); + } + + /** + * + * Sets up the first plane of a round. + */ + public void initPlane() + { + Card firstPlane = null; + while(true) + { + firstPlane = getZone(ZoneType.PlanarDeck).get(0); + getZone(ZoneType.PlanarDeck).remove(firstPlane); + if(firstPlane.getType().contains("Phenomenon")) + { + getZone(ZoneType.PlanarDeck).add(firstPlane); + } + else + { + currentPlanes.add(firstPlane); + getZone(ZoneType.Command).add(firstPlane); + break; + } + } + + game.setActivePlanes(currentPlanes); + } + + /** + *

+ * resetAttackedThisCombat. + *

+ * + * @param player + * a {@link forge.game.player.Player} object. + */ + public final void resetAttackedThisCombat() { + // resets the status of attacked/blocked this phase + List list = CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.CREATURES); + + for (int i = 0; i < list.size(); i++) { + final Card c = list.get(i); + if (c.getDamageHistory().getCreatureAttackedThisCombat()) { + c.getDamageHistory().setCreatureAttackedThisCombat(false); + } + if (c.getDamageHistory().getCreatureBlockedThisCombat()) { + c.getDamageHistory().setCreatureBlockedThisCombat(false); + } + + if (c.getDamageHistory().getCreatureGotBlockedThisCombat()) { + c.getDamageHistory().setCreatureGotBlockedThisCombat(false); + } + } + } + + /** + *

+ * drawMiracle. + *

+ * + * @param card + * a {@link forge.game.card.Card} object. + */ + public final void drawMiracle(final Card card) { + // Whenever a card with miracle is the first card drawn in a turn, + // you may cast it for it's miracle cost + if (card.getMiracleCost() == null) { + return; + } + + final SpellAbility playForMiracleCost = card.getFirstSpellAbility().copy(); + playForMiracleCost.setPayCosts(card.getMiracleCost()); + playForMiracleCost.setStackDescription(card.getName() + " - Cast via Miracle"); + + // TODO Convert this to a Trigger + final Ability miracleTrigger = new MiracleTrigger(card, ManaCost.ZERO, playForMiracleCost); + miracleTrigger.setStackDescription(card.getName() + " - Miracle."); + miracleTrigger.setActivatingPlayer(card.getOwner()); + miracleTrigger.setTrigger(true); + + game.getStack().add(miracleTrigger); + } + + public boolean isSkippingDraw() { + + if (hasKeyword("Skip your next draw step.")) { + removeKeyword("Skip your next draw step."); + return true; + } + + if (hasKeyword("Skip your draw step.")) { + return true; + } + + return false; + } + + public void addInboundToken(Card c) + { + inboundTokens.add(c); + } + + public void removeInboundToken(Card c) + { + inboundTokens.remove(c); + } + + /** + * TODO: Write javadoc for this type. + * + */ + private final class MiracleTrigger extends Ability { + private final SpellAbility miracle; + + /** + * TODO: Write javadoc for Constructor. + * @param sourceCard + * @param manaCost + * @param miracle + */ + private MiracleTrigger(Card sourceCard, ManaCost manaCost, SpellAbility miracle) { + super(sourceCard, manaCost); + this.miracle = miracle; + } + + @Override + public void resolve() { + miracle.setActivatingPlayer(getSourceCard().getOwner()); + // pay miracle cost here. + getSourceCard().getOwner().getController().playMiracle(miracle, getSourceCard()); + } + } + + /** + * TODO: Write javadoc for this method. + */ + public void onMulliganned() { + game.fireEvent(new GameEventMulligan(this)); // quest listener may interfere here + final int newHand = getCardsIn(ZoneType.Hand).size(); + stats.notifyHasMulliganed(); + stats.notifyOpeningHandSize(newHand); + } + + /** + * @return the commander + */ + public Card getCommander() { + return commander; + } + + /** + * @param commander0 the commander to set + */ + public void setCommander(Card commander0) { + this.commander = commander0; + } + + /** + * @return the commanderDamage + */ + public Map getCommanderDamage() { + return commanderDamage; + } + + /** + * @param b isPlayingExtraTurn to set + */ + public void setExtraTurn(boolean b) { + this.isPlayingExtraTrun = b; + } + + /** + * @return a boolean + */ + public boolean isPlayingExtraTurn() { + return isPlayingExtraTrun; + } + + public void initVariantsZones(RegisteredPlayer registeredPlayer) { + + PlayerZone bf = getZone(ZoneType.Battlefield); + Iterable cards = registeredPlayer.getCardsOnBattlefield(); + if (cards != null) { + for (final IPaperCard cp : cards) { + Card c = Card.fromPaperCard(cp, this); + bf.add(c); + c.setSickness(true); + c.setStartsGameInPlay(true); + } + } + + + PlayerZone com = getZone(ZoneType.Command); + // Mainly for avatar, but might find something else here + for (final IPaperCard cp : registeredPlayer.getCardsInCommand()) { + com.add(Card.fromPaperCard(cp, this)); + } + + // Schemes + List sd = new ArrayList(); + for(IPaperCard cp : registeredPlayer.getSchemes()) + sd.add(Card.fromPaperCard(cp, this)); + if ( !sd.isEmpty()) { + for(Card c : sd) { + getZone(ZoneType.SchemeDeck).add(c); + } + getZone(ZoneType.SchemeDeck).shuffle(); + } + + + // Planes + List l = new ArrayList(); + for(IPaperCard cp : registeredPlayer.getPlanes()) + l.add(Card.fromPaperCard(cp, this)); + if ( !l.isEmpty() ) { + for(Card c : l) { + getZone(ZoneType.PlanarDeck).add(c); + } + getZone(ZoneType.PlanarDeck).shuffle(); + } + + // Commander + if(registeredPlayer.getCommander() != null) + { + Card cmd = Card.fromPaperCard(registeredPlayer.getCommander(), this); + cmd.setCommander(true); + com.add(cmd); + setCommander(cmd); + + final Card eff = new Card(getGame().nextCardId()); + eff.setName("Commander effect"); + eff.addType("Effect"); + eff.setToken(true); + eff.setOwner(this); + eff.setColor(cmd.getColor()); + eff.setImmutable(true); + + eff.setSVar("CommanderMoveReplacement", "DB$ ChangeZone | Origin$ Battlefield,Graveyard,Exile,Library | Destination$ Command | Defined$ ReplacedCard"); + eff.setSVar("DBCommanderIncCast","DB$ StoreSVar | SVar$ CommanderCostRaise | Type$ CountSVar | Expression$ CommanderCostRaise/Plus.2"); + eff.setSVar("CommanderCostRaise","Number$0"); + + Trigger t = TriggerHandler.parseTrigger("Mode$ SpellCast | Static$ True | ValidCard$ Card.YouOwn+IsCommander+wasCastFromCommand | Execute$ DBCommanderIncCast", eff, true); + eff.addTrigger(t); + ReplacementEffect r = ReplacementHandler.parseReplacement("Event$ Moved | Destination$ Graveyard,Exile | ValidCard$ Card.IsCommander+YouOwn | Secondary$ True | Optional$ True | OptionalDecider$ You | ReplaceWith$ CommanderMoveReplacement | Description$ If a commander would be put into its owner's graveyard or exile from anywhere, that player may put it into the command zone instead.", eff, true); + eff.addReplacementEffect(r); + eff.addStaticAbility("Mode$ Continuous | EffectZone$ Command | AddKeyword$ May be played | Affected$ Card.YouOwn+IsCommander | AffectedZone$ Command"); + eff.addStaticAbility("Mode$ RaiseCost | EffectZone$ Command | Amount$ CommanderCostRaise | Type$ Spell | ValidCard$ Card.YouOwn+IsCommander+wasCastFromCommand | EffectZone$ All | AffectedZone$ Command,Stack"); + + getZone(ZoneType.Command).add(eff); + } + + } + + public void changeOwnership(Card card) { + // If lost then gained, just clear out of lost. + // If gained then lost, just clear out of gained. + Player oldOwner = card.getOwner(); + + if (this.equals(oldOwner)) { + return; + } + card.setOwner(this); + + if (this.lostOwnership.contains(card)) { + this.lostOwnership.remove(card); + } else { + this.gainedOwnership.add(card); + } + + if (oldOwner.gainedOwnership.contains(card)) { + oldOwner.gainedOwnership.remove(card); + } else { + oldOwner.lostOwnership.add(card); + } + } + + public List getLostOwnership() { + return lostOwnership; + } + + public List getGainedOwnership() { + return gainedOwnership; + } +} diff --git a/forge-game/src/main/java/forge/game/player/PlayerActionConfirmMode.java b/forge-gui/src/main/java/forge/game/player/PlayerActionConfirmMode.java similarity index 100% rename from forge-game/src/main/java/forge/game/player/PlayerActionConfirmMode.java rename to forge-gui/src/main/java/forge/game/player/PlayerActionConfirmMode.java diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-gui/src/main/java/forge/game/player/PlayerController.java similarity index 100% rename from forge-game/src/main/java/forge/game/player/PlayerController.java rename to forge-gui/src/main/java/forge/game/player/PlayerController.java diff --git a/forge-game/src/main/java/forge/game/player/PlayerControllerAi.java b/forge-gui/src/main/java/forge/game/player/PlayerControllerAi.java similarity index 100% rename from forge-game/src/main/java/forge/game/player/PlayerControllerAi.java rename to forge-gui/src/main/java/forge/game/player/PlayerControllerAi.java diff --git a/forge-game/src/main/java/forge/game/player/PlayerOutcome.java b/forge-gui/src/main/java/forge/game/player/PlayerOutcome.java similarity index 100% rename from forge-game/src/main/java/forge/game/player/PlayerOutcome.java rename to forge-gui/src/main/java/forge/game/player/PlayerOutcome.java diff --git a/forge-game/src/main/java/forge/game/player/PlayerStatistics.java b/forge-gui/src/main/java/forge/game/player/PlayerStatistics.java similarity index 100% rename from forge-game/src/main/java/forge/game/player/PlayerStatistics.java rename to forge-gui/src/main/java/forge/game/player/PlayerStatistics.java diff --git a/forge-game/src/main/java/forge/game/player/RegisteredPlayer.java b/forge-gui/src/main/java/forge/game/player/RegisteredPlayer.java similarity index 100% rename from forge-game/src/main/java/forge/game/player/RegisteredPlayer.java rename to forge-gui/src/main/java/forge/game/player/RegisteredPlayer.java diff --git a/forge-game/src/main/java/forge/game/player/package-info.java b/forge-gui/src/main/java/forge/game/player/package-info.java similarity index 94% rename from forge-game/src/main/java/forge/game/player/package-info.java rename to forge-gui/src/main/java/forge/game/player/package-info.java index 2f2011c58fb..fe3263c95cb 100644 --- a/forge-game/src/main/java/forge/game/player/package-info.java +++ b/forge-gui/src/main/java/forge/game/player/package-info.java @@ -1,3 +1,3 @@ -/** Forge Card Game. */ -package forge.game.player; - +/** Forge Card Game. */ +package forge.game.player; + diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceAddCounter.java b/forge-gui/src/main/java/forge/game/replacement/ReplaceAddCounter.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplaceAddCounter.java rename to forge-gui/src/main/java/forge/game/replacement/ReplaceAddCounter.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceCounter.java b/forge-gui/src/main/java/forge/game/replacement/ReplaceCounter.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplaceCounter.java rename to forge-gui/src/main/java/forge/game/replacement/ReplaceCounter.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceDamage.java b/forge-gui/src/main/java/forge/game/replacement/ReplaceDamage.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplaceDamage.java rename to forge-gui/src/main/java/forge/game/replacement/ReplaceDamage.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceDestroy.java b/forge-gui/src/main/java/forge/game/replacement/ReplaceDestroy.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplaceDestroy.java rename to forge-gui/src/main/java/forge/game/replacement/ReplaceDestroy.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceDiscard.java b/forge-gui/src/main/java/forge/game/replacement/ReplaceDiscard.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplaceDiscard.java rename to forge-gui/src/main/java/forge/game/replacement/ReplaceDiscard.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceDraw.java b/forge-gui/src/main/java/forge/game/replacement/ReplaceDraw.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplaceDraw.java rename to forge-gui/src/main/java/forge/game/replacement/ReplaceDraw.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceGainLife.java b/forge-gui/src/main/java/forge/game/replacement/ReplaceGainLife.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplaceGainLife.java rename to forge-gui/src/main/java/forge/game/replacement/ReplaceGainLife.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceGameLoss.java b/forge-gui/src/main/java/forge/game/replacement/ReplaceGameLoss.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplaceGameLoss.java rename to forge-gui/src/main/java/forge/game/replacement/ReplaceGameLoss.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java b/forge-gui/src/main/java/forge/game/replacement/ReplaceMoved.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java rename to forge-gui/src/main/java/forge/game/replacement/ReplaceMoved.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java b/forge-gui/src/main/java/forge/game/replacement/ReplaceProduceMana.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java rename to forge-gui/src/main/java/forge/game/replacement/ReplaceProduceMana.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceSetInMotion.java b/forge-gui/src/main/java/forge/game/replacement/ReplaceSetInMotion.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplaceSetInMotion.java rename to forge-gui/src/main/java/forge/game/replacement/ReplaceSetInMotion.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceTurnFaceUp.java b/forge-gui/src/main/java/forge/game/replacement/ReplaceTurnFaceUp.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplaceTurnFaceUp.java rename to forge-gui/src/main/java/forge/game/replacement/ReplaceTurnFaceUp.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceUntap.java b/forge-gui/src/main/java/forge/game/replacement/ReplaceUntap.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplaceUntap.java rename to forge-gui/src/main/java/forge/game/replacement/ReplaceUntap.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java b/forge-gui/src/main/java/forge/game/replacement/ReplacementEffect.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java rename to forge-gui/src/main/java/forge/game/replacement/ReplacementEffect.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-gui/src/main/java/forge/game/replacement/ReplacementHandler.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java rename to forge-gui/src/main/java/forge/game/replacement/ReplacementHandler.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementLayer.java b/forge-gui/src/main/java/forge/game/replacement/ReplacementLayer.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplacementLayer.java rename to forge-gui/src/main/java/forge/game/replacement/ReplacementLayer.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementResult.java b/forge-gui/src/main/java/forge/game/replacement/ReplacementResult.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplacementResult.java rename to forge-gui/src/main/java/forge/game/replacement/ReplacementResult.java diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementType.java b/forge-gui/src/main/java/forge/game/replacement/ReplacementType.java similarity index 100% rename from forge-game/src/main/java/forge/game/replacement/ReplacementType.java rename to forge-gui/src/main/java/forge/game/replacement/ReplacementType.java diff --git a/forge-game/src/main/java/forge/game/replacement/package-info.java b/forge-gui/src/main/java/forge/game/replacement/package-info.java similarity index 95% rename from forge-game/src/main/java/forge/game/replacement/package-info.java rename to forge-gui/src/main/java/forge/game/replacement/package-info.java index 0efb54793b4..be28b8eee3b 100644 --- a/forge-game/src/main/java/forge/game/replacement/package-info.java +++ b/forge-gui/src/main/java/forge/game/replacement/package-info.java @@ -1,3 +1,3 @@ -/** Forge Card Game. */ -package forge.game.replacement; - +/** Forge Card Game. */ +package forge.game.replacement; + diff --git a/forge-game/src/main/java/forge/game/spellability/Ability.java b/forge-gui/src/main/java/forge/game/spellability/Ability.java similarity index 94% rename from forge-game/src/main/java/forge/game/spellability/Ability.java rename to forge-gui/src/main/java/forge/game/spellability/Ability.java index b78a4275e01..f1920cd82c0 100644 --- a/forge-game/src/main/java/forge/game/spellability/Ability.java +++ b/forge-gui/src/main/java/forge/game/spellability/Ability.java @@ -1,94 +1,94 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import com.esotericsoftware.minlog.Log; - -import forge.card.mana.ManaCost; -import forge.game.Game; -import forge.game.card.Card; -import forge.game.cost.Cost; - -/** - *

- * Abstract Ability class. - *

- * - * @author Forge - * @version $Id: Ability.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public abstract class Ability extends SpellAbility { - - /** - *

- * Constructor for Ability. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - * @param manaCost - * a {@link java.lang.String} object. - */ - public Ability(final Card sourceCard, final ManaCost manaCost) { - this(sourceCard, new Cost(manaCost, true)); - } - public Ability(final Card sourceCard, final Cost cost) { - super(sourceCard, cost); - } - /** - *

- * Constructor for Ability. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - * @param manaCost - * a {@link java.lang.String} object. - * @param stackDescription - * a {@link java.lang.String} object. - */ - public Ability(final Card sourceCard, final ManaCost manaCost, final String stackDescription) { - this(sourceCard, manaCost); - this.setStackDescription(stackDescription); - Log.debug("an ability is being played from" + sourceCard.getName()); - } - - /** {@inheritDoc} */ - @Override - public boolean canPlay() { - final Game game = getActivatingPlayer().getGame(); - if (game.getStack().isSplitSecondOnStack() && !this.isManaAbility()) { - return false; - } - - return this.getSourceCard().isInPlay() && !this.getSourceCard().isFaceDown(); - } - - public static final Ability PLAY_LAND_SURROGATE = new Ability(null, (Cost)null){ - @Override - public boolean canPlay() { - return true; //if this ability is added anywhere, it can be assumed that land can be played - } - @Override - public void resolve() { - throw new RuntimeException("This ability is intended to indicate \"land to play\" choice only"); - } - @Override - public String toUnsuppressedString() { return "Play land"; } - }; -} +/* + * 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 . + */ +package forge.game.spellability; + +import com.esotericsoftware.minlog.Log; + +import forge.card.mana.ManaCost; +import forge.game.Game; +import forge.game.card.Card; +import forge.game.cost.Cost; + +/** + *

+ * Abstract Ability class. + *

+ * + * @author Forge + * @version $Id$ + */ +public abstract class Ability extends SpellAbility { + + /** + *

+ * Constructor for Ability. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + * @param manaCost + * a {@link java.lang.String} object. + */ + public Ability(final Card sourceCard, final ManaCost manaCost) { + this(sourceCard, new Cost(manaCost, true)); + } + public Ability(final Card sourceCard, final Cost cost) { + super(sourceCard, cost); + } + /** + *

+ * Constructor for Ability. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + * @param manaCost + * a {@link java.lang.String} object. + * @param stackDescription + * a {@link java.lang.String} object. + */ + public Ability(final Card sourceCard, final ManaCost manaCost, final String stackDescription) { + this(sourceCard, manaCost); + this.setStackDescription(stackDescription); + Log.debug("an ability is being played from" + sourceCard.getName()); + } + + /** {@inheritDoc} */ + @Override + public boolean canPlay() { + final Game game = getActivatingPlayer().getGame(); + if (game.getStack().isSplitSecondOnStack() && !this.isManaAbility()) { + return false; + } + + return this.getSourceCard().isInPlay() && !this.getSourceCard().isFaceDown(); + } + + public static final Ability PLAY_LAND_SURROGATE = new Ability(null, (Cost)null){ + @Override + public boolean canPlay() { + return true; //if this ability is added anywhere, it can be assumed that land can be played + } + @Override + public void resolve() { + throw new RuntimeException("This ability is intended to indicate \"land to play\" choice only"); + } + @Override + public String toUnsuppressedString() { return "Play land"; } + }; +} diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java b/forge-gui/src/main/java/forge/game/spellability/AbilityActivated.java similarity index 95% rename from forge-game/src/main/java/forge/game/spellability/AbilityActivated.java rename to forge-gui/src/main/java/forge/game/spellability/AbilityActivated.java index 528a705a7e8..8d8c33373c1 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java +++ b/forge-gui/src/main/java/forge/game/spellability/AbilityActivated.java @@ -1,136 +1,136 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import java.util.ArrayList; - -import forge.game.Game; -import forge.game.GlobalRuleChange; -import forge.game.card.Card; -import forge.game.cost.Cost; -import forge.game.cost.CostPayment; -import forge.game.player.Player; -import forge.game.staticability.StaticAbility; -import forge.game.zone.ZoneType; - -/** - *

- * Abstract Ability_Activated class. - *

- * - * @author Forge - * @version $Id: AbilityActivated.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public abstract class AbilityActivated extends SpellAbility implements java.io.Serializable { - /** Constant serialVersionUID=1L. */ - private static final long serialVersionUID = 1L; - - /** - *

- * Constructor for Ability_Activated. - *

- * - * @param card - * a {@link forge.game.card.Card} object. - * @param manacost - * a {@link java.lang.String} object. - */ - public AbilityActivated(final Card card, final String manacost) { - this(card, new Cost(manacost, true), null); - } - - /** - *

- * Constructor for Ability_Activated. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - * @param abCost - * a {@link forge.game.cost.Cost} object. - * @param tgt - * a {@link forge.game.spellability.TargetRestrictions} object. - */ - public AbilityActivated(final Card sourceCard, final Cost abCost, final TargetRestrictions tgt) { - super(sourceCard, abCost); - if ((tgt != null) && tgt.doesTarget()) { - this.setTargetRestrictions(tgt); - } - } - - public abstract AbilityActivated getCopy(); /* { - return null; - } - - /** {@inheritDoc} */ - @Override - public boolean canPlay() { - Player player = getActivatingPlayer(); - if (player == null) { - player = this.getSourceCard().getController(); - } - - final Game game = player.getGame(); - if (game.getStack().isSplitSecondOnStack() && !this.isManaAbility()) { - return false; - } - - final Card c = this.getSourceCard(); - - // CantBeActivated static abilities - for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { - final ArrayList staticAbilities = ca.getStaticAbilities(); - for (final StaticAbility stAb : staticAbilities) { - if (stAb.applyAbility("CantBeActivated", c, this)) { - return false; - } - } - } - - if (c.hasKeyword("CARDNAME's activated abilities can't be activated.") || this.isSuppressed()) { - return false; - } - - if (this.isCycling() - && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCycling)) { - return false; - } - - if (!(this.getRestrictions().canPlay(c, this))) { - return false; - } - - return CostPayment.canPayAdditionalCosts(this.getPayCosts(), this); - } - - /** {@inheritDoc} */ - @Override - public boolean isPossible() { - //consider activated abilities possible always and simply disable if not currently playable - //the exception is to consider them not possible if there's a zone or activator restriction that's not met - return this.getRestrictions().checkZoneRestrictions(this.getSourceCard(), this) && - this.getRestrictions().checkActivatorRestrictions(this.getSourceCard(), this); - } - - /** {@inheritDoc} */ - @Override - public boolean promptIfOnlyPossibleAbility() { - return false; //TODO: allow showing prompt based on whether ability has cost that requires user input and possible "misclick protection" setting - //return !this.isManaAbility(); //prompt user for non-mana activated abilities even is only possible ability - } -} +/* + * 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 . + */ +package forge.game.spellability; + +import java.util.ArrayList; + +import forge.game.Game; +import forge.game.GlobalRuleChange; +import forge.game.card.Card; +import forge.game.cost.Cost; +import forge.game.cost.CostPayment; +import forge.game.player.Player; +import forge.game.staticability.StaticAbility; +import forge.game.zone.ZoneType; + +/** + *

+ * Abstract Ability_Activated class. + *

+ * + * @author Forge + * @version $Id$ + */ +public abstract class AbilityActivated extends SpellAbility implements java.io.Serializable { + /** Constant serialVersionUID=1L. */ + private static final long serialVersionUID = 1L; + + /** + *

+ * Constructor for Ability_Activated. + *

+ * + * @param card + * a {@link forge.game.card.Card} object. + * @param manacost + * a {@link java.lang.String} object. + */ + public AbilityActivated(final Card card, final String manacost) { + this(card, new Cost(manacost, true), null); + } + + /** + *

+ * Constructor for Ability_Activated. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + * @param abCost + * a {@link forge.game.cost.Cost} object. + * @param tgt + * a {@link forge.game.spellability.TargetRestrictions} object. + */ + public AbilityActivated(final Card sourceCard, final Cost abCost, final TargetRestrictions tgt) { + super(sourceCard, abCost); + if ((tgt != null) && tgt.doesTarget()) { + this.setTargetRestrictions(tgt); + } + } + + public abstract AbilityActivated getCopy(); /* { + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean canPlay() { + Player player = getActivatingPlayer(); + if (player == null) { + player = this.getSourceCard().getController(); + } + + final Game game = player.getGame(); + if (game.getStack().isSplitSecondOnStack() && !this.isManaAbility()) { + return false; + } + + final Card c = this.getSourceCard(); + + // CantBeActivated static abilities + for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { + final ArrayList staticAbilities = ca.getStaticAbilities(); + for (final StaticAbility stAb : staticAbilities) { + if (stAb.applyAbility("CantBeActivated", c, this)) { + return false; + } + } + } + + if (c.hasKeyword("CARDNAME's activated abilities can't be activated.") || this.isSuppressed()) { + return false; + } + + if (this.isCycling() + && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCycling)) { + return false; + } + + if (!(this.getRestrictions().canPlay(c, this))) { + return false; + } + + return CostPayment.canPayAdditionalCosts(this.getPayCosts(), this); + } + + /** {@inheritDoc} */ + @Override + public boolean isPossible() { + //consider activated abilities possible always and simply disable if not currently playable + //the exception is to consider them not possible if there's a zone or activator restriction that's not met + return this.getRestrictions().checkZoneRestrictions(this.getSourceCard(), this) && + this.getRestrictions().checkActivatorRestrictions(this.getSourceCard(), this); + } + + /** {@inheritDoc} */ + @Override + public boolean promptIfOnlyPossibleAbility() { + return false; //TODO: allow showing prompt based on whether ability has cost that requires user input and possible "misclick protection" setting + //return !this.isManaAbility(); //prompt user for non-mana activated abilities even is only possible ability + } +} diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-gui/src/main/java/forge/game/spellability/AbilityManaPart.java similarity index 96% rename from forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java rename to forge-gui/src/main/java/forge/game/spellability/AbilityManaPart.java index b839c957ada..05334882970 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-gui/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -1,634 +1,634 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.lang3.StringUtils; - -import forge.card.ColorSet; -import forge.card.MagicColor; -import forge.game.GameType; -import forge.game.card.Card; -import forge.game.mana.Mana; -import forge.game.mana.ManaPool; -import forge.game.player.Player; -import forge.game.replacement.ReplacementEffect; -import forge.game.replacement.ReplacementHandler; -import forge.game.replacement.ReplacementLayer; -import forge.game.replacement.ReplacementResult; -import forge.game.trigger.TriggerType; - -/** - *

- * Abstract AbilityMana class. - *

- * - * @author Forge - * @version $Id: AbilityManaPart.java 24011 2013-12-21 17:34:25Z friarsol $ - */ -public class AbilityManaPart implements java.io.Serializable { - /** Constant serialVersionUID=-6816356991224950520L. */ - private static final long serialVersionUID = -6816356991224950520L; - - private final String origProduced; - private String lastExpressChoice = ""; - private final String manaRestrictions; - private final String cannotCounterSpell; - private final String addsKeywords; - private final String addsCounters; - private final boolean persistentMana; - private String manaReplaceType; - - private transient ArrayList lastManaProduced = new ArrayList(); - - private final transient Card sourceCard; - - - // Spells paid with this mana spell can't be countered. - - - /** - *

- * Constructor for AbilityMana. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - * @param parse - * a {@link java.lang.String} object. - * @param produced - * a {@link java.lang.String} object. - * @param num - * a int. - */ - public AbilityManaPart(final Card sourceCard, final Map params) { - this.sourceCard = sourceCard; - - origProduced = params.containsKey("Produced") ? params.get("Produced") : "1"; - this.manaRestrictions = params.containsKey("RestrictValid") ? params.get("RestrictValid") : ""; - this.cannotCounterSpell = params.get("AddsNoCounter"); - this.addsKeywords = params.get("AddsKeywords"); - this.addsCounters = params.get("AddsCounters"); - this.persistentMana = (null == params.get("PersistentMana")) ? false : - "True".equalsIgnoreCase(params.get("PersistentMana")); - this.manaReplaceType = params.containsKey("ManaReplaceType") ? params.get("ManaReplaceType") : ""; - } - - /** - *

- * produceMana. - *

- * @param ability - */ - public final void produceMana(SpellAbility sa) { - this.produceMana(this.getOrigProduced(), this.getSourceCard().getController(), sa); - } - - /** - *

- * produceMana. - *

- * - * @param produced - * a {@link java.lang.String} object. - * @param player - * a {@link forge.game.player.Player} object. - * @param sa - */ - public final void produceMana(final String produced, final Player player, SpellAbility sa) { - final Card source = this.getSourceCard(); - final ManaPool manaPool = player.getManaPool(); - String afterReplace = applyManaReplacement(sa, produced); - final HashMap repParams = new HashMap(); - repParams.put("Event", "ProduceMana"); - repParams.put("Mana", afterReplace); - repParams.put("Affected", source); - repParams.put("Player", player); - repParams.put("AbilityMana", sa); - if (player.getGame().getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { - return; - } - ColorSet CID = null; - - if (player.getGame().getType() == GameType.Commander) { - CID = player.getCommander().getRules().getColorIdentity(); - } - //clear lastProduced - this.lastManaProduced.clear(); - - // loop over mana produced string - for (final String c : afterReplace.split(" ")) { - if (StringUtils.isNumeric(c)) { - for (int i = Integer.parseInt(c); i > 0; i--) { - this.lastManaProduced.add(new Mana(MagicColor.COLORLESS, source, this)); - } - } else { - byte attemptedMana = MagicColor.fromName(c); - if (CID != null) { - if (!CID.hasAnyColor(attemptedMana)) { - attemptedMana = MagicColor.COLORLESS; - } - } - - this.lastManaProduced.add(new Mana(attemptedMana, source, this)); - } - } - - // add the mana produced to the mana pool - manaPool.add(this.lastManaProduced); - - // Run triggers - final HashMap runParams = new HashMap(); - - runParams.put("Card", source); - runParams.put("Player", player); - runParams.put("AbilityMana", sa); - runParams.put("Produced", afterReplace); - player.getGame().getTriggerHandler().runTrigger(TriggerType.TapsForMana, runParams, false); - // Clear Mana replacement - this.manaReplaceType = ""; - } // end produceMana(String) - - /** - *

- * cannotCounterPaidWith. - *

- * @param saBeingPaid - * - * @return a {@link java.lang.String} object. - */ - public boolean cannotCounterPaidWith(SpellAbility saBeingPaid) { - if (null == cannotCounterSpell) return false; - if ("True".equalsIgnoreCase(cannotCounterSpell)) return true; - - Card source = saBeingPaid.getSourceCard(); - if (source == null) return false; - return source.isValid(cannotCounterSpell, sourceCard.getController(), sourceCard); - } - - /** - *

- * addKeywords. - *

- * @param saBeingPaid - * - * @return a {@link java.lang.String} object. - */ - public boolean addKeywords(SpellAbility saBeingPaid) { - return this.addsKeywords != null; - } - - /** - *

- * getKeywords. - *

- * @param saBeingPaid - * - * @return a {@link java.lang.String} object. - */ - public String getKeywords() { - return this.addsKeywords; - } - - /** - *

- * addsCounters. - *

- * @param saBeingPaid - * - * @return a {@link java.lang.String} object. - */ - public boolean addsCounters(SpellAbility saBeingPaid) { - return this.addsCounters != null; - } - - /** - * createETBCounters - */ - public void createETBCounters(Card c) { - String[] parse = this.addsCounters.split("_"); - // Convert random SVars if there are other cards with this effect - if (c.isValid(parse[0], c.getController(), c)) { - String abStr = "AB$ ChangeZone | Cost$ 0 | Hidden$ True | Origin$ All | Destination$ Battlefield" - + "| Defined$ ReplacedCard | SubAbility$ ManaDBETBCounters"; - String dbStr = "DB$ PutCounter | Defined$ Self | CounterType$ " + parse[1] + " | CounterNum$ " + parse[2]; - try { - Integer.parseInt(parse[2]); - } catch (NumberFormatException ignored) { - dbStr += " | References$ " + parse[2]; - c.setSVar(parse[2], sourceCard.getSVar(parse[2])); - } - c.setSVar("ManaETBCounters", abStr); - c.setSVar("ManaDBETBCounters", dbStr); - - String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield " - + "| ReplaceWith$ ManaETBCounters | Secondary$ True | Description$ CARDNAME" - + " enters the battlefield with " + parse[1] + " counters."; - - ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, c, false); - re.setLayer(ReplacementLayer.Other); - - c.addReplacementEffect(re); - } - } - - /** - *

- * getManaRestrictions. - *

- * - * @return a {@link java.lang.String} object. - */ - public String getManaRestrictions() { - return this.manaRestrictions; - } - - /** - *

- * meetsManaRestrictions. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean. - */ - public boolean meetsManaRestrictions(final SpellAbility sa) { - // No restrictions - if (this.manaRestrictions.isEmpty()) { - return true; - } - - // Loop over restrictions - for (String restriction : this.manaRestrictions.split(",")) { - if (restriction.equals("nonSpell")) { - return !sa.isSpell(); - } - - if (restriction.startsWith("CostContainsX")) { - if (sa.isXCost()) { - return true; - } - continue; - } - - if (sa.isAbility()) { - if (restriction.startsWith("Activated")) { - restriction = restriction.replace("Activated", "Card"); - } - else { - continue; - } - } - - if (sa.getSourceCard() != null) { - if (sa.getSourceCard().isValid(restriction, this.getSourceCard().getController(), this.getSourceCard())) { - return true; - } - } - - } - - return false; - } - - /** - *

- * mana. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String mana() { - if (this.getOrigProduced().contains("Chosen")) { - if (this.getSourceCard() != null && !this.getSourceCard().getChosenColor().isEmpty()) { - return MagicColor.toShortString(this.getSourceCard() - .getChosenColor().get(0)); - } - } - return this.getOrigProduced(); - } - - /** - *

- * setAnyChoice. - *

- * - * @param s a {@link java.lang.String} object. - */ - public void setExpressChoice(String s) { - this.lastExpressChoice = s; - } - - public void setExpressChoice(ColorSet cs) { - StringBuilder sb = new StringBuilder(); - if(cs.hasBlack()) sb.append("B "); - if(cs.hasBlue()) sb.append("U "); - if(cs.hasWhite()) sb.append("W "); - if(cs.hasRed()) sb.append("R "); - if(cs.hasGreen()) sb.append("G "); - this.lastExpressChoice = sb.toString().trim(); - } - - /** - *

- * Getter for the field lastAnyChoice. - *

- * - * @return a {@link java.lang.String} object. - */ - public String getExpressChoice() { - return this.lastExpressChoice; - } - - /** - *

- * clearExpressChoice. - *

- * - */ - public void clearExpressChoice() { - this.lastExpressChoice = ""; - } - - /** - *

- * Getter for the field lastProduced. - *

- * - * @return a {@link java.lang.String} object. - */ - public ArrayList getLastManaProduced() { - return this.lastManaProduced; - } - - /** - *

- * isSnow. - *

- * - * @return a boolean. - */ - public final boolean isSnow() { - return this.getSourceCard().isSnow(); - } - - /** - *

- * isAnyMana. - *

- * - * @return a boolean. - */ - public boolean isAnyMana() { - return this.getOrigProduced().contains("Any"); - } - - /** - *

- * isComboMana. - *

- * - * @return a boolean. - */ - public boolean isComboMana() { - return this.getOrigProduced().contains("Combo"); - } - - /** - *

- * isSpecialMana. - *

- * - * @return a boolean. - */ - public boolean isSpecialMana() { - return this.getOrigProduced().contains("Special"); - } - /** - *

- * canProduce. - *

- * - * @param s - * a {@link java.lang.String} object. - * @return a boolean. - */ - public final boolean canProduce(final String s) { - return canProduce(s, null); - } - - /** - *

- * canProduce. - *

- * - * @param s - * a {@link java.lang.String} object. - * @return a boolean. - */ - public final boolean canProduce(final String s, final SpellAbility sa) { - if (isAnyMana()) { - return true; - } - - if (this.getOrigProduced().contains("Chosen") && sourceCard != null ) { - List chosenCol = this.getSourceCard().getChosenColor(); - if ( !chosenCol.isEmpty() && MagicColor.toShortString(chosenCol.get(0)).contains(s)) { - return true; - } - } - if (sa != null) { - return applyManaReplacement(sa, this.getOrigProduced()).contains(s); - } - return this.getOrigProduced().contains(s); - } - - /** - *

- * isBasic. - *

- * - * @return a boolean. - */ - public final boolean isBasic() { - if (this.getOrigProduced().length() != 1 && !this.getOrigProduced().contains("Any") - && !this.getOrigProduced().contains("Chosen")) { - return false; - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public final boolean equals(final Object o) { - // Mana abilities with same Descriptions are "equal" - if ((o == null) || !(o instanceof AbilityManaPart)) { - return false; - } - - final AbilityManaPart abm = (AbilityManaPart) o; - - return sourceCard.equals(abm.sourceCard) && origProduced.equals(abm.getOrigProduced()); - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return (41 * (41 + this.getSourceCard().hashCode())); - } - - /** - * @return the origProduced - */ - public String getOrigProduced() { - return origProduced; - } - - /** - * @return the color available in combination mana - */ - public String getComboColors() { - String retVal = ""; - if (this.getOrigProduced().contains("Combo")) { - retVal = this.getOrigProduced().replace("Combo ", ""); - if (retVal.contains("Any")) { - retVal = "W U B R G"; - } - if(retVal.contains("ColorIdentity")) { - retVal = ""; - Card cmdr = this.getSourceCard().getController().getCommander(); - if(cmdr == null) - { - return retVal; - } - ColorSet CID = cmdr.getRules().getColorIdentity(); - if(CID.hasWhite()) - { - retVal += "W "; - } - if(CID.hasBlue()) - { - retVal += "U "; - } - if(CID.hasBlack()) - { - retVal += "B "; - } - if(CID.hasRed()) - { - retVal += "R "; - } - if(CID.hasGreen()) - { - retVal += "G "; - } - retVal = retVal.substring(0,retVal.length()-1); - } - } - return retVal; - } - - public Card getSourceCard() { - return sourceCard; - } - - /** - *

- * isPersistentMana. - *

- * - * @return a boolean. - */ - public boolean isPersistentMana() { - return this.persistentMana; - } - - /** - * @return the manaReplaceType - */ - public String getManaReplaceType() { - return manaReplaceType; - } - - /** - * setManaReplaceType. - */ - public void setManaReplaceType(final String type) { - this.manaReplaceType = type; - } - /** - *

- * applyManaReplacement. - *

- * @return a String - */ - public static String applyManaReplacement(final SpellAbility sa, final String original) { - final HashMap repMap = new HashMap(); - final Player act = sa != null ? sa.getActivatingPlayer() : null; - final String manaReplace = sa != null ? sa.getManaPart().getManaReplaceType(): ""; - if (manaReplace.isEmpty()) { - return original; - } - if (manaReplace.startsWith("Any")) { - // Replace any type and amount - return manaReplace.split("->")[1]; - } - final Pattern splitter = Pattern.compile("->"); - // Replace any type - for (String part : manaReplace.split(" & ")) { - final String[] v = splitter.split(part, 2); - if (v[0].equals("Colorless")) { - repMap.put("[0-9][0-9]?", v.length > 1 ? v[1].trim() : ""); - } else { - repMap.put(v[0], v.length > 1 ? v[1].trim() : ""); - } - } - // Handle different replacement simultaneously - Pattern pattern = Pattern.compile(StringUtils.join(repMap.keySet().iterator(), "|")); - Matcher m = pattern.matcher(original); - StringBuffer sb = new StringBuffer(); - while(m.find()) { - if (m.group().matches("[0-9][0-9]?")) { - final String rep = StringUtils.repeat(repMap.get("[0-9][0-9]?") + " ", - Integer.parseInt(m.group())).trim(); - m.appendReplacement(sb, rep); - } else { - m.appendReplacement(sb, repMap.get(m.group())); - } - } - m.appendTail(sb); - String replaced = sb.toString(); - while (replaced.contains("Any")) { - byte rs = MagicColor.GREEN; - if (act != null) { - rs = act.getController().chooseColor("Choose a color", sa, ColorSet.fromMask(MagicColor.ALL_COLORS)); - } - replaced = replaced.replaceFirst("Any", MagicColor.toShortString(rs)); - } - return replaced; - } - -} // end class AbilityMana - +/* + * 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 . + */ +package forge.game.spellability; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; + +import forge.card.ColorSet; +import forge.card.MagicColor; +import forge.game.GameType; +import forge.game.card.Card; +import forge.game.mana.Mana; +import forge.game.mana.ManaPool; +import forge.game.player.Player; +import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementHandler; +import forge.game.replacement.ReplacementLayer; +import forge.game.replacement.ReplacementResult; +import forge.game.trigger.TriggerType; + +/** + *

+ * Abstract AbilityMana class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class AbilityManaPart implements java.io.Serializable { + /** Constant serialVersionUID=-6816356991224950520L. */ + private static final long serialVersionUID = -6816356991224950520L; + + private final String origProduced; + private String lastExpressChoice = ""; + private final String manaRestrictions; + private final String cannotCounterSpell; + private final String addsKeywords; + private final String addsCounters; + private final boolean persistentMana; + private String manaReplaceType; + + private transient ArrayList lastManaProduced = new ArrayList(); + + private final transient Card sourceCard; + + + // Spells paid with this mana spell can't be countered. + + + /** + *

+ * Constructor for AbilityMana. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + * @param parse + * a {@link java.lang.String} object. + * @param produced + * a {@link java.lang.String} object. + * @param num + * a int. + */ + public AbilityManaPart(final Card sourceCard, final Map params) { + this.sourceCard = sourceCard; + + origProduced = params.containsKey("Produced") ? params.get("Produced") : "1"; + this.manaRestrictions = params.containsKey("RestrictValid") ? params.get("RestrictValid") : ""; + this.cannotCounterSpell = params.get("AddsNoCounter"); + this.addsKeywords = params.get("AddsKeywords"); + this.addsCounters = params.get("AddsCounters"); + this.persistentMana = (null == params.get("PersistentMana")) ? false : + "True".equalsIgnoreCase(params.get("PersistentMana")); + this.manaReplaceType = params.containsKey("ManaReplaceType") ? params.get("ManaReplaceType") : ""; + } + + /** + *

+ * produceMana. + *

+ * @param ability + */ + public final void produceMana(SpellAbility sa) { + this.produceMana(this.getOrigProduced(), this.getSourceCard().getController(), sa); + } + + /** + *

+ * produceMana. + *

+ * + * @param produced + * a {@link java.lang.String} object. + * @param player + * a {@link forge.game.player.Player} object. + * @param sa + */ + public final void produceMana(final String produced, final Player player, SpellAbility sa) { + final Card source = this.getSourceCard(); + final ManaPool manaPool = player.getManaPool(); + String afterReplace = applyManaReplacement(sa, produced); + final HashMap repParams = new HashMap(); + repParams.put("Event", "ProduceMana"); + repParams.put("Mana", afterReplace); + repParams.put("Affected", source); + repParams.put("Player", player); + repParams.put("AbilityMana", sa); + if (player.getGame().getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) { + return; + } + ColorSet CID = null; + + if (player.getGame().getType() == GameType.Commander) { + CID = player.getCommander().getRules().getColorIdentity(); + } + //clear lastProduced + this.lastManaProduced.clear(); + + // loop over mana produced string + for (final String c : afterReplace.split(" ")) { + if (StringUtils.isNumeric(c)) { + for (int i = Integer.parseInt(c); i > 0; i--) { + this.lastManaProduced.add(new Mana(MagicColor.COLORLESS, source, this)); + } + } else { + byte attemptedMana = MagicColor.fromName(c); + if (CID != null) { + if (!CID.hasAnyColor(attemptedMana)) { + attemptedMana = MagicColor.COLORLESS; + } + } + + this.lastManaProduced.add(new Mana(attemptedMana, source, this)); + } + } + + // add the mana produced to the mana pool + manaPool.add(this.lastManaProduced); + + // Run triggers + final HashMap runParams = new HashMap(); + + runParams.put("Card", source); + runParams.put("Player", player); + runParams.put("AbilityMana", sa); + runParams.put("Produced", afterReplace); + player.getGame().getTriggerHandler().runTrigger(TriggerType.TapsForMana, runParams, false); + // Clear Mana replacement + this.manaReplaceType = ""; + } // end produceMana(String) + + /** + *

+ * cannotCounterPaidWith. + *

+ * @param saBeingPaid + * + * @return a {@link java.lang.String} object. + */ + public boolean cannotCounterPaidWith(SpellAbility saBeingPaid) { + if (null == cannotCounterSpell) return false; + if ("True".equalsIgnoreCase(cannotCounterSpell)) return true; + + Card source = saBeingPaid.getSourceCard(); + if (source == null) return false; + return source.isValid(cannotCounterSpell, sourceCard.getController(), sourceCard); + } + + /** + *

+ * addKeywords. + *

+ * @param saBeingPaid + * + * @return a {@link java.lang.String} object. + */ + public boolean addKeywords(SpellAbility saBeingPaid) { + return this.addsKeywords != null; + } + + /** + *

+ * getKeywords. + *

+ * @param saBeingPaid + * + * @return a {@link java.lang.String} object. + */ + public String getKeywords() { + return this.addsKeywords; + } + + /** + *

+ * addsCounters. + *

+ * @param saBeingPaid + * + * @return a {@link java.lang.String} object. + */ + public boolean addsCounters(SpellAbility saBeingPaid) { + return this.addsCounters != null; + } + + /** + * createETBCounters + */ + public void createETBCounters(Card c) { + String[] parse = this.addsCounters.split("_"); + // Convert random SVars if there are other cards with this effect + if (c.isValid(parse[0], c.getController(), c)) { + String abStr = "AB$ ChangeZone | Cost$ 0 | Hidden$ True | Origin$ All | Destination$ Battlefield" + + "| Defined$ ReplacedCard | SubAbility$ ManaDBETBCounters"; + String dbStr = "DB$ PutCounter | Defined$ Self | CounterType$ " + parse[1] + " | CounterNum$ " + parse[2]; + try { + Integer.parseInt(parse[2]); + } catch (NumberFormatException ignored) { + dbStr += " | References$ " + parse[2]; + c.setSVar(parse[2], sourceCard.getSVar(parse[2])); + } + c.setSVar("ManaETBCounters", abStr); + c.setSVar("ManaDBETBCounters", dbStr); + + String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield " + + "| ReplaceWith$ ManaETBCounters | Secondary$ True | Description$ CARDNAME" + + " enters the battlefield with " + parse[1] + " counters."; + + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, c, false); + re.setLayer(ReplacementLayer.Other); + + c.addReplacementEffect(re); + } + } + + /** + *

+ * getManaRestrictions. + *

+ * + * @return a {@link java.lang.String} object. + */ + public String getManaRestrictions() { + return this.manaRestrictions; + } + + /** + *

+ * meetsManaRestrictions. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean. + */ + public boolean meetsManaRestrictions(final SpellAbility sa) { + // No restrictions + if (this.manaRestrictions.isEmpty()) { + return true; + } + + // Loop over restrictions + for (String restriction : this.manaRestrictions.split(",")) { + if (restriction.equals("nonSpell")) { + return !sa.isSpell(); + } + + if (restriction.startsWith("CostContainsX")) { + if (sa.isXCost()) { + return true; + } + continue; + } + + if (sa.isAbility()) { + if (restriction.startsWith("Activated")) { + restriction = restriction.replace("Activated", "Card"); + } + else { + continue; + } + } + + if (sa.getSourceCard() != null) { + if (sa.getSourceCard().isValid(restriction, this.getSourceCard().getController(), this.getSourceCard())) { + return true; + } + } + + } + + return false; + } + + /** + *

+ * mana. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String mana() { + if (this.getOrigProduced().contains("Chosen")) { + if (this.getSourceCard() != null && !this.getSourceCard().getChosenColor().isEmpty()) { + return MagicColor.toShortString(this.getSourceCard() + .getChosenColor().get(0)); + } + } + return this.getOrigProduced(); + } + + /** + *

+ * setAnyChoice. + *

+ * + * @param s a {@link java.lang.String} object. + */ + public void setExpressChoice(String s) { + this.lastExpressChoice = s; + } + + public void setExpressChoice(ColorSet cs) { + StringBuilder sb = new StringBuilder(); + if(cs.hasBlack()) sb.append("B "); + if(cs.hasBlue()) sb.append("U "); + if(cs.hasWhite()) sb.append("W "); + if(cs.hasRed()) sb.append("R "); + if(cs.hasGreen()) sb.append("G "); + this.lastExpressChoice = sb.toString().trim(); + } + + /** + *

+ * Getter for the field lastAnyChoice. + *

+ * + * @return a {@link java.lang.String} object. + */ + public String getExpressChoice() { + return this.lastExpressChoice; + } + + /** + *

+ * clearExpressChoice. + *

+ * + */ + public void clearExpressChoice() { + this.lastExpressChoice = ""; + } + + /** + *

+ * Getter for the field lastProduced. + *

+ * + * @return a {@link java.lang.String} object. + */ + public ArrayList getLastManaProduced() { + return this.lastManaProduced; + } + + /** + *

+ * isSnow. + *

+ * + * @return a boolean. + */ + public final boolean isSnow() { + return this.getSourceCard().isSnow(); + } + + /** + *

+ * isAnyMana. + *

+ * + * @return a boolean. + */ + public boolean isAnyMana() { + return this.getOrigProduced().contains("Any"); + } + + /** + *

+ * isComboMana. + *

+ * + * @return a boolean. + */ + public boolean isComboMana() { + return this.getOrigProduced().contains("Combo"); + } + + /** + *

+ * isSpecialMana. + *

+ * + * @return a boolean. + */ + public boolean isSpecialMana() { + return this.getOrigProduced().contains("Special"); + } + /** + *

+ * canProduce. + *

+ * + * @param s + * a {@link java.lang.String} object. + * @return a boolean. + */ + public final boolean canProduce(final String s) { + return canProduce(s, null); + } + + /** + *

+ * canProduce. + *

+ * + * @param s + * a {@link java.lang.String} object. + * @return a boolean. + */ + public final boolean canProduce(final String s, final SpellAbility sa) { + if (isAnyMana()) { + return true; + } + + if (this.getOrigProduced().contains("Chosen") && sourceCard != null ) { + List chosenCol = this.getSourceCard().getChosenColor(); + if ( !chosenCol.isEmpty() && MagicColor.toShortString(chosenCol.get(0)).contains(s)) { + return true; + } + } + if (sa != null) { + return applyManaReplacement(sa, this.getOrigProduced()).contains(s); + } + return this.getOrigProduced().contains(s); + } + + /** + *

+ * isBasic. + *

+ * + * @return a boolean. + */ + public final boolean isBasic() { + if (this.getOrigProduced().length() != 1 && !this.getOrigProduced().contains("Any") + && !this.getOrigProduced().contains("Chosen")) { + return false; + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(final Object o) { + // Mana abilities with same Descriptions are "equal" + if ((o == null) || !(o instanceof AbilityManaPart)) { + return false; + } + + final AbilityManaPart abm = (AbilityManaPart) o; + + return sourceCard.equals(abm.sourceCard) && origProduced.equals(abm.getOrigProduced()); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (41 * (41 + this.getSourceCard().hashCode())); + } + + /** + * @return the origProduced + */ + public String getOrigProduced() { + return origProduced; + } + + /** + * @return the color available in combination mana + */ + public String getComboColors() { + String retVal = ""; + if (this.getOrigProduced().contains("Combo")) { + retVal = this.getOrigProduced().replace("Combo ", ""); + if (retVal.contains("Any")) { + retVal = "W U B R G"; + } + if(retVal.contains("ColorIdentity")) { + retVal = ""; + Card cmdr = this.getSourceCard().getController().getCommander(); + if(cmdr == null) + { + return retVal; + } + ColorSet CID = cmdr.getRules().getColorIdentity(); + if(CID.hasWhite()) + { + retVal += "W "; + } + if(CID.hasBlue()) + { + retVal += "U "; + } + if(CID.hasBlack()) + { + retVal += "B "; + } + if(CID.hasRed()) + { + retVal += "R "; + } + if(CID.hasGreen()) + { + retVal += "G "; + } + retVal = retVal.substring(0,retVal.length()-1); + } + } + return retVal; + } + + public Card getSourceCard() { + return sourceCard; + } + + /** + *

+ * isPersistentMana. + *

+ * + * @return a boolean. + */ + public boolean isPersistentMana() { + return this.persistentMana; + } + + /** + * @return the manaReplaceType + */ + public String getManaReplaceType() { + return manaReplaceType; + } + + /** + * setManaReplaceType. + */ + public void setManaReplaceType(final String type) { + this.manaReplaceType = type; + } + /** + *

+ * applyManaReplacement. + *

+ * @return a String + */ + public static String applyManaReplacement(final SpellAbility sa, final String original) { + final HashMap repMap = new HashMap(); + final Player act = sa != null ? sa.getActivatingPlayer() : null; + final String manaReplace = sa != null ? sa.getManaPart().getManaReplaceType(): ""; + if (manaReplace.isEmpty()) { + return original; + } + if (manaReplace.startsWith("Any")) { + // Replace any type and amount + return manaReplace.split("->")[1]; + } + final Pattern splitter = Pattern.compile("->"); + // Replace any type + for (String part : manaReplace.split(" & ")) { + final String[] v = splitter.split(part, 2); + if (v[0].equals("Colorless")) { + repMap.put("[0-9][0-9]?", v.length > 1 ? v[1].trim() : ""); + } else { + repMap.put(v[0], v.length > 1 ? v[1].trim() : ""); + } + } + // Handle different replacement simultaneously + Pattern pattern = Pattern.compile(StringUtils.join(repMap.keySet().iterator(), "|")); + Matcher m = pattern.matcher(original); + StringBuffer sb = new StringBuffer(); + while(m.find()) { + if (m.group().matches("[0-9][0-9]?")) { + final String rep = StringUtils.repeat(repMap.get("[0-9][0-9]?") + " ", + Integer.parseInt(m.group())).trim(); + m.appendReplacement(sb, rep); + } else { + m.appendReplacement(sb, repMap.get(m.group())); + } + } + m.appendTail(sb); + String replaced = sb.toString(); + while (replaced.contains("Any")) { + byte rs = MagicColor.GREEN; + if (act != null) { + rs = act.getController().chooseColor("Choose a color", sa, ColorSet.fromMask(MagicColor.ALL_COLORS)); + } + replaced = replaced.replaceFirst("Any", MagicColor.toShortString(rs)); + } + return replaced; + } + +} // end class AbilityMana + diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java b/forge-gui/src/main/java/forge/game/spellability/AbilityStatic.java similarity index 93% rename from forge-game/src/main/java/forge/game/spellability/AbilityStatic.java rename to forge-gui/src/main/java/forge/game/spellability/AbilityStatic.java index a47ecfb5f16..707171d8947 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java +++ b/forge-gui/src/main/java/forge/game/spellability/AbilityStatic.java @@ -1,65 +1,65 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import forge.card.mana.ManaCost; -import forge.game.card.Card; -import forge.game.cost.Cost; -import forge.game.player.Player; - -/** - *

- * Abstract Ability_Static class. - *

- * - * @author Forge - * @version $Id: AbilityStatic.java 24007 2013-12-21 01:08:36Z swordshine $ - */ -public abstract class AbilityStatic extends Ability { - /** - *

- * Constructor for Ability_Static. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - * @param manaCost - * a {@link java.lang.String} object. - */ - public AbilityStatic(final Card sourceCard, final ManaCost manaCost) { - super(sourceCard, manaCost); - } - - public AbilityStatic(final Card sourceCard, final Cost abCost, final TargetRestrictions tgt) { - super(sourceCard, abCost); - if ((tgt != null) && tgt.doesTarget()) { - this.setTargetRestrictions(tgt); - } - } - @Override - public boolean canPlay() { - Player player = getActivatingPlayer(); - if (player == null) { - player = this.getSourceCard().getController(); - } - - final Card c = this.getSourceCard(); - - return this.getRestrictions().canPlay(c, this); - } -} +/* + * 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 . + */ +package forge.game.spellability; + +import forge.card.mana.ManaCost; +import forge.game.card.Card; +import forge.game.cost.Cost; +import forge.game.player.Player; + +/** + *

+ * Abstract Ability_Static class. + *

+ * + * @author Forge + * @version $Id$ + */ +public abstract class AbilityStatic extends Ability { + /** + *

+ * Constructor for Ability_Static. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + * @param manaCost + * a {@link java.lang.String} object. + */ + public AbilityStatic(final Card sourceCard, final ManaCost manaCost) { + super(sourceCard, manaCost); + } + + public AbilityStatic(final Card sourceCard, final Cost abCost, final TargetRestrictions tgt) { + super(sourceCard, abCost); + if ((tgt != null) && tgt.doesTarget()) { + this.setTargetRestrictions(tgt); + } + } + @Override + public boolean canPlay() { + Player player = getActivatingPlayer(); + if (player == null) { + player = this.getSourceCard().getController(); + } + + final Card c = this.getSourceCard(); + + return this.getRestrictions().canPlay(c, this); + } +} diff --git a/forge-game/src/main/java/forge/game/spellability/AbilitySub.java b/forge-gui/src/main/java/forge/game/spellability/AbilitySub.java similarity index 94% rename from forge-game/src/main/java/forge/game/spellability/AbilitySub.java rename to forge-gui/src/main/java/forge/game/spellability/AbilitySub.java index 1510b6c81c4..935c1db5828 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilitySub.java +++ b/forge-gui/src/main/java/forge/game/spellability/AbilitySub.java @@ -1,137 +1,137 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import java.util.Map; - -import forge.ai.SpellAbilityAi; -import forge.game.ability.AbilityFactory; -import forge.game.ability.ApiType; -import forge.game.ability.SpellAbilityEffect; -import forge.game.ability.effects.ChangeZoneAllEffect; -import forge.game.ability.effects.ChangeZoneEffect; -import forge.game.ability.effects.ManaEffect; -import forge.game.ability.effects.ManaReflectedEffect; -import forge.game.card.Card; -import forge.game.card.CardFactory; -import forge.game.cost.Cost; -import forge.game.player.Player; - -/** - *

- * Abstract Ability_Sub class. - *

- * - * @author Forge - * @version $Id: AbilitySub.java 23792 2013-11-24 10:02:58Z Max mtg $ - */ -public final class AbilitySub extends SpellAbility implements java.io.Serializable { - /** Constant serialVersionUID=4650634415821733134L. */ - private static final long serialVersionUID = 4650634415821733134L; - - private SpellAbility parent = null; - - /** - *

- * Setter for the field parent. - *

- * - * @param parent - * a {@link forge.game.spellability.SpellAbility} object. - */ - public final void setParent(final SpellAbility parent) { - this.parent = parent; - } - - /** - *

- * Getter for the field parent. - *

- * - * @return a {@link forge.game.spellability.SpellAbility} object. - */ - @Override - public final SpellAbility getParent() { - return this.parent; - } - - - - /** {@inheritDoc} */ - @Override - public boolean canPlay() { - // this should never be on the Stack by itself - return false; - } - - - private final SpellAbilityEffect effect; - private final SpellAbilityAi ai; - - /** - * @return the ai - */ - public SpellAbilityAi getAi() { - return ai; - } - - public AbilitySub(ApiType api0, final Card ca, final TargetRestrictions tgt, Map params0) { - super(ca, Cost.Zero); - this.setTargetRestrictions(tgt); - - api = api0; - params = params0; - ai = api.getAi(); - effect = api.getSpellEffect(); - - if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) { - this.setManaPart(new AbilityManaPart(ca, params)); - } - - if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) { - AbilityFactory.adjustChangeZoneTarget(params, this); - } - } - - public AbilitySub getCopy() { - TargetRestrictions t = getTargetRestrictions() == null ? null : new TargetRestrictions(getTargetRestrictions()); - AbilitySub res = new AbilitySub(api, getSourceCard(), t, params); - CardFactory.copySpellAbility(this, res); - return res; - } - - @Override - public String getStackDescription() { - return effect.getStackDescriptionWithSubs(params, this); - } - - @Override - public boolean canPlayAI(Player aiPlayer) { - return ai.canPlayAIWithSubs(aiPlayer, this); - } - - @Override - public void resolve() { - effect.resolve(this); - } - - @Override - public boolean doTrigger(final boolean mandatory, Player aiPlayer) { - return ai.doTriggerAI(aiPlayer, this, mandatory); - } -} +/* + * 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 . + */ +package forge.game.spellability; + +import java.util.Map; + +import forge.ai.SpellAbilityAi; +import forge.game.ability.AbilityFactory; +import forge.game.ability.ApiType; +import forge.game.ability.SpellAbilityEffect; +import forge.game.ability.effects.ChangeZoneAllEffect; +import forge.game.ability.effects.ChangeZoneEffect; +import forge.game.ability.effects.ManaEffect; +import forge.game.ability.effects.ManaReflectedEffect; +import forge.game.card.Card; +import forge.game.card.CardFactory; +import forge.game.cost.Cost; +import forge.game.player.Player; + +/** + *

+ * Abstract Ability_Sub class. + *

+ * + * @author Forge + * @version $Id$ + */ +public final class AbilitySub extends SpellAbility implements java.io.Serializable { + /** Constant serialVersionUID=4650634415821733134L. */ + private static final long serialVersionUID = 4650634415821733134L; + + private SpellAbility parent = null; + + /** + *

+ * Setter for the field parent. + *

+ * + * @param parent + * a {@link forge.game.spellability.SpellAbility} object. + */ + public final void setParent(final SpellAbility parent) { + this.parent = parent; + } + + /** + *

+ * Getter for the field parent. + *

+ * + * @return a {@link forge.game.spellability.SpellAbility} object. + */ + @Override + public final SpellAbility getParent() { + return this.parent; + } + + + + /** {@inheritDoc} */ + @Override + public boolean canPlay() { + // this should never be on the Stack by itself + return false; + } + + + private final SpellAbilityEffect effect; + private final SpellAbilityAi ai; + + /** + * @return the ai + */ + public SpellAbilityAi getAi() { + return ai; + } + + public AbilitySub(ApiType api0, final Card ca, final TargetRestrictions tgt, Map params0) { + super(ca, Cost.Zero); + this.setTargetRestrictions(tgt); + + api = api0; + params = params0; + ai = api.getAi(); + effect = api.getSpellEffect(); + + if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) { + this.setManaPart(new AbilityManaPart(ca, params)); + } + + if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) { + AbilityFactory.adjustChangeZoneTarget(params, this); + } + } + + public AbilitySub getCopy() { + TargetRestrictions t = getTargetRestrictions() == null ? null : new TargetRestrictions(getTargetRestrictions()); + AbilitySub res = new AbilitySub(api, getSourceCard(), t, params); + CardFactory.copySpellAbility(this, res); + return res; + } + + @Override + public String getStackDescription() { + return effect.getStackDescriptionWithSubs(params, this); + } + + @Override + public boolean canPlayAI(Player aiPlayer) { + return ai.canPlayAIWithSubs(aiPlayer, this); + } + + @Override + public void resolve() { + effect.resolve(this); + } + + @Override + public boolean doTrigger(final boolean mandatory, Player aiPlayer) { + return ai.doTriggerAI(aiPlayer, this, mandatory); + } +} diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityTriggered.java b/forge-gui/src/main/java/forge/game/spellability/AbilityTriggered.java similarity index 95% rename from forge-game/src/main/java/forge/game/spellability/AbilityTriggered.java rename to forge-gui/src/main/java/forge/game/spellability/AbilityTriggered.java index cf2b4ae7f97..7ea96defdaf 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityTriggered.java +++ b/forge-gui/src/main/java/forge/game/spellability/AbilityTriggered.java @@ -1,189 +1,189 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import java.util.Arrays; - -import forge.Command; -import forge.card.CardCharacteristicName; -import forge.card.mana.ManaCost; -import forge.game.card.Card; -import forge.game.card.CardCharacteristics; -import forge.game.trigger.ZCTrigger; - -/** - *

- * Ability_Triggered class. - *

- * - * @author Forge - * @version $Id: AbilityTriggered.java 23789 2013-11-24 09:24:50Z Max mtg $ - */ -public class AbilityTriggered extends Ability implements Command { - - /** - * - */ - private static final long serialVersionUID = 4970998845621323960L; - - /** The restrictions. */ - private String[] restrictions; - - /** The trigger. */ - private ZCTrigger trigger; - - /** - * Gets the trigger. - * - * @return the trigger - */ - public final ZCTrigger getTrigger() { - return this.trigger; - } - - /** - * Sets the trigger. - * - * @param trigger - * the new trigger - */ - public final void setTrigger(final ZCTrigger trigger) { - this.trigger = trigger; - } - - /** The todo. */ - private final Command todo; - - /** - *

- * Constructor for Ability_Triggered. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - * @param sourceCommand - * a {@link forge.Command} object. - * @param situation - * a {@link forge.game.trigger.ZCTrigger} object. - */ - public AbilityTriggered(final Card sourceCard, final Command sourceCommand, final ZCTrigger situation) { - super(sourceCard, ManaCost.ZERO); - this.todo = sourceCommand; - this.trigger = situation; - if (this.todo instanceof AbilityTriggered) { - this.setStackDescription(((SpellAbility) this.todo).getStackDescription()); - this.restrictions = ((AbilityTriggered) this.todo).restrictions; - } else { - this.setStackDescription("Triggered ability: " + sourceCard + " " + situation); - if (!sourceCard.isInAlternateState()) { - this.restrictions = new String[] { "named " + sourceCard.getName() }; - } - else { - CardCharacteristics origChar = sourceCard.getState(CardCharacteristicName.Original); - this.restrictions = new String[] { "named " + origChar.getName() }; - } - } - } - - /** {@inheritDoc} */ - @Override - public final boolean canPlay() { - return false; - } // this is a triggered ability: it cannot be "played" - - /** {@inheritDoc} */ - @Override - public final void resolve() { - this.todo.run(); - } - - /** - *

- * execute. - *

- */ - @Override - public final void run() { - this.resolve(); - } - - /** - *

- * triggerFor. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean triggerFor(final Card c) { - return c != null && c.isValid(this.restrictions, c.getController(), c); - } - - /** - *

- * triggerOnZoneChange. - *

- * - * @param sourceZone - * a {@link java.lang.String} object. - * @param destinationZone - * a {@link java.lang.String} object. - * @return a boolean. - */ - public final boolean triggerOnZoneChange(final String sourceZone, final String destinationZone) { - return this.trigger.triggerOn(sourceZone, destinationZone); - } - - /** {@inheritDoc} */ - @Override - public final boolean equals(final Object o) { - // TODO triggers affecting other - // cards - if (!(o instanceof AbilityTriggered)) { - return false; - } - final AbilityTriggered tmp = (AbilityTriggered) o; - return tmp.getSourceCard().equals(this.getSourceCard()) && tmp.trigger.equals(this.trigger) - && tmp.todo.equals(this.todo) && Arrays.equals(tmp.restrictions, this.restrictions); - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return (41 * (41 + this.getSourceCard().hashCode() + this.trigger.hashCode() + this.todo.hashCode() + this.restrictions - .hashCode())); - } - - /** - *

- * isBasic. - *

- * - * @return a boolean. - */ - public final boolean isBasic() { - return (this.restrictions.length == 1) - && this.restrictions[0].equals("named " + this.getSourceCard().getName()); - } - - @Override - public final boolean isTrigger() { - 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 . + */ +package forge.game.spellability; + +import java.util.Arrays; + +import forge.Command; +import forge.card.CardCharacteristicName; +import forge.card.mana.ManaCost; +import forge.game.card.Card; +import forge.game.card.CardCharacteristics; +import forge.game.trigger.ZCTrigger; + +/** + *

+ * Ability_Triggered class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class AbilityTriggered extends Ability implements Command { + + /** + * + */ + private static final long serialVersionUID = 4970998845621323960L; + + /** The restrictions. */ + private String[] restrictions; + + /** The trigger. */ + private ZCTrigger trigger; + + /** + * Gets the trigger. + * + * @return the trigger + */ + public final ZCTrigger getTrigger() { + return this.trigger; + } + + /** + * Sets the trigger. + * + * @param trigger + * the new trigger + */ + public final void setTrigger(final ZCTrigger trigger) { + this.trigger = trigger; + } + + /** The todo. */ + private final Command todo; + + /** + *

+ * Constructor for Ability_Triggered. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + * @param sourceCommand + * a {@link forge.Command} object. + * @param situation + * a {@link forge.game.trigger.ZCTrigger} object. + */ + public AbilityTriggered(final Card sourceCard, final Command sourceCommand, final ZCTrigger situation) { + super(sourceCard, ManaCost.ZERO); + this.todo = sourceCommand; + this.trigger = situation; + if (this.todo instanceof AbilityTriggered) { + this.setStackDescription(((SpellAbility) this.todo).getStackDescription()); + this.restrictions = ((AbilityTriggered) this.todo).restrictions; + } else { + this.setStackDescription("Triggered ability: " + sourceCard + " " + situation); + if (!sourceCard.isInAlternateState()) { + this.restrictions = new String[] { "named " + sourceCard.getName() }; + } + else { + CardCharacteristics origChar = sourceCard.getState(CardCharacteristicName.Original); + this.restrictions = new String[] { "named " + origChar.getName() }; + } + } + } + + /** {@inheritDoc} */ + @Override + public final boolean canPlay() { + return false; + } // this is a triggered ability: it cannot be "played" + + /** {@inheritDoc} */ + @Override + public final void resolve() { + this.todo.run(); + } + + /** + *

+ * execute. + *

+ */ + @Override + public final void run() { + this.resolve(); + } + + /** + *

+ * triggerFor. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean triggerFor(final Card c) { + return c != null && c.isValid(this.restrictions, c.getController(), c); + } + + /** + *

+ * triggerOnZoneChange. + *

+ * + * @param sourceZone + * a {@link java.lang.String} object. + * @param destinationZone + * a {@link java.lang.String} object. + * @return a boolean. + */ + public final boolean triggerOnZoneChange(final String sourceZone, final String destinationZone) { + return this.trigger.triggerOn(sourceZone, destinationZone); + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(final Object o) { + // TODO triggers affecting other + // cards + if (!(o instanceof AbilityTriggered)) { + return false; + } + final AbilityTriggered tmp = (AbilityTriggered) o; + return tmp.getSourceCard().equals(this.getSourceCard()) && tmp.trigger.equals(this.trigger) + && tmp.todo.equals(this.todo) && Arrays.equals(tmp.restrictions, this.restrictions); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (41 * (41 + this.getSourceCard().hashCode() + this.trigger.hashCode() + this.todo.hashCode() + this.restrictions + .hashCode())); + } + + /** + *

+ * isBasic. + *

+ * + * @return a boolean. + */ + public final boolean isBasic() { + return (this.restrictions.length == 1) + && this.restrictions[0].equals("named " + this.getSourceCard().getName()); + } + + @Override + public final boolean isTrigger() { + return true; + } +} diff --git a/forge-game/src/main/java/forge/game/spellability/ISpellAbility.java b/forge-gui/src/main/java/forge/game/spellability/ISpellAbility.java similarity index 100% rename from forge-game/src/main/java/forge/game/spellability/ISpellAbility.java rename to forge-gui/src/main/java/forge/game/spellability/ISpellAbility.java diff --git a/forge-game/src/main/java/forge/game/spellability/OptionalCost.java b/forge-gui/src/main/java/forge/game/spellability/OptionalCost.java similarity index 100% rename from forge-game/src/main/java/forge/game/spellability/OptionalCost.java rename to forge-gui/src/main/java/forge/game/spellability/OptionalCost.java diff --git a/forge-game/src/main/java/forge/game/spellability/Spell.java b/forge-gui/src/main/java/forge/game/spellability/Spell.java similarity index 95% rename from forge-game/src/main/java/forge/game/spellability/Spell.java rename to forge-gui/src/main/java/forge/game/spellability/Spell.java index 1627a6ba743..d0ed4345a0b 100644 --- a/forge-game/src/main/java/forge/game/spellability/Spell.java +++ b/forge-gui/src/main/java/forge/game/spellability/Spell.java @@ -1,213 +1,214 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import java.util.ArrayList; -import java.util.List; - - -import forge.game.Game; -import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CardLists; -import forge.game.cost.Cost; -import forge.game.cost.CostPayment; -import forge.game.player.Player; -import forge.game.staticability.StaticAbility; -import forge.game.zone.ZoneType; -import forge.util.Expressions; - -/** - *

- * Abstract Spell class. - *

- * - * @author Forge - * @version $Id: Spell.java 23792 2013-11-24 10:02:58Z Max mtg $ - */ -public abstract class Spell extends SpellAbility implements java.io.Serializable, Cloneable { - - /** Constant serialVersionUID=-7930920571482203460L. */ - private static final long serialVersionUID = -7930920571482203460L; - - private boolean castFaceDown = false; - - /** - *

- * Constructor for Spell. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - */ - public Spell(final Card sourceCard) { - this(sourceCard, new Cost(sourceCard.getManaCost(), false)); - } - public Spell(final Card sourceCard, final Cost abCost) { - super(sourceCard, abCost); - - this.setStackDescription(sourceCard.getSpellText()); - this.getRestrictions().setZone(ZoneType.Hand); - } - - /** {@inheritDoc} */ - @Override - public boolean canPlay() { - final Card card = this.getSourceCard(); - Player activator = this.getActivatingPlayer(); - if (activator == null) { - activator = card.getController(); - if (activator == null) { - return false; - } - } - - final Game game = activator.getGame(); - if (game.getStack().isSplitSecondOnStack()) { - return false; - } - - if (!(card.isInstant() || activator.canCastSorcery() || card.hasKeyword("Flash") - || this.getRestrictions().isInstantSpeed() - || activator.hasKeyword("You may cast nonland cards as though they had flash.") - || card.hasStartOfKeyword("You may cast CARDNAME as though it had flash."))) { - return false; - } - - if (!this.getRestrictions().canPlay(card, this)) { - return false; - } - - // for uncastables like lotus bloom, check if manaCost is blank - if (isBasicSpell() && card.getManaCost().isNoCost()) { - return false; - } - - if (this.getPayCosts() != null) { - if (!CostPayment.canPayAdditionalCosts(this.getPayCosts(), this)) { - return false; - } - } - - return checkOtherRestrictions(); - } // canPlay() - - public boolean checkOtherRestrictions() { - final Card source = this.getSourceCard(); - Player activator = getActivatingPlayer(); - final Game game = activator.getGame(); - // CantBeCast static abilities - final List allp = new ArrayList(game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))); - allp.add(source); - for (final Card ca : allp) { - final ArrayList staticAbilities = ca.getStaticAbilities(); - for (final StaticAbility stAb : staticAbilities) { - if (stAb.applyAbility("CantBeCast", source, activator)) { - return false; - } - } - } - return true; - } - - /** {@inheritDoc} */ - @Override - public boolean canPlayAI(Player aiPlayer) { - final Card card = this.getSourceCard(); - final Game game = getActivatingPlayer().getGame(); - if (card.getSVar("NeedsToPlay").length() > 0) { - final String needsToPlay = card.getSVar("NeedsToPlay"); - List list = game.getCardsIn(ZoneType.Battlefield); - - list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card); - if (list.isEmpty()) { - return false; - } - } - if (card.getSVar("NeedsToPlayVar").length() > 0) { - final String needsToPlay = card.getSVar("NeedsToPlayVar"); - int x = 0; - int y = 0; - String sVar = needsToPlay.split(" ")[0]; - String comparator = needsToPlay.split(" ")[1]; - String compareTo = comparator.substring(2); - try { - x = Integer.parseInt(sVar); - } catch (final NumberFormatException e) { - x = CardFactoryUtil.xCount(card, card.getSVar(sVar)); - } - try { - y = Integer.parseInt(compareTo); - } catch (final NumberFormatException e) { - y = CardFactoryUtil.xCount(card, card.getSVar(compareTo)); - } - if (!Expressions.compare(x, comparator, y)) { - return false; - } - } - - return super.canPlayAI(aiPlayer); - } - - - /** {@inheritDoc} */ - @Override - public final Object clone() { - try { - return super.clone(); - } catch (final Exception ex) { - throw new RuntimeException("Spell : clone() error, " + ex); - } - } - - @Override - public boolean isSpell() { return true; } - @Override - public boolean isAbility() { return false; } - - - /** - *

- * canPlayFromEffectAI. - *

- * - * @param mandatory - * can the controller chose not to play the spell - * @param withOutManaCost - * is the spell cast without paying mana - * @return a boolean. - */ - public boolean canPlayFromEffectAI(Player aiPlayer, boolean mandatory, boolean withOutManaCost) { - return canPlayAI(aiPlayer); - } - - /** - * @return the castFaceDown - */ - public boolean isCastFaceDown() { - return castFaceDown; - } - - /** - * @param faceDown the castFaceDown to set - */ - public void setCastFaceDown(boolean faceDown) { - this.castFaceDown = faceDown; - } - -} +/* + * 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 . + */ +package forge.game.spellability; + +import java.util.ArrayList; +import java.util.List; + +import forge.error.BugReporter; +import forge.game.Game; +import forge.game.card.Card; +import forge.game.card.CardFactoryUtil; +import forge.game.card.CardLists; +import forge.game.cost.Cost; +import forge.game.cost.CostPayment; +import forge.game.player.Player; +import forge.game.staticability.StaticAbility; +import forge.game.zone.ZoneType; +import forge.util.Expressions; + +/** + *

+ * Abstract Spell class. + *

+ * + * @author Forge + * @version $Id$ + */ +public abstract class Spell extends SpellAbility implements java.io.Serializable, Cloneable { + + /** Constant serialVersionUID=-7930920571482203460L. */ + private static final long serialVersionUID = -7930920571482203460L; + + private boolean castFaceDown = false; + + /** + *

+ * Constructor for Spell. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + */ + public Spell(final Card sourceCard) { + this(sourceCard, new Cost(sourceCard.getManaCost(), false)); + } + public Spell(final Card sourceCard, final Cost abCost) { + super(sourceCard, abCost); + + this.setStackDescription(sourceCard.getSpellText()); + this.getRestrictions().setZone(ZoneType.Hand); + } + + /** {@inheritDoc} */ + @Override + public boolean canPlay() { + final Card card = this.getSourceCard(); + Player activator = this.getActivatingPlayer(); + if (activator == null) { + activator = card.getController(); + if (activator == null) { + return false; + } + } + + final Game game = activator.getGame(); + if (game.getStack().isSplitSecondOnStack()) { + return false; + } + + if (!(card.isInstant() || activator.canCastSorcery() || card.hasKeyword("Flash") + || this.getRestrictions().isInstantSpeed() + || activator.hasKeyword("You may cast nonland cards as though they had flash.") + || card.hasStartOfKeyword("You may cast CARDNAME as though it had flash."))) { + return false; + } + + if (!this.getRestrictions().canPlay(card, this)) { + return false; + } + + // for uncastables like lotus bloom, check if manaCost is blank + if (isBasicSpell() && card.getManaCost().isNoCost()) { + return false; + } + + if (this.getPayCosts() != null) { + if (!CostPayment.canPayAdditionalCosts(this.getPayCosts(), this)) { + return false; + } + } + + return checkOtherRestrictions(); + } // canPlay() + + public boolean checkOtherRestrictions() { + final Card source = this.getSourceCard(); + Player activator = getActivatingPlayer(); + final Game game = activator.getGame(); + // CantBeCast static abilities + final List allp = new ArrayList(game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))); + allp.add(source); + for (final Card ca : allp) { + final ArrayList staticAbilities = ca.getStaticAbilities(); + for (final StaticAbility stAb : staticAbilities) { + if (stAb.applyAbility("CantBeCast", source, activator)) { + return false; + } + } + } + return true; + } + + /** {@inheritDoc} */ + @Override + public boolean canPlayAI(Player aiPlayer) { + final Card card = this.getSourceCard(); + final Game game = getActivatingPlayer().getGame(); + if (card.getSVar("NeedsToPlay").length() > 0) { + final String needsToPlay = card.getSVar("NeedsToPlay"); + List list = game.getCardsIn(ZoneType.Battlefield); + + list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card); + if (list.isEmpty()) { + return false; + } + } + if (card.getSVar("NeedsToPlayVar").length() > 0) { + final String needsToPlay = card.getSVar("NeedsToPlayVar"); + int x = 0; + int y = 0; + String sVar = needsToPlay.split(" ")[0]; + String comparator = needsToPlay.split(" ")[1]; + String compareTo = comparator.substring(2); + try { + x = Integer.parseInt(sVar); + } catch (final NumberFormatException e) { + x = CardFactoryUtil.xCount(card, card.getSVar(sVar)); + } + try { + y = Integer.parseInt(compareTo); + } catch (final NumberFormatException e) { + y = CardFactoryUtil.xCount(card, card.getSVar(compareTo)); + } + if (!Expressions.compare(x, comparator, y)) { + return false; + } + } + + return super.canPlayAI(aiPlayer); + } + + + /** {@inheritDoc} */ + @Override + public final Object clone() { + try { + return super.clone(); + } catch (final Exception ex) { + BugReporter.reportException(ex); + throw new RuntimeException("Spell : clone() error, " + ex); + } + } + + @Override + public boolean isSpell() { return true; } + @Override + public boolean isAbility() { return false; } + + + /** + *

+ * canPlayFromEffectAI. + *

+ * + * @param mandatory + * can the controller chose not to play the spell + * @param withOutManaCost + * is the spell cast without paying mana + * @return a boolean. + */ + public boolean canPlayFromEffectAI(Player aiPlayer, boolean mandatory, boolean withOutManaCost) { + return canPlayAI(aiPlayer); + } + + /** + * @return the castFaceDown + */ + public boolean isCastFaceDown() { + return castFaceDown; + } + + /** + * @param faceDown the castFaceDown to set + */ + public void setCastFaceDown(boolean faceDown) { + this.castFaceDown = faceDown; + } + +} diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-gui/src/main/java/forge/game/spellability/SpellAbility.java similarity index 96% rename from forge-game/src/main/java/forge/game/spellability/SpellAbility.java rename to forge-gui/src/main/java/forge/game/spellability/SpellAbility.java index 51cd621fcd1..f918a56de6c 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-gui/src/main/java/forge/game/spellability/SpellAbility.java @@ -1,1721 +1,1721 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.lang3.StringUtils; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - -import forge.card.mana.ManaCost; -import forge.game.Game; -import forge.game.GameEntity; -import forge.game.GameObject; -import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; -import forge.game.card.Card; -import forge.game.cost.Cost; -import forge.game.cost.CostPartMana; -import forge.game.mana.Mana; -import forge.game.player.Player; -import forge.util.TextUtil; - -//only SpellAbility can go on the stack -//override any methods as needed -/** - *

- * Abstract SpellAbility class. - *

- * - * @author Forge - * @version $Id: SpellAbility.java 24065 2013-12-25 08:21:25Z Max mtg $ - */ -public abstract class SpellAbility extends GameObject implements ISpellAbility { - - public static class EmptySa extends SpellAbility { - public EmptySa(Card sourceCard) { super(sourceCard, Cost.Zero); setActivatingPlayer(sourceCard.getController());} - public EmptySa(ApiType api, Card sourceCard) { super(sourceCard, Cost.Zero); setActivatingPlayer(sourceCard.getController()); this.api = api;} - public EmptySa(Card sourceCard, Player activator) { super(sourceCard, Cost.Zero); setActivatingPlayer(activator);} - public EmptySa(ApiType api, Card sourceCard, Player activator) { super(sourceCard, Cost.Zero); setActivatingPlayer(activator); this.api = api;} - @Override public void resolve() {} - @Override public boolean canPlay() { return false; } - } - - // choices for constructor isPermanent argument - private String description = ""; - private String stackDescription = ""; - private ManaCost multiKickerManaCost = null; - private Player activatingPlayer = null; - - private boolean temporary; // that is given by some static ability - private boolean basicLandAbility; // granted by basic land type - - private Card sourceCard; - private Card grantorCard = null; // card which grants the ability (equipment or owner of static ability that gave this one) - - private List splicedCards = null; -// private List targetList; - // targetList doesn't appear to be used anymore - - private boolean basicSpell = true; - private boolean trigger = false; - private boolean optionalTrigger = false; - private int sourceTrigger = -1; - private boolean temporarilySuppressed = false; - - private boolean flashBackAbility = false; - private boolean cycling = false; - private boolean delve = false; - private boolean offering = false; - private boolean morphup = false; - - /** The pay costs. */ - private Cost payCosts = null; - private SpellAbilityRestriction restrictions = new SpellAbilityRestriction(); - private SpellAbilityCondition conditions = new SpellAbilityCondition(); - private AbilitySub subAbility = null; - - protected Map params = null; - protected ApiType api = null; - - private final ArrayList payingMana = new ArrayList(); - private final List paidAbilities = new ArrayList(); - - private HashMap> paidLists = new HashMap>(); - - private HashMap triggeringObjects = new HashMap(); - - private HashMap replacingObjects = new HashMap(); - - private List tappedForConvoke = new ArrayList(); - private Card sacrificedAsOffering = null; - private int conspireInstances = 0; - private boolean madness = false; - - private HashMap sVars = new HashMap(); - - private AbilityManaPart manaPart = null; - - private boolean undoable; - - private boolean isCopied = false; - - public final AbilityManaPart getManaPart() { - return manaPart; - } - - public final AbilityManaPart getManaPartRecursive() { - SpellAbility tail = this; - while (tail != null) { - if(tail.manaPart != null) - return tail.manaPart; - tail = tail.getSubAbility(); - } - return null; - } - - public final boolean isManaAbility() { - // Check whether spell or ability first - if (this.isSpell()) - return false; - // without a target - if (this.usesTargeting()) return false; - if (getRestrictions() != null && getRestrictions().getPlaneswalker()) - return false; //Loyalty ability, not a mana ability. - - return getManaPartRecursive() != null; - } - - protected final void setManaPart(AbilityManaPart manaPart) { - this.manaPart = manaPart; - } - - public final String getSVar(final String name) { - return sVars.get(name) != null ? sVars.get(name) : ""; - } - - public final void setSVar(final String name, final String value) { - sVars.put(name, value); - } - - public Set getSVars() { - return sVars.keySet(); - } - - /** - *

- * Constructor for SpellAbility. - *

- * - * @param spellOrAbility - * a int. - * @param iSourceCard - * a {@link forge.game.card.Card} object. - */ - public SpellAbility(final Card iSourceCard, Cost toPay) { - this.sourceCard = iSourceCard; - this.payCosts = toPay; - } - - // Spell, and Ability, and other Ability objects override this method - /** - *

- * canPlay. - *

- * - * @return a boolean. - */ - public abstract boolean canPlay(); - - /** - *

- * isPossible. - *

- * - * @return a boolean. - */ - public boolean isPossible() { - return canPlay(); //by default, ability is only possible if it can be played - } - - /** - *

- * promptIfOnlyPossibleAbility. - *

- * - * @return a boolean. - */ - public boolean promptIfOnlyPossibleAbility() { - return false; //by default, don't prompt user if ability is only possible ability - } - - // all Spell's and Abilities must override this method - /** - *

- * resolve. - *

- */ - public abstract void resolve(); - - /** - *

- * canPlayAI. - *

- * - * @return a boolean. - */ - public /*final*/ boolean canPlayAI(Player aiPlayer) { - return true; - } - - // This should be overridden by ALL AFs - /** - *

- * doTrigger. - *

- * - * @param mandatory - * a boolean. - * @param ai TODO - * @return a boolean. - */ - public boolean doTrigger(final boolean mandatory, Player ai) { - return false; - } - - /** - *

- * Getter for the field multiKickerManaCost. - *

- * - * @return a {@link java.lang.String} object. - */ - public ManaCost getMultiKickerManaCost() { - return this.multiKickerManaCost; - } - - /** - *

- * Setter for the field multiKickerManaCost. - *

- * - * @param cost - * a {@link java.lang.String} object. - */ - public void setMultiKickerManaCost(final ManaCost cost) { - this.multiKickerManaCost = cost; - } - - - /** - *

- * Getter for the field activatingPlayer. - *

- * - * @return a {@link forge.game.player.Player} object. - */ - public Player getActivatingPlayer() { - return this.activatingPlayer; - } - - /** - *

- * Setter for the field activatingPlayer. - *

- * - * @param player - * a {@link forge.game.player.Player} object. - */ - public void setActivatingPlayer(final Player player) { - // trickle down activating player - this.activatingPlayer = player; - if (this.subAbility != null) { - this.subAbility.setActivatingPlayer(player); - } - } - - /** - *

- * isSpell. - *

- * - * @return a boolean. - */ - public boolean isSpell() { return false; } - public boolean isAbility() { return true; } - - - /** - *

- * isMultiKicker. - *

- * - * @return a boolean. - */ - public boolean isMultiKicker() { - return this.multiKickerManaCost != null && !this.isAnnouncing("Multikicker"); - } - - /** - *

- * setIsMorphUp. - *

- * - * @param b - * a boolean. - */ - public final void setIsMorphUp(final boolean b) { - this.morphup = b; - } - - /** - *

- * isMorphUp. - *

- * - * @return a boolean. - */ - public boolean isMorphUp() { - return this.morphup; - } - - /** - *

- * setIsCycling. - *

- * - * @param b - * a boolean. - */ - public final void setIsCycling(final boolean b) { - this.cycling = b; - } - - /** - *

- * isCycling. - *

- * - * @return a boolean. - */ - public boolean isCycling() { - return this.cycling; - } - - /** - *

- * Setter for the field sourceCard. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - */ - public void setSourceCard(final Card c) { - this.sourceCard = c; - } - - /** - *

- * Getter for the field sourceCard. - *

- * - * @return a {@link forge.game.card.Card} object. - */ - public Card getSourceCard() { - return this.sourceCard; - } - - /** - *

- * Setter for the field originalHost. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - */ - public void setOriginalHost(final Card c) { - this.grantorCard = c; - } - - /** - *

- * Getter for the field originalHost. - *

- * - * @return a {@link forge.game.card.Card} object. - */ - public Card getOriginalHost() { - return this.grantorCard; - } - - public String getParamOrDefault(String key, String defaultValue) { - return params == null || !params.containsKey(key) ? defaultValue : params.get(key); - } - - public String getParam(String key) { - return params == null ? null : params.get(key); - } - public boolean hasParam(String key) { - return params == null ? false : params.containsKey(key); - } - - /** - * TODO: Write javadoc for this method. - * @param mapParams - */ - public void copyParamsToMap(Map mapParams) { - if (null != params) { - mapParams.putAll(params); - } - } - - // If this is not null, then ability was made in a factory - public ApiType getApi() { - return api; - } - - public final boolean isCurse() { - return this.hasParam("IsCurse"); - } - - // begin - Input methods - /** - *

- * Getter for the field payCosts. - *

- * - * @return a {@link forge.game.cost.Cost} object. - */ - public Cost getPayCosts() { - return this.payCosts; - } - - /** - *

- * Setter for the field payCosts. - *

- * - * @param abCost - * a {@link forge.game.cost.Cost} object. - */ - public void setPayCosts(final Cost abCost) { - this.payCosts = abCost; - } - - /** - *

- * Setter for the field restrictions. - *

- * - * @param restrict - * a {@link forge.game.spellability.SpellAbilityRestriction} - * object. - */ - public void setRestrictions(final SpellAbilityRestriction restrict) { - this.restrictions = restrict; - } - - /** - *

- * Getter for the field restrictions. - *

- * - * @return a {@link forge.game.spellability.SpellAbilityRestriction} object. - */ - public SpellAbilityRestriction getRestrictions() { - return this.restrictions; - } - - /** - *

- * Shortcut to see how many activations there were. - *

- * - * @return the activations this turn - */ - public int getActivationsThisTurn() { - return this.restrictions.getNumberTurnActivations(); - } - - /** - *

- * Setter for the field conditions. - *

- * - * @param condition - * a {@link forge.game.spellability.SpellAbilityCondition} - * object. - * @since 1.0.15 - */ - public final void setConditions(final SpellAbilityCondition condition) { - this.conditions = condition; - } - - /** - *

- * Getter for the field conditions. - *

- * - * @return a {@link forge.game.spellability.SpellAbilityCondition} object. - * @since 1.0.15 - */ - public SpellAbilityCondition getConditions() { - return this.conditions; - } - - /** - *

- * Getter for the field payingMana. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public ArrayList getPayingMana() { - return this.payingMana; - } - - /** - *

- * getPayingManaAbilities. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public List getPayingManaAbilities() { - return this.paidAbilities; - } - - // Combined PaidLists - /** - *

- * setPaidHash. - *

- * - * @param hash - * a {@link java.util.HashMap} object. - */ - public void setPaidHash(final HashMap> hash) { - this.paidLists = hash; - } - - /** - *

- * getPaidHash. - *

- * - * @return a {@link java.util.HashMap} object. - */ - public HashMap> getPaidHash() { - return this.paidLists; - } - - /** - *

- * getPaidList. - *

- * - * @param str - * a {@link java.lang.String} object. - * @return a {@link forge.CardList} object. - */ - public List getPaidList(final String str) { - return this.paidLists.get(str); - } - - /** - *

- * addCostToHashList. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param str - * a {@link java.lang.String} object. - */ - public void addCostToHashList(final Card c, final String str) { - if (!this.paidLists.containsKey(str)) { - this.paidLists.put(str, new ArrayList()); - } - - this.paidLists.get(str).add(c); - } - - /** - *

- * resetPaidHash. - *

- */ - public void resetPaidHash() { - this.paidLists = new HashMap>(); - } - - private EnumSet optionalCosts = EnumSet.noneOf(OptionalCost.class); - /** - * @return the optionalAdditionalCosts - */ - public Iterable getOptionalCosts() { - return optionalCosts; - } - - /** - * @param cost the optionalAdditionalCost to add - */ - public final void addOptionalCost(OptionalCost cost) { - // Optional costs are added to swallow copies of original SAs, - // Thus, to protect the original's set from changes, we make a copy right here. - this.optionalCosts = EnumSet.copyOf(optionalCosts); - this.optionalCosts.add(cost); - } - - public boolean isBuyBackAbility() { - return isOptionalCostPaid(OptionalCost.Buyback); - } - - public boolean isKicked() { - return isOptionalCostPaid(OptionalCost.Kicker1) || isOptionalCostPaid(OptionalCost.Kicker2); - } - - public boolean isOptionalCostPaid(OptionalCost cost) { - SpellAbility saRoot = this.getRootAbility(); - return saRoot.optionalCosts.contains(cost); - } - - /** - *

- * Getter for the field triggeringObjects. - *

- * - * @return a {@link java.util.HashMap} object. - * @since 1.0.15 - */ - public HashMap getTriggeringObjects() { - return this.triggeringObjects; - } - - /** - *

- * setAllTriggeringObjects. - *

- * - * @param triggeredObjects - * a {@link java.util.HashMap} object. - * @since 1.0.15 - */ - public void setAllTriggeringObjects(final HashMap triggeredObjects) { - this.triggeringObjects = triggeredObjects; - } - - /** - *

- * setTriggeringObject. - *

- * - * @param type - * a {@link java.lang.String} object. - * @param o - * a {@link java.lang.Object} object. - * @since 1.0.15 - */ - public void setTriggeringObject(final String type, final Object o) { - this.triggeringObjects.put(type, o); - } - - /** - *

- * getTriggeringObject. - *

- * - * @param type - * a {@link java.lang.String} object. - * @return a {@link java.lang.Object} object. - * @since 1.0.15 - */ - public Object getTriggeringObject(final String type) { - return this.triggeringObjects.get(type); - } - - /** - *

- * hasTriggeringObject. - *

- * - * @param type - * a {@link java.lang.String} object. - * @return a boolean. - * @since 1.0.15 - */ - public boolean hasTriggeringObject(final String type) { - return this.triggeringObjects.containsKey(type); - } - - /** - *

- * resetTriggeringObjects. - *

- * - * @since 1.0.15 - */ - public void resetTriggeringObjects() { - this.triggeringObjects = new HashMap(); - } - - /** - * Gets the replacing objects. - * - * @return the replacing objects - */ - public HashMap getReplacingObjects() { - return this.replacingObjects; - } - - /** - * Sets the replacing object. - * - * @param type - * the type - * @param o - * the o - */ - public void setReplacingObject(final String type, final Object o) { - this.replacingObjects.put(type, o); - } - - /** - * Gets the replacing object. - * - * @param type - * the type - * @return the replacing object - */ - public Object getReplacingObject(final String type) { - final Object res = this.replacingObjects.get(type); - return res; - } - - - /** - *

- * resetOnceResolved. - *

- */ - public void resetOnceResolved() { - this.resetPaidHash(); - this.resetTargets(); - this.resetTriggeringObjects(); - - // Clear SVars - for (final String store : Card.getStorableSVars()) { - final String value = this.sourceCard.getSVar(store); - if (value.length() > 0) { - this.sourceCard.setSVar(store, ""); - } - } - } - - /** - *

- * Setter for the field stackDescription. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public void setStackDescription(final String s) { - this.stackDescription = s; - if ((this.description == "") && this.sourceCard.getText().equals("")) { - this.description = s; - } - } - - /** - *

- * Getter for the field stackDescription. - *

- * - * @return a {@link java.lang.String} object. - */ - public String getStackDescription() { - if (this.stackDescription.equals(this.getSourceCard().getText().trim())) { - return this.getSourceCard().getName() + " - " + this.getSourceCard().getText(); - } - - return this.stackDescription.replaceAll("CARDNAME", this.getSourceCard().getName()); - } - - // setDescription() includes mana cost and everything like - // "G, tap: put target creature from your hand onto the battlefield" - /** - *

- * Setter for the field description. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public void setDescription(final String s) { - this.description = s; - } - - /** - *

- * Getter for the field description. - *

- * - * @return a {@link java.lang.String} object. - */ - public String getDescription() { - return this.description; - } - - /** {@inheritDoc} */ - @Override - public final String toString() { - if (this.isSuppressed()) { - return ""; - } - - return this.toUnsuppressedString(); - } - - /** - * To unsuppressed string. - * - * @return the string - */ - public String toUnsuppressedString() { - - final StringBuilder sb = new StringBuilder(); - SpellAbility node = this; - - while (node != null) { - if (node != this) { - sb.append(" "); - } - - sb.append(node.getDescription().replace("CARDNAME", node.getSourceCard().getName())); - node = node.getSubAbility(); - } - return sb.toString(); - } - - public void appendSubAbility(final AbilitySub toAdd) { - SpellAbility tailend = this; - while (tailend.getSubAbility() != null) { - tailend = tailend.getSubAbility(); - } - tailend.setSubAbility(toAdd); - } - - /** - *

- * Setter for the field subAbility. - *

- * - * @param subAbility - * a {@link forge.game.spellability.AbilitySub} object. - */ - public void setSubAbility(final AbilitySub subAbility) { - this.subAbility = subAbility; - if (subAbility != null) { - subAbility.setParent(this); - } - } - - /** - *

- * Getter for the field subAbility. - *

- * - * @return a {@link forge.game.spellability.AbilitySub} object. - */ - public AbilitySub getSubAbility() { - return this.subAbility; - } - - /** - *

- * isBasicAbility. - *

- * - * @return a boolean. - */ - public boolean isBasicSpell() { - return this.basicSpell && !this.isFlashBackAbility() && !this.isBuyBackAbility(); - } - - /** - *

- * Setter for the field setBasicSpell. - *

- * - * @param basicSpell - * a boolean. - */ - public void setBasicSpell(final boolean basicSpell) { - this.basicSpell = basicSpell; - } - - /** - *

- * Setter for the field flashBackAbility. - *

- * - * @param flashBackAbility - * a boolean. - */ - public void setFlashBackAbility(final boolean flashBackAbility) { - this.flashBackAbility = flashBackAbility; - } - - /** - *

- * isFlashBackAbility. - *

- * - * @return a boolean. - */ - public boolean isFlashBackAbility() { - return this.flashBackAbility; - } - - /** - *

- * copy. - *

- * - * @return a {@link forge.game.spellability.SpellAbility} object. - */ - public SpellAbility copy() { - SpellAbility clone = null; - try { - clone = (SpellAbility) this.clone(); - } catch (final CloneNotSupportedException e) { - System.err.println(e); - } - return clone; - } - - public SpellAbility copyWithNoManaCost() { - final SpellAbility newSA = this.copy(); - newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana()); - newSA.setDescription(newSA.getDescription() + " (without paying its mana cost)"); - return newSA; - } - - public SpellAbility copyWithDefinedCost(Cost abCost) { - final SpellAbility newSA = this.copy(); - newSA.setPayCosts(abCost); - return newSA; - } - /** - *

- * Setter for the field trigger. - *

- * - * @param trigger - * a boolean. - */ - public void setTrigger(final boolean trigger) { - this.trigger = trigger; - } - - /** - *

- * isTrigger. - *

- * - * @return a boolean. - */ - public boolean isTrigger() { - return this.trigger; - } - - /** - * Sets the optional trigger. - * - * @param optrigger - * the new optional trigger - */ - public void setOptionalTrigger(final boolean optrigger) { - this.optionalTrigger = optrigger; - } - - /** - * Checks if is optional trigger. - * - * @return true, if is optional trigger - */ - public boolean isOptionalTrigger() { - return this.optionalTrigger; - } - - /** - *

- * setSourceTrigger. - *

- * - * @param id - * a int. - */ - public void setSourceTrigger(final int id) { - this.sourceTrigger = id; - } - - /** - *

- * getSourceTrigger. - *

- * - * @return a int. - */ - public int getSourceTrigger() { - return this.sourceTrigger; - } - - /** - *

- * isMandatory. - *

- * - * @return a boolean. - */ - public boolean isMandatory() { - return false; - } - - /** - *

- * canTarget. - *

- * - * @param entity - * a GameEntity - * @return a boolean. - */ - public final boolean canTarget(final GameObject entity) { - final TargetRestrictions tr = this.getTargetRestrictions(); - - // Restriction related to this ability - if (tr != null) { - if (tr.isUniqueTargets() && this.getUniqueTargets().contains(entity)) - return false; - - // If the cards must have a specific controller - if (hasParam("TargetsWithDefinedController") && entity instanceof Card) { - final Card c = (Card) entity; - List pl = AbilityUtils.getDefinedPlayers(getSourceCard(), getParam("TargetsWithDefinedController"), this); - if (pl == null || !pl.contains(c.getController()) ) { - return false; - } - } - - String[] validTgt = tr.getValidTgts(); - if (entity instanceof GameEntity && !((GameEntity) entity).isValid(validTgt, this.getActivatingPlayer(), this.getSourceCard())) - return false; - } - - // Restrictions coming from target - return entity.canBeTargetedBy(this); - } - - // is this a wrapping ability (used by trigger abilities) - /** - *

- * isWrapper. - *

- * - * @return a boolean. - * @since 1.0.15 - */ - public boolean isWrapper() { - return false; - } - - /** - * Sets the temporarily suppressed. - * - * @param supp - * the new temporarily suppressed - */ - public final void setTemporarilySuppressed(final boolean supp) { - this.temporarilySuppressed = supp; - } - - /** - * Checks if is suppressed. - * - * @return true, if is suppressed - */ - public final boolean isSuppressed() { - return (this.temporarilySuppressed); - } - - /** - * Gets the checks if is delve. - * - * @return the isDelve - */ - public final boolean isDelve() { - return this.delve; - } - - /** - * Sets the checks if is delve. - * - * @param isDelve0 - * the isDelve to set - */ - public final void setDelve(final boolean isDelve0) { - this.delve = isDelve0; - } - - /** - * Adds the tapped for convoke. - * - * @param c - * the c - */ - public void addTappedForConvoke(final Card c) { - if (this.tappedForConvoke == null) { - this.tappedForConvoke = new ArrayList(); - } - - this.tappedForConvoke.add(c); - } - - /** - * Gets the tapped for convoke. - * - * @return the tapped for convoke - */ - public List getTappedForConvoke() { - return this.tappedForConvoke; - } - - /** - * Clear tapped for convoke. - */ - public void clearTappedForConvoke() { - if (this.tappedForConvoke != null) { - this.tappedForConvoke.clear(); - } - } - - /** - * Returns whether the SA is a patron offering. - */ - public boolean isOffering() { - return this.offering; - } - - /** - * Sets the SA as a patron offering. - * - * @param c card sacrificed for a patron offering - */ - public void setIsOffering(final boolean bOffering) { - this.offering = bOffering; - } - - /** - * Sets the card sacrificed for a patron offering. - * - * @param c card sacrificed for a patron offering - */ - public void setSacrificedAsOffering(final Card c) { - this.sacrificedAsOffering = c; - } - - /** - * Gets the card sacrificed for a patron offering. - * - * @return the card sacrificed for a patron offering - */ - public Card getSacrificedAsOffering() { - return this.sacrificedAsOffering; - } - - /** - * Clear the card sacrificed for a patron offering. - */ - public void resetSacrificedAsOffering() { - this.sacrificedAsOffering = null; - } - - /** - * @return the splicedCards - */ - public List getSplicedCards() { - return splicedCards; - } - - /** - * @param splicedCard the splicedCards to set - */ - public void setSplicedCards(List splicedCards) { - this.splicedCards = splicedCards; - } - - /** - * @param splicedCard the splicedCard to add - */ - public void addSplicedCards(Card splicedCard) { - if (this.splicedCards == null) { - this.splicedCards = new ArrayList(); - } - this.splicedCards.add(splicedCard); - } - - /** - *

- * knownDetermineDefined. - *

- * - * @param defined - * a {@link java.lang.String} object. - * @return a {@link forge.CardList} object. - */ - public List knownDetermineDefined(final String defined) { - final List ret = new ArrayList(); - final List list = AbilityUtils.getDefinedCards(getSourceCard(), defined, this); - final Game game = getActivatingPlayer().getGame(); - - for (final Card c : list) { - final Card actualCard = game.getCardState(c); - ret.add(actualCard); - } - return ret; - } - - /** - *

- * findRootAbility. - *

- * - * @return a {@link forge.game.spellability.SpellAbility} object. - */ - public SpellAbility getRootAbility() { - SpellAbility parent = this; - while (null != parent.getParent()) { - parent = parent.getParent(); - } - - return parent; - } - - public SpellAbility getParent() { - return null; - } - - /** - * TODO: Write javadoc for this method. - * @return - */ - public boolean isUndoable() { - return this.undoable && this.payCosts.isUndoable() && this.getSourceCard().isInPlay(); - } - - /** - * TODO: Write javadoc for this method. - */ - public boolean undo() { - if (isUndoable() && this.getActivatingPlayer().getManaPool().accountFor(this.getManaPart())) { - this.payCosts.refundPaidCost(sourceCard); - } - return false; - } - - /** - * TODO: Write javadoc for this method. - * @param b - */ - public void setUndoable(boolean b) { - this.undoable = b; - } - - /** - * @return the isCopied - */ - public boolean isCopied() { - return isCopied; - } - - /** - * @param isCopied0 the isCopied to set - */ - public void setCopied(boolean isCopied0) { - this.isCopied = isCopied0; - } - - /** - * Returns whether variable was present in the announce list. - */ - public boolean isAnnouncing(String variable) { - String announce = getParam("Announce"); - if (StringUtils.isBlank(announce)) return false; - String[] announcedOnes = TextUtil.split(announce, ','); - for(String a : announcedOnes) { - if( a.trim().equalsIgnoreCase(variable)) - return true; - } - return false; - } - - public boolean isXCost() { - CostPartMana cm = payCosts != null ? getPayCosts().getCostMana() : null; - return cm != null && cm.getAmountOfX() > 0; - } - - public boolean isBasicLandAbility() { - return basicLandAbility; - } - - public void setBasicLandAbility(boolean basicLandAbility) { - this.basicLandAbility = basicLandAbility; // TODO: Add 0 to parameter's name. - } - - public boolean isTemporary() { - return temporary; - } - - public void setTemporary(boolean temporary) { - this.temporary = temporary; // TODO: Add 0 to parameter's name. - } - - @Override - public boolean canBeTargetedBy(SpellAbility sa) { - return sa.canTargetSpellAbility(this); - } - - /** The chosen target. */ - private TargetRestrictions targetRestricions = null; - private TargetChoices targetChosen = new TargetChoices(); - - public boolean usesTargeting() { - return targetRestricions != null; - } - - public TargetRestrictions getTargetRestrictions() { - return targetRestricions; - } - - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // - // THE CODE BELOW IS RELATED TO TARGETING. It might be extracted to other class from here - // - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - public void setTargetRestrictions(final TargetRestrictions tgt) { - targetRestricions = tgt; - } - - /** - * Gets the chosen target. - * - * @return the chosenTarget - */ - public TargetChoices getTargets() { - return this.targetChosen; - } - - public void setTargets(TargetChoices targets) { - this.targetChosen = targets; - } - - public void resetTargets() { - targetChosen = new TargetChoices(); - } - - /** - * Reset the first target. - * - */ - public void resetFirstTarget(GameObject c) { - SpellAbility sa = this; - while (sa != null) { - if (sa.targetRestricions != null) { - sa.targetChosen = new TargetChoices(); - sa.targetChosen.add(c); - break; - } - sa = sa.subAbility; - } - } - - /** - *

- * getAllTargetChoices. - *

- * - * @return a {@link java.util.ArrayList} object. - * @since 1.0.15 - */ - public final ArrayList getAllTargetChoices() { - final ArrayList res = new ArrayList(); - - SpellAbility sa = this.getRootAbility(); - if (sa.getTargetRestrictions() != null) { - res.add(sa.getTargets()); - } - while (sa.getSubAbility() != null) { - sa = sa.getSubAbility(); - - if (sa.getTargetRestrictions() != null) { - res.add(sa.getTargets()); - } - } - - return res; - } - - public Card getTargetCard() { - return targetChosen.getFirstTargetedCard(); - } - - /** - *

- * Setter for the field targetCard. - *

- * - * @param card - * a {@link forge.game.card.Card} object. - */ - public void setTargetCard(final Card card) { - if (card == null) { - System.out.println(this.getSourceCard() - + " - SpellAbility.setTargetCard() called with null for target card."); - return; - } - - resetTargets(); - targetChosen.add(card); - - final String desc; - - if (!card.isFaceDown()) { - desc = this.getSourceCard().getName() + " - targeting " + card; - } else { - desc = this.getSourceCard().getName() + " - targeting Morph(" + card.getUniqueNumber() + ")"; - } - this.setStackDescription(desc); - } - - /** - *

- * findTargetCards. - *

- * - * @return a {@link forge.game.spellability.SpellAbility} object. - */ - public List findTargetedCards() { - // First search for targeted cards associated with current ability - if (targetChosen.isTargetingAnyCard()) { - return Lists.newArrayList(targetChosen.getTargetCards()); - } - - // Next search for source cards of targeted SAs associated with current ability - if (targetChosen.isTargetingAnySpell()) { - List res = Lists.newArrayList(); - for (final SpellAbility ability : targetChosen.getTargetSpells()) { - res.add(ability.getSourceCard()); - } - return res; - } - - // Lastly Search parent SAs that targets a card - SpellAbility parent = this.getParentTargetingCard(); - if (null != parent) { - return parent.findTargetedCards(); - } - - // Lastly Search parent SAs that targets an SA - parent = this.getParentTargetingSA(); - if (null != parent) { - return parent.findTargetedCards(); - } - - return ImmutableList.of(); - } - - - public SpellAbility getSATargetingCard() { - return targetChosen.isTargetingAnyCard() ? this : getParentTargetingCard(); - } - - public SpellAbility getParentTargetingCard() { - SpellAbility parent = this.getParent(); - while (parent != null) { - if (parent.targetChosen.isTargetingAnyCard()) - return parent; - parent = parent.getParent(); - } - return null; - } - - public SpellAbility getSATargetingSA() { - return targetChosen.isTargetingAnySpell() ? this : getParentTargetingSA(); - } - - public SpellAbility getParentTargetingSA() { - SpellAbility parent = this.getParent(); - while (parent != null) { - if (parent.targetChosen.isTargetingAnySpell()) - return parent; - parent = parent.getParent(); - } - return null; - } - - public SpellAbility getSATargetingPlayer() { - return targetChosen.isTargetingAnyPlayer() ? this : getParentTargetingPlayer(); - } - - /** - *

- * findParentsTargetedPlayer. - *

- * - * @return a {@link forge.game.spellability.SpellAbility} object. - */ - public SpellAbility getParentTargetingPlayer() { - SpellAbility parent = this.getParent(); - while (parent != null) { - if (parent.getTargets().isTargetingAnyPlayer()) - return parent; - parent = parent.getParent(); - } - return null; - } - - /** - * Gets the unique targets. - * - * @param ability - * the ability - * @return the unique targets - */ - public final List getUniqueTargets() { - final List targets = new ArrayList(); - SpellAbility child = this.getParent(); - while (child != null) { - if (child.getTargetRestrictions() != null) { - Iterables.addAll(targets, child.getTargets().getTargets()); - } - child = child.getParent(); - } - return targets; - } - - public boolean canTargetSpellAbility(final SpellAbility topSA) { - final TargetRestrictions tgt = this.getTargetRestrictions(); - - if (this.hasParam("TargetType") && !topSA.isValid(this.getParam("TargetType").split(","), this.getActivatingPlayer(), this.getSourceCard())) { - return false; - } - - final String splitTargetRestrictions = tgt.getSAValidTargeting(); - if (splitTargetRestrictions != null) { - // TODO What about spells with SubAbilities with Targets? - - final TargetChoices matchTgt = topSA.getTargets(); - - if (matchTgt == null) { - return false; - } - - boolean result = false; - - for (final GameObject o : matchTgt.getTargets()) { - if (o.isValid(splitTargetRestrictions.split(","), this.getActivatingPlayer(), this.getSourceCard())) { - result = true; - break; - } - } - - if (!result) { - return false; - } - } - - if (tgt.isSingleTarget()) { - int totalTargets = 0; - for(TargetChoices tc : topSA.getAllTargetChoices()) { - totalTargets += tc.getNumTargeted(); - if (totalTargets > 1) { - // As soon as we get more than one, bail out - return false; - } - } - if (totalTargets != 1) { - // Make sure that there actually is one target - return false; - } - } - - return topSA.getSourceCard().isValid(tgt.getValidTgts(), this.getActivatingPlayer(), this.getSourceCard()); - } - - // Takes one argument like Permanent.Blue+withFlying - /** - *

- * isValid. - *

- * - * @param restriction - * a {@link java.lang.String} object. - * @param sourceController - * a {@link forge.game.player.Player} object. - * @param source - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - @Override - public final boolean isValid(final String restriction, final Player sourceController, final Card source) { - - // Inclusive restrictions are Card types - final String[] incR = restriction.split("\\.", 2); - - if (incR[0].equals("Spell")) { - if (!this.isSpell()) - return false; - } else if (incR[0].equals("Triggered")) { - if (!this.isTrigger()) - return false; - } else if (incR[0].equals("Activated")) { - if (!(this instanceof AbilityActivated)) - return false; - } else { //not a spell/ability type - return false; - } - - if (incR.length > 1) { - final String excR = incR[1]; - final String[] exR = excR.split("\\+"); // Exclusive Restrictions are ... - for (int j = 0; j < exR.length; j++) { - if (!this.hasProperty(exR[j], sourceController, source)) { - return false; - } - } - } - return true; - } // isValid(String Restriction) - - // Takes arguments like Blue or withFlying - /** - *

- * hasProperty. - *

- * - * @param property - * a {@link java.lang.String} object. - * @param sourceController - * a {@link forge.game.player.Player} object. - * @param source - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - @Override - public boolean hasProperty(final String property, final Player sourceController, final Card source) { - return true; - } - - // Methods enabling multiple instances of conspire - public void addConspireInstance() { - this.conspireInstances++; - } - - public void subtractConspireInstance() { - this.conspireInstances--; - } - - public int getConspireInstances() { - return this.conspireInstances; - } // End of Conspire methods - - // Madness - public boolean isMadness() { - return madness; - } - - public void setMadness(boolean madness) { - this.madness = madness; - } - - // Return whether this spell tracks what color mana is spent to cast it for the sake of the effect - public boolean tracksManaSpent() { - if (this.sourceCard == null) { return false; } - if (!this.isSpell()) { return false; } - - if (this.sourceCard.hasKeyword("Sunburst")) { - return true; - } - if (this.sourceCard.getRules().getOracleText().contains("was spent to cast")) { - return true; - } - return false; - } - +/* + * 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 . + */ +package forge.game.spellability; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import forge.card.mana.ManaCost; +import forge.game.Game; +import forge.game.GameEntity; +import forge.game.GameObject; +import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; +import forge.game.card.Card; +import forge.game.cost.Cost; +import forge.game.cost.CostPartMana; +import forge.game.mana.Mana; +import forge.game.player.Player; +import forge.util.TextUtil; + +//only SpellAbility can go on the stack +//override any methods as needed +/** + *

+ * Abstract SpellAbility class. + *

+ * + * @author Forge + * @version $Id$ + */ +public abstract class SpellAbility extends GameObject implements ISpellAbility { + + public static class EmptySa extends SpellAbility { + public EmptySa(Card sourceCard) { super(sourceCard, Cost.Zero); setActivatingPlayer(sourceCard.getController());} + public EmptySa(ApiType api, Card sourceCard) { super(sourceCard, Cost.Zero); setActivatingPlayer(sourceCard.getController()); this.api = api;} + public EmptySa(Card sourceCard, Player activator) { super(sourceCard, Cost.Zero); setActivatingPlayer(activator);} + public EmptySa(ApiType api, Card sourceCard, Player activator) { super(sourceCard, Cost.Zero); setActivatingPlayer(activator); this.api = api;} + @Override public void resolve() {} + @Override public boolean canPlay() { return false; } + } + + // choices for constructor isPermanent argument + private String description = ""; + private String stackDescription = ""; + private ManaCost multiKickerManaCost = null; + private Player activatingPlayer = null; + + private boolean temporary; // that is given by some static ability + private boolean basicLandAbility; // granted by basic land type + + private Card sourceCard; + private Card grantorCard = null; // card which grants the ability (equipment or owner of static ability that gave this one) + + private List splicedCards = null; +// private List targetList; + // targetList doesn't appear to be used anymore + + private boolean basicSpell = true; + private boolean trigger = false; + private boolean optionalTrigger = false; + private int sourceTrigger = -1; + private boolean temporarilySuppressed = false; + + private boolean flashBackAbility = false; + private boolean cycling = false; + private boolean delve = false; + private boolean offering = false; + private boolean morphup = false; + + /** The pay costs. */ + private Cost payCosts = null; + private SpellAbilityRestriction restrictions = new SpellAbilityRestriction(); + private SpellAbilityCondition conditions = new SpellAbilityCondition(); + private AbilitySub subAbility = null; + + protected Map params = null; + protected ApiType api = null; + + private final ArrayList payingMana = new ArrayList(); + private final List paidAbilities = new ArrayList(); + + private HashMap> paidLists = new HashMap>(); + + private HashMap triggeringObjects = new HashMap(); + + private HashMap replacingObjects = new HashMap(); + + private List tappedForConvoke = new ArrayList(); + private Card sacrificedAsOffering = null; + private int conspireInstances = 0; + private boolean madness = false; + + private HashMap sVars = new HashMap(); + + private AbilityManaPart manaPart = null; + + private boolean undoable; + + private boolean isCopied = false; + + public final AbilityManaPart getManaPart() { + return manaPart; + } + + public final AbilityManaPart getManaPartRecursive() { + SpellAbility tail = this; + while (tail != null) { + if(tail.manaPart != null) + return tail.manaPart; + tail = tail.getSubAbility(); + } + return null; + } + + public final boolean isManaAbility() { + // Check whether spell or ability first + if (this.isSpell()) + return false; + // without a target + if (this.usesTargeting()) return false; + if (getRestrictions() != null && getRestrictions().getPlaneswalker()) + return false; //Loyalty ability, not a mana ability. + + return getManaPartRecursive() != null; + } + + protected final void setManaPart(AbilityManaPart manaPart) { + this.manaPart = manaPart; + } + + public final String getSVar(final String name) { + return sVars.get(name) != null ? sVars.get(name) : ""; + } + + public final void setSVar(final String name, final String value) { + sVars.put(name, value); + } + + public Set getSVars() { + return sVars.keySet(); + } + + /** + *

+ * Constructor for SpellAbility. + *

+ * + * @param spellOrAbility + * a int. + * @param iSourceCard + * a {@link forge.game.card.Card} object. + */ + public SpellAbility(final Card iSourceCard, Cost toPay) { + this.sourceCard = iSourceCard; + this.payCosts = toPay; + } + + // Spell, and Ability, and other Ability objects override this method + /** + *

+ * canPlay. + *

+ * + * @return a boolean. + */ + public abstract boolean canPlay(); + + /** + *

+ * isPossible. + *

+ * + * @return a boolean. + */ + public boolean isPossible() { + return canPlay(); //by default, ability is only possible if it can be played + } + + /** + *

+ * promptIfOnlyPossibleAbility. + *

+ * + * @return a boolean. + */ + public boolean promptIfOnlyPossibleAbility() { + return false; //by default, don't prompt user if ability is only possible ability + } + + // all Spell's and Abilities must override this method + /** + *

+ * resolve. + *

+ */ + public abstract void resolve(); + + /** + *

+ * canPlayAI. + *

+ * + * @return a boolean. + */ + public /*final*/ boolean canPlayAI(Player aiPlayer) { + return true; + } + + // This should be overridden by ALL AFs + /** + *

+ * doTrigger. + *

+ * + * @param mandatory + * a boolean. + * @param ai TODO + * @return a boolean. + */ + public boolean doTrigger(final boolean mandatory, Player ai) { + return false; + } + + /** + *

+ * Getter for the field multiKickerManaCost. + *

+ * + * @return a {@link java.lang.String} object. + */ + public ManaCost getMultiKickerManaCost() { + return this.multiKickerManaCost; + } + + /** + *

+ * Setter for the field multiKickerManaCost. + *

+ * + * @param cost + * a {@link java.lang.String} object. + */ + public void setMultiKickerManaCost(final ManaCost cost) { + this.multiKickerManaCost = cost; + } + + + /** + *

+ * Getter for the field activatingPlayer. + *

+ * + * @return a {@link forge.game.player.Player} object. + */ + public Player getActivatingPlayer() { + return this.activatingPlayer; + } + + /** + *

+ * Setter for the field activatingPlayer. + *

+ * + * @param player + * a {@link forge.game.player.Player} object. + */ + public void setActivatingPlayer(final Player player) { + // trickle down activating player + this.activatingPlayer = player; + if (this.subAbility != null) { + this.subAbility.setActivatingPlayer(player); + } + } + + /** + *

+ * isSpell. + *

+ * + * @return a boolean. + */ + public boolean isSpell() { return false; } + public boolean isAbility() { return true; } + + + /** + *

+ * isMultiKicker. + *

+ * + * @return a boolean. + */ + public boolean isMultiKicker() { + return this.multiKickerManaCost != null && !this.isAnnouncing("Multikicker"); + } + + /** + *

+ * setIsMorphUp. + *

+ * + * @param b + * a boolean. + */ + public final void setIsMorphUp(final boolean b) { + this.morphup = b; + } + + /** + *

+ * isMorphUp. + *

+ * + * @return a boolean. + */ + public boolean isMorphUp() { + return this.morphup; + } + + /** + *

+ * setIsCycling. + *

+ * + * @param b + * a boolean. + */ + public final void setIsCycling(final boolean b) { + this.cycling = b; + } + + /** + *

+ * isCycling. + *

+ * + * @return a boolean. + */ + public boolean isCycling() { + return this.cycling; + } + + /** + *

+ * Setter for the field sourceCard. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + */ + public void setSourceCard(final Card c) { + this.sourceCard = c; + } + + /** + *

+ * Getter for the field sourceCard. + *

+ * + * @return a {@link forge.game.card.Card} object. + */ + public Card getSourceCard() { + return this.sourceCard; + } + + /** + *

+ * Setter for the field originalHost. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + */ + public void setOriginalHost(final Card c) { + this.grantorCard = c; + } + + /** + *

+ * Getter for the field originalHost. + *

+ * + * @return a {@link forge.game.card.Card} object. + */ + public Card getOriginalHost() { + return this.grantorCard; + } + + public String getParamOrDefault(String key, String defaultValue) { + return params == null || !params.containsKey(key) ? defaultValue : params.get(key); + } + + public String getParam(String key) { + return params == null ? null : params.get(key); + } + public boolean hasParam(String key) { + return params == null ? false : params.containsKey(key); + } + + /** + * TODO: Write javadoc for this method. + * @param mapParams + */ + public void copyParamsToMap(Map mapParams) { + if (null != params) { + mapParams.putAll(params); + } + } + + // If this is not null, then ability was made in a factory + public ApiType getApi() { + return api; + } + + public final boolean isCurse() { + return this.hasParam("IsCurse"); + } + + // begin - Input methods + /** + *

+ * Getter for the field payCosts. + *

+ * + * @return a {@link forge.game.cost.Cost} object. + */ + public Cost getPayCosts() { + return this.payCosts; + } + + /** + *

+ * Setter for the field payCosts. + *

+ * + * @param abCost + * a {@link forge.game.cost.Cost} object. + */ + public void setPayCosts(final Cost abCost) { + this.payCosts = abCost; + } + + /** + *

+ * Setter for the field restrictions. + *

+ * + * @param restrict + * a {@link forge.game.spellability.SpellAbilityRestriction} + * object. + */ + public void setRestrictions(final SpellAbilityRestriction restrict) { + this.restrictions = restrict; + } + + /** + *

+ * Getter for the field restrictions. + *

+ * + * @return a {@link forge.game.spellability.SpellAbilityRestriction} object. + */ + public SpellAbilityRestriction getRestrictions() { + return this.restrictions; + } + + /** + *

+ * Shortcut to see how many activations there were. + *

+ * + * @return the activations this turn + */ + public int getActivationsThisTurn() { + return this.restrictions.getNumberTurnActivations(); + } + + /** + *

+ * Setter for the field conditions. + *

+ * + * @param condition + * a {@link forge.game.spellability.SpellAbilityCondition} + * object. + * @since 1.0.15 + */ + public final void setConditions(final SpellAbilityCondition condition) { + this.conditions = condition; + } + + /** + *

+ * Getter for the field conditions. + *

+ * + * @return a {@link forge.game.spellability.SpellAbilityCondition} object. + * @since 1.0.15 + */ + public SpellAbilityCondition getConditions() { + return this.conditions; + } + + /** + *

+ * Getter for the field payingMana. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public ArrayList getPayingMana() { + return this.payingMana; + } + + /** + *

+ * getPayingManaAbilities. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public List getPayingManaAbilities() { + return this.paidAbilities; + } + + // Combined PaidLists + /** + *

+ * setPaidHash. + *

+ * + * @param hash + * a {@link java.util.HashMap} object. + */ + public void setPaidHash(final HashMap> hash) { + this.paidLists = hash; + } + + /** + *

+ * getPaidHash. + *

+ * + * @return a {@link java.util.HashMap} object. + */ + public HashMap> getPaidHash() { + return this.paidLists; + } + + /** + *

+ * getPaidList. + *

+ * + * @param str + * a {@link java.lang.String} object. + * @return a {@link forge.CardList} object. + */ + public List getPaidList(final String str) { + return this.paidLists.get(str); + } + + /** + *

+ * addCostToHashList. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param str + * a {@link java.lang.String} object. + */ + public void addCostToHashList(final Card c, final String str) { + if (!this.paidLists.containsKey(str)) { + this.paidLists.put(str, new ArrayList()); + } + + this.paidLists.get(str).add(c); + } + + /** + *

+ * resetPaidHash. + *

+ */ + public void resetPaidHash() { + this.paidLists = new HashMap>(); + } + + private EnumSet optionalCosts = EnumSet.noneOf(OptionalCost.class); + /** + * @return the optionalAdditionalCosts + */ + public Iterable getOptionalCosts() { + return optionalCosts; + } + + /** + * @param cost the optionalAdditionalCost to add + */ + public final void addOptionalCost(OptionalCost cost) { + // Optional costs are added to swallow copies of original SAs, + // Thus, to protect the original's set from changes, we make a copy right here. + this.optionalCosts = EnumSet.copyOf(optionalCosts); + this.optionalCosts.add(cost); + } + + public boolean isBuyBackAbility() { + return isOptionalCostPaid(OptionalCost.Buyback); + } + + public boolean isKicked() { + return isOptionalCostPaid(OptionalCost.Kicker1) || isOptionalCostPaid(OptionalCost.Kicker2); + } + + public boolean isOptionalCostPaid(OptionalCost cost) { + SpellAbility saRoot = this.getRootAbility(); + return saRoot.optionalCosts.contains(cost); + } + + /** + *

+ * Getter for the field triggeringObjects. + *

+ * + * @return a {@link java.util.HashMap} object. + * @since 1.0.15 + */ + public HashMap getTriggeringObjects() { + return this.triggeringObjects; + } + + /** + *

+ * setAllTriggeringObjects. + *

+ * + * @param triggeredObjects + * a {@link java.util.HashMap} object. + * @since 1.0.15 + */ + public void setAllTriggeringObjects(final HashMap triggeredObjects) { + this.triggeringObjects = triggeredObjects; + } + + /** + *

+ * setTriggeringObject. + *

+ * + * @param type + * a {@link java.lang.String} object. + * @param o + * a {@link java.lang.Object} object. + * @since 1.0.15 + */ + public void setTriggeringObject(final String type, final Object o) { + this.triggeringObjects.put(type, o); + } + + /** + *

+ * getTriggeringObject. + *

+ * + * @param type + * a {@link java.lang.String} object. + * @return a {@link java.lang.Object} object. + * @since 1.0.15 + */ + public Object getTriggeringObject(final String type) { + return this.triggeringObjects.get(type); + } + + /** + *

+ * hasTriggeringObject. + *

+ * + * @param type + * a {@link java.lang.String} object. + * @return a boolean. + * @since 1.0.15 + */ + public boolean hasTriggeringObject(final String type) { + return this.triggeringObjects.containsKey(type); + } + + /** + *

+ * resetTriggeringObjects. + *

+ * + * @since 1.0.15 + */ + public void resetTriggeringObjects() { + this.triggeringObjects = new HashMap(); + } + + /** + * Gets the replacing objects. + * + * @return the replacing objects + */ + public HashMap getReplacingObjects() { + return this.replacingObjects; + } + + /** + * Sets the replacing object. + * + * @param type + * the type + * @param o + * the o + */ + public void setReplacingObject(final String type, final Object o) { + this.replacingObjects.put(type, o); + } + + /** + * Gets the replacing object. + * + * @param type + * the type + * @return the replacing object + */ + public Object getReplacingObject(final String type) { + final Object res = this.replacingObjects.get(type); + return res; + } + + + /** + *

+ * resetOnceResolved. + *

+ */ + public void resetOnceResolved() { + this.resetPaidHash(); + this.resetTargets(); + this.resetTriggeringObjects(); + + // Clear SVars + for (final String store : Card.getStorableSVars()) { + final String value = this.sourceCard.getSVar(store); + if (value.length() > 0) { + this.sourceCard.setSVar(store, ""); + } + } + } + + /** + *

+ * Setter for the field stackDescription. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public void setStackDescription(final String s) { + this.stackDescription = s; + if ((this.description == "") && this.sourceCard.getText().equals("")) { + this.description = s; + } + } + + /** + *

+ * Getter for the field stackDescription. + *

+ * + * @return a {@link java.lang.String} object. + */ + public String getStackDescription() { + if (this.stackDescription.equals(this.getSourceCard().getText().trim())) { + return this.getSourceCard().getName() + " - " + this.getSourceCard().getText(); + } + + return this.stackDescription.replaceAll("CARDNAME", this.getSourceCard().getName()); + } + + // setDescription() includes mana cost and everything like + // "G, tap: put target creature from your hand onto the battlefield" + /** + *

+ * Setter for the field description. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public void setDescription(final String s) { + this.description = s; + } + + /** + *

+ * Getter for the field description. + *

+ * + * @return a {@link java.lang.String} object. + */ + public String getDescription() { + return this.description; + } + + /** {@inheritDoc} */ + @Override + public final String toString() { + if (this.isSuppressed()) { + return ""; + } + + return this.toUnsuppressedString(); + } + + /** + * To unsuppressed string. + * + * @return the string + */ + public String toUnsuppressedString() { + + final StringBuilder sb = new StringBuilder(); + SpellAbility node = this; + + while (node != null) { + if (node != this) { + sb.append(" "); + } + + sb.append(node.getDescription().replace("CARDNAME", node.getSourceCard().getName())); + node = node.getSubAbility(); + } + return sb.toString(); + } + + public void appendSubAbility(final AbilitySub toAdd) { + SpellAbility tailend = this; + while (tailend.getSubAbility() != null) { + tailend = tailend.getSubAbility(); + } + tailend.setSubAbility(toAdd); + } + + /** + *

+ * Setter for the field subAbility. + *

+ * + * @param subAbility + * a {@link forge.game.spellability.AbilitySub} object. + */ + public void setSubAbility(final AbilitySub subAbility) { + this.subAbility = subAbility; + if (subAbility != null) { + subAbility.setParent(this); + } + } + + /** + *

+ * Getter for the field subAbility. + *

+ * + * @return a {@link forge.game.spellability.AbilitySub} object. + */ + public AbilitySub getSubAbility() { + return this.subAbility; + } + + /** + *

+ * isBasicAbility. + *

+ * + * @return a boolean. + */ + public boolean isBasicSpell() { + return this.basicSpell && !this.isFlashBackAbility() && !this.isBuyBackAbility(); + } + + /** + *

+ * Setter for the field setBasicSpell. + *

+ * + * @param basicSpell + * a boolean. + */ + public void setBasicSpell(final boolean basicSpell) { + this.basicSpell = basicSpell; + } + + /** + *

+ * Setter for the field flashBackAbility. + *

+ * + * @param flashBackAbility + * a boolean. + */ + public void setFlashBackAbility(final boolean flashBackAbility) { + this.flashBackAbility = flashBackAbility; + } + + /** + *

+ * isFlashBackAbility. + *

+ * + * @return a boolean. + */ + public boolean isFlashBackAbility() { + return this.flashBackAbility; + } + + /** + *

+ * copy. + *

+ * + * @return a {@link forge.game.spellability.SpellAbility} object. + */ + public SpellAbility copy() { + SpellAbility clone = null; + try { + clone = (SpellAbility) this.clone(); + } catch (final CloneNotSupportedException e) { + System.err.println(e); + } + return clone; + } + + public SpellAbility copyWithNoManaCost() { + final SpellAbility newSA = this.copy(); + newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana()); + newSA.setDescription(newSA.getDescription() + " (without paying its mana cost)"); + return newSA; + } + + public SpellAbility copyWithDefinedCost(Cost abCost) { + final SpellAbility newSA = this.copy(); + newSA.setPayCosts(abCost); + return newSA; + } + /** + *

+ * Setter for the field trigger. + *

+ * + * @param trigger + * a boolean. + */ + public void setTrigger(final boolean trigger) { + this.trigger = trigger; + } + + /** + *

+ * isTrigger. + *

+ * + * @return a boolean. + */ + public boolean isTrigger() { + return this.trigger; + } + + /** + * Sets the optional trigger. + * + * @param optrigger + * the new optional trigger + */ + public void setOptionalTrigger(final boolean optrigger) { + this.optionalTrigger = optrigger; + } + + /** + * Checks if is optional trigger. + * + * @return true, if is optional trigger + */ + public boolean isOptionalTrigger() { + return this.optionalTrigger; + } + + /** + *

+ * setSourceTrigger. + *

+ * + * @param id + * a int. + */ + public void setSourceTrigger(final int id) { + this.sourceTrigger = id; + } + + /** + *

+ * getSourceTrigger. + *

+ * + * @return a int. + */ + public int getSourceTrigger() { + return this.sourceTrigger; + } + + /** + *

+ * isMandatory. + *

+ * + * @return a boolean. + */ + public boolean isMandatory() { + return false; + } + + /** + *

+ * canTarget. + *

+ * + * @param entity + * a GameEntity + * @return a boolean. + */ + public final boolean canTarget(final GameObject entity) { + final TargetRestrictions tr = this.getTargetRestrictions(); + + // Restriction related to this ability + if (tr != null) { + if (tr.isUniqueTargets() && this.getUniqueTargets().contains(entity)) + return false; + + // If the cards must have a specific controller + if (hasParam("TargetsWithDefinedController") && entity instanceof Card) { + final Card c = (Card) entity; + List pl = AbilityUtils.getDefinedPlayers(getSourceCard(), getParam("TargetsWithDefinedController"), this); + if (pl == null || !pl.contains(c.getController()) ) { + return false; + } + } + + String[] validTgt = tr.getValidTgts(); + if (entity instanceof GameEntity && !((GameEntity) entity).isValid(validTgt, this.getActivatingPlayer(), this.getSourceCard())) + return false; + } + + // Restrictions coming from target + return entity.canBeTargetedBy(this); + } + + // is this a wrapping ability (used by trigger abilities) + /** + *

+ * isWrapper. + *

+ * + * @return a boolean. + * @since 1.0.15 + */ + public boolean isWrapper() { + return false; + } + + /** + * Sets the temporarily suppressed. + * + * @param supp + * the new temporarily suppressed + */ + public final void setTemporarilySuppressed(final boolean supp) { + this.temporarilySuppressed = supp; + } + + /** + * Checks if is suppressed. + * + * @return true, if is suppressed + */ + public final boolean isSuppressed() { + return (this.temporarilySuppressed); + } + + /** + * Gets the checks if is delve. + * + * @return the isDelve + */ + public final boolean isDelve() { + return this.delve; + } + + /** + * Sets the checks if is delve. + * + * @param isDelve0 + * the isDelve to set + */ + public final void setDelve(final boolean isDelve0) { + this.delve = isDelve0; + } + + /** + * Adds the tapped for convoke. + * + * @param c + * the c + */ + public void addTappedForConvoke(final Card c) { + if (this.tappedForConvoke == null) { + this.tappedForConvoke = new ArrayList(); + } + + this.tappedForConvoke.add(c); + } + + /** + * Gets the tapped for convoke. + * + * @return the tapped for convoke + */ + public List getTappedForConvoke() { + return this.tappedForConvoke; + } + + /** + * Clear tapped for convoke. + */ + public void clearTappedForConvoke() { + if (this.tappedForConvoke != null) { + this.tappedForConvoke.clear(); + } + } + + /** + * Returns whether the SA is a patron offering. + */ + public boolean isOffering() { + return this.offering; + } + + /** + * Sets the SA as a patron offering. + * + * @param c card sacrificed for a patron offering + */ + public void setIsOffering(final boolean bOffering) { + this.offering = bOffering; + } + + /** + * Sets the card sacrificed for a patron offering. + * + * @param c card sacrificed for a patron offering + */ + public void setSacrificedAsOffering(final Card c) { + this.sacrificedAsOffering = c; + } + + /** + * Gets the card sacrificed for a patron offering. + * + * @return the card sacrificed for a patron offering + */ + public Card getSacrificedAsOffering() { + return this.sacrificedAsOffering; + } + + /** + * Clear the card sacrificed for a patron offering. + */ + public void resetSacrificedAsOffering() { + this.sacrificedAsOffering = null; + } + + /** + * @return the splicedCards + */ + public List getSplicedCards() { + return splicedCards; + } + + /** + * @param splicedCard the splicedCards to set + */ + public void setSplicedCards(List splicedCards) { + this.splicedCards = splicedCards; + } + + /** + * @param splicedCard the splicedCard to add + */ + public void addSplicedCards(Card splicedCard) { + if (this.splicedCards == null) { + this.splicedCards = new ArrayList(); + } + this.splicedCards.add(splicedCard); + } + + /** + *

+ * knownDetermineDefined. + *

+ * + * @param defined + * a {@link java.lang.String} object. + * @return a {@link forge.CardList} object. + */ + public List knownDetermineDefined(final String defined) { + final List ret = new ArrayList(); + final List list = AbilityUtils.getDefinedCards(getSourceCard(), defined, this); + final Game game = getActivatingPlayer().getGame(); + + for (final Card c : list) { + final Card actualCard = game.getCardState(c); + ret.add(actualCard); + } + return ret; + } + + /** + *

+ * findRootAbility. + *

+ * + * @return a {@link forge.game.spellability.SpellAbility} object. + */ + public SpellAbility getRootAbility() { + SpellAbility parent = this; + while (null != parent.getParent()) { + parent = parent.getParent(); + } + + return parent; + } + + public SpellAbility getParent() { + return null; + } + + /** + * TODO: Write javadoc for this method. + * @return + */ + public boolean isUndoable() { + return this.undoable && this.payCosts.isUndoable() && this.getSourceCard().isInPlay(); + } + + /** + * TODO: Write javadoc for this method. + */ + public boolean undo() { + if (isUndoable() && this.getActivatingPlayer().getManaPool().accountFor(this.getManaPart())) { + this.payCosts.refundPaidCost(sourceCard); + } + return false; + } + + /** + * TODO: Write javadoc for this method. + * @param b + */ + public void setUndoable(boolean b) { + this.undoable = b; + } + + /** + * @return the isCopied + */ + public boolean isCopied() { + return isCopied; + } + + /** + * @param isCopied0 the isCopied to set + */ + public void setCopied(boolean isCopied0) { + this.isCopied = isCopied0; + } + + /** + * Returns whether variable was present in the announce list. + */ + public boolean isAnnouncing(String variable) { + String announce = getParam("Announce"); + if (StringUtils.isBlank(announce)) return false; + String[] announcedOnes = TextUtil.split(announce, ','); + for(String a : announcedOnes) { + if( a.trim().equalsIgnoreCase(variable)) + return true; + } + return false; + } + + public boolean isXCost() { + CostPartMana cm = payCosts != null ? getPayCosts().getCostMana() : null; + return cm != null && cm.getAmountOfX() > 0; + } + + public boolean isBasicLandAbility() { + return basicLandAbility; + } + + public void setBasicLandAbility(boolean basicLandAbility) { + this.basicLandAbility = basicLandAbility; // TODO: Add 0 to parameter's name. + } + + public boolean isTemporary() { + return temporary; + } + + public void setTemporary(boolean temporary) { + this.temporary = temporary; // TODO: Add 0 to parameter's name. + } + + @Override + public boolean canBeTargetedBy(SpellAbility sa) { + return sa.canTargetSpellAbility(this); + } + + /** The chosen target. */ + private TargetRestrictions targetRestricions = null; + private TargetChoices targetChosen = new TargetChoices(); + + public boolean usesTargeting() { + return targetRestricions != null; + } + + public TargetRestrictions getTargetRestrictions() { + return targetRestricions; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // THE CODE BELOW IS RELATED TO TARGETING. It might be extracted to other class from here + // + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + public void setTargetRestrictions(final TargetRestrictions tgt) { + targetRestricions = tgt; + } + + /** + * Gets the chosen target. + * + * @return the chosenTarget + */ + public TargetChoices getTargets() { + return this.targetChosen; + } + + public void setTargets(TargetChoices targets) { + this.targetChosen = targets; + } + + public void resetTargets() { + targetChosen = new TargetChoices(); + } + + /** + * Reset the first target. + * + */ + public void resetFirstTarget(GameObject c) { + SpellAbility sa = this; + while (sa != null) { + if (sa.targetRestricions != null) { + sa.targetChosen = new TargetChoices(); + sa.targetChosen.add(c); + break; + } + sa = sa.subAbility; + } + } + + /** + *

+ * getAllTargetChoices. + *

+ * + * @return a {@link java.util.ArrayList} object. + * @since 1.0.15 + */ + public final ArrayList getAllTargetChoices() { + final ArrayList res = new ArrayList(); + + SpellAbility sa = this.getRootAbility(); + if (sa.getTargetRestrictions() != null) { + res.add(sa.getTargets()); + } + while (sa.getSubAbility() != null) { + sa = sa.getSubAbility(); + + if (sa.getTargetRestrictions() != null) { + res.add(sa.getTargets()); + } + } + + return res; + } + + public Card getTargetCard() { + return targetChosen.getFirstTargetedCard(); + } + + /** + *

+ * Setter for the field targetCard. + *

+ * + * @param card + * a {@link forge.game.card.Card} object. + */ + public void setTargetCard(final Card card) { + if (card == null) { + System.out.println(this.getSourceCard() + + " - SpellAbility.setTargetCard() called with null for target card."); + return; + } + + resetTargets(); + targetChosen.add(card); + + final String desc; + + if (!card.isFaceDown()) { + desc = this.getSourceCard().getName() + " - targeting " + card; + } else { + desc = this.getSourceCard().getName() + " - targeting Morph(" + card.getUniqueNumber() + ")"; + } + this.setStackDescription(desc); + } + + /** + *

+ * findTargetCards. + *

+ * + * @return a {@link forge.game.spellability.SpellAbility} object. + */ + public List findTargetedCards() { + // First search for targeted cards associated with current ability + if (targetChosen.isTargetingAnyCard()) { + return Lists.newArrayList(targetChosen.getTargetCards()); + } + + // Next search for source cards of targeted SAs associated with current ability + if (targetChosen.isTargetingAnySpell()) { + List res = Lists.newArrayList(); + for (final SpellAbility ability : targetChosen.getTargetSpells()) { + res.add(ability.getSourceCard()); + } + return res; + } + + // Lastly Search parent SAs that targets a card + SpellAbility parent = this.getParentTargetingCard(); + if (null != parent) { + return parent.findTargetedCards(); + } + + // Lastly Search parent SAs that targets an SA + parent = this.getParentTargetingSA(); + if (null != parent) { + return parent.findTargetedCards(); + } + + return ImmutableList.of(); + } + + + public SpellAbility getSATargetingCard() { + return targetChosen.isTargetingAnyCard() ? this : getParentTargetingCard(); + } + + public SpellAbility getParentTargetingCard() { + SpellAbility parent = this.getParent(); + while (parent != null) { + if (parent.targetChosen.isTargetingAnyCard()) + return parent; + parent = parent.getParent(); + } + return null; + } + + public SpellAbility getSATargetingSA() { + return targetChosen.isTargetingAnySpell() ? this : getParentTargetingSA(); + } + + public SpellAbility getParentTargetingSA() { + SpellAbility parent = this.getParent(); + while (parent != null) { + if (parent.targetChosen.isTargetingAnySpell()) + return parent; + parent = parent.getParent(); + } + return null; + } + + public SpellAbility getSATargetingPlayer() { + return targetChosen.isTargetingAnyPlayer() ? this : getParentTargetingPlayer(); + } + + /** + *

+ * findParentsTargetedPlayer. + *

+ * + * @return a {@link forge.game.spellability.SpellAbility} object. + */ + public SpellAbility getParentTargetingPlayer() { + SpellAbility parent = this.getParent(); + while (parent != null) { + if (parent.getTargets().isTargetingAnyPlayer()) + return parent; + parent = parent.getParent(); + } + return null; + } + + /** + * Gets the unique targets. + * + * @param ability + * the ability + * @return the unique targets + */ + public final List getUniqueTargets() { + final List targets = new ArrayList(); + SpellAbility child = this.getParent(); + while (child != null) { + if (child.getTargetRestrictions() != null) { + Iterables.addAll(targets, child.getTargets().getTargets()); + } + child = child.getParent(); + } + return targets; + } + + public boolean canTargetSpellAbility(final SpellAbility topSA) { + final TargetRestrictions tgt = this.getTargetRestrictions(); + + if (this.hasParam("TargetType") && !topSA.isValid(this.getParam("TargetType").split(","), this.getActivatingPlayer(), this.getSourceCard())) { + return false; + } + + final String splitTargetRestrictions = tgt.getSAValidTargeting(); + if (splitTargetRestrictions != null) { + // TODO What about spells with SubAbilities with Targets? + + final TargetChoices matchTgt = topSA.getTargets(); + + if (matchTgt == null) { + return false; + } + + boolean result = false; + + for (final GameObject o : matchTgt.getTargets()) { + if (o.isValid(splitTargetRestrictions.split(","), this.getActivatingPlayer(), this.getSourceCard())) { + result = true; + break; + } + } + + if (!result) { + return false; + } + } + + if (tgt.isSingleTarget()) { + int totalTargets = 0; + for(TargetChoices tc : topSA.getAllTargetChoices()) { + totalTargets += tc.getNumTargeted(); + if (totalTargets > 1) { + // As soon as we get more than one, bail out + return false; + } + } + if (totalTargets != 1) { + // Make sure that there actually is one target + return false; + } + } + + return topSA.getSourceCard().isValid(tgt.getValidTgts(), this.getActivatingPlayer(), this.getSourceCard()); + } + + // Takes one argument like Permanent.Blue+withFlying + /** + *

+ * isValid. + *

+ * + * @param restriction + * a {@link java.lang.String} object. + * @param sourceController + * a {@link forge.game.player.Player} object. + * @param source + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + @Override + public final boolean isValid(final String restriction, final Player sourceController, final Card source) { + + // Inclusive restrictions are Card types + final String[] incR = restriction.split("\\.", 2); + + if (incR[0].equals("Spell")) { + if (!this.isSpell()) + return false; + } else if (incR[0].equals("Triggered")) { + if (!this.isTrigger()) + return false; + } else if (incR[0].equals("Activated")) { + if (!(this instanceof AbilityActivated)) + return false; + } else { //not a spell/ability type + return false; + } + + if (incR.length > 1) { + final String excR = incR[1]; + final String[] exR = excR.split("\\+"); // Exclusive Restrictions are ... + for (int j = 0; j < exR.length; j++) { + if (!this.hasProperty(exR[j], sourceController, source)) { + return false; + } + } + } + return true; + } // isValid(String Restriction) + + // Takes arguments like Blue or withFlying + /** + *

+ * hasProperty. + *

+ * + * @param property + * a {@link java.lang.String} object. + * @param sourceController + * a {@link forge.game.player.Player} object. + * @param source + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + @Override + public boolean hasProperty(final String property, final Player sourceController, final Card source) { + return true; + } + + // Methods enabling multiple instances of conspire + public void addConspireInstance() { + this.conspireInstances++; + } + + public void subtractConspireInstance() { + this.conspireInstances--; + } + + public int getConspireInstances() { + return this.conspireInstances; + } // End of Conspire methods + + // Madness + public boolean isMadness() { + return madness; + } + + public void setMadness(boolean madness) { + this.madness = madness; + } + + // Return whether this spell tracks what color mana is spent to cast it for the sake of the effect + public boolean tracksManaSpent() { + if (this.sourceCard == null) { return false; } + if (!this.isSpell()) { return false; } + + if (this.sourceCard.hasKeyword("Sunburst")) { + return true; + } + if (this.sourceCard.getRules().getOracleText().contains("was spent to cast")) { + return true; + } + return false; + } + } \ No newline at end of file diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java b/forge-gui/src/main/java/forge/game/spellability/SpellAbilityCondition.java similarity index 96% rename from forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java rename to forge-gui/src/main/java/forge/game/spellability/SpellAbilityCondition.java index a6251e72cf7..2ea76961eb3 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java +++ b/forge-gui/src/main/java/forge/game/spellability/SpellAbilityCondition.java @@ -1,368 +1,368 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; - -import forge.card.MagicColor; -import forge.game.Game; -import forge.game.GameObject; -import forge.game.ability.AbilityUtils; -import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CardLists; -import forge.game.phase.PhaseType; -import forge.game.player.Player; -import forge.game.zone.ZoneType; -import forge.util.Expressions; - -/** - *

- * SpellAbility_Condition class. - *

- * - * @author Forge - * @version $Id: SpellAbilityCondition.java 23788 2013-11-24 07:17:43Z Max mtg $ - * @since 1.0.15 - */ -public class SpellAbilityCondition extends SpellAbilityVariables { - // A class for handling SpellAbility Conditions. These restrictions include: - // Zone, Phase, OwnTurn, Speed (instant/sorcery), Amount per Turn, Player, - // Threshold, Metalcraft, LevelRange, etc - // Each value will have a default, that can be overridden (mostly by - // AbilityFactory) - // The CanPlay function will use these values to determine if the current - // game state is ok with these restrictions - - /** - *

- * Constructor for SpellAbility_Condition. - *

- */ - public SpellAbilityCondition() { - } - - /** - *

- * setConditions. - *

- * - * @param params - * a {@link java.util.HashMap} object. - */ - public final void setConditions(final Map params) { - if (params.containsKey("Condition")) { - final String value = params.get("Condition"); - if (value.equals("Threshold")) { - this.setThreshold(true); - } - if (value.equals("Metalcraft")) { - this.setMetalcraft(true); - } - if (value.equals("Hellbent")) { - this.setHellbent(true); - } - if (value.equals("Kicked")) { - this.kicked = true; - } - if (value.equals("Kicked 1")) { - this.kicked1 = true; - } - if (value.equals("Kicked 2")) { - this.kicked2 = true; - } - if (value.equals("AllTargetsLegal")) { - this.setAllTargetsLegal(true); - } - if (value.equals("AltCost")) - this.altCostPaid = true; - } - - if (params.containsKey("ConditionZone")) { - this.setZone(ZoneType.smartValueOf(params.get("ContitionZone"))); - } - - if (params.containsKey("ConditionSorcerySpeed")) { - this.setSorcerySpeed(true); - } - - if (params.containsKey("ConditionPlayerTurn")) { - this.setPlayerTurn(true); - } - - if (params.containsKey("ConditionOpponentTurn")) { - this.setOpponentTurn(true); - } - - if (params.containsKey("ConditionPhases")) { - this.setPhases(PhaseType.parseRange(params.get("ConditionPhases"))); - } - - if (params.containsKey("ConditionChosenColor")) { - this.setColorToCheck(params.get("ConditionChosenColor")); - } - - // Condition version of IsPresent stuff - if (params.containsKey("ConditionPresent")) { - this.setIsPresent(params.get("ConditionPresent")); - if (params.containsKey("ConditionCompare")) { - this.setPresentCompare(params.get("ConditionCompare")); - } - } - - if (params.containsKey("ConditionDefined")) { - this.setPresentDefined(params.get("ConditionDefined")); - } - - if (params.containsKey("ConditionPlayerDefined")) { - this.setPlayerDefined(params.get("ConditionPlayerDefined")); - } - - if (params.containsKey("ConditionPlayerContains")) { - this.setPlayerContains(params.get("ConditionPlayerContains")); - } - - if (params.containsKey("ConditionNotPresent")) { - this.setIsPresent(params.get("ConditionNotPresent")); - this.setPresentCompare("EQ0"); - } - - // basically PresentCompare for life totals: - if (params.containsKey("ConditionLifeTotal")) { - this.setLifeTotal(params.get("ConditionLifeTotal")); - if (params.containsKey("ConditionLifeAmount")) { - this.setLifeAmount(params.get("ConditionLifeAmount")); - } - } - - if (params.containsKey("ConditionManaSpent")) { - this.setManaSpent(params.get("ConditionManaSpent")); - } - - if (params.containsKey("ConditionCheckSVar")) { - this.setSvarToCheck(params.get("ConditionCheckSVar")); - } - if (params.containsKey("ConditionSVarCompare")) { - this.setSvarOperator(params.get("ConditionSVarCompare").substring(0, 2)); - this.setSvarOperand(params.get("ConditionSVarCompare").substring(2)); - } - if (params.containsKey("ConditionTargetValidTargeting")) { - this.setTargetValidTargeting(params.get("ConditionTargetValidTargeting")); - } - if (params.containsKey("ConditionTargetsSingleTarget")) { - this.setTargetsSingleTarget(true); - } - - } // setConditions - - /** - *

- * checkConditions. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean. - */ - public final boolean areMet(final SpellAbility sa) { - - Player activator = sa.getActivatingPlayer(); - if (activator == null) { - activator = sa.getSourceCard().getController(); - System.out.println(sa.getSourceCard().getName() - + " Did not have activator set in SpellAbility_Condition.checkConditions()"); - } - final Game game = activator.getGame(); - - if (this.isHellbent() && !activator.hasHellbent()) return false; - if (this.isThreshold() && !activator.hasThreshold()) return false; - if (this.isMetalcraft() && !activator.hasMetalcraft()) return false; - - if (this.kicked && !sa.isKicked()) return false; - if (this.kicked1 && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) return false; - if (this.kicked2 && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) return false; - if( this.altCostPaid && !sa.isOptionalCostPaid(OptionalCost.AltCost)) return false; - - if (this.isAllTargetsLegal()) { - for (Card c : sa.getTargets().getTargetCards()) { - if (!CardFactoryUtil.isTargetStillValid(sa, c)) { - return false; - } - } - } - - if (this.isSorcerySpeed() && !activator.canCastSorcery()) { - return false; - } - - if (this.isPlayerTurn() && !activator.getGame().getPhaseHandler().isPlayerTurn(activator)) { - return false; - } - - if (this.isOpponentTurn() && !activator.getGame().getPhaseHandler().getPlayerTurn().isOpponentOf(activator)) { - return false; - } - - if ((this.getActivationLimit() != -1) && (this.getNumberTurnActivations() >= this.getActivationLimit())) { - return false; - } - - if ((this.getGameActivationLimit() != -1) && (this.getNumberGameActivations() >= this.getGameActivationLimit())) { - return false; - } - - if (this.getPhases().size() > 0) { - boolean isPhase = false; - final PhaseType currPhase = game.getPhaseHandler().getPhase(); - for (final PhaseType s : this.getPhases()) { - if (s == currPhase) { - isPhase = true; - break; - } - } - - if (!isPhase) { - return false; - } - } - - if (this.getCardsInHand() != -1) { - // Can handle Library of Alexandria, or Hellbent - if (activator.getCardsIn(ZoneType.Hand).size() != this.getCardsInHand()) { - return false; - } - } - - if (this.getColorToCheck() != null) { - if (!sa.getSourceCard().getChosenColor().contains(this.getColorToCheck())) { - return false; - } - } - - if (this.getIsPresent() != null) { - List list = new ArrayList(); - if (this.getPresentDefined() != null) { - list.addAll(AbilityUtils.getDefinedCards(sa.getSourceCard(), this.getPresentDefined(), sa)); - } else { - list = game.getCardsIn(ZoneType.Battlefield); - } - - list = CardLists.getValidCards(list, this.getIsPresent().split(","), sa.getActivatingPlayer(), sa.getSourceCard()); - - int right; - final String rightString = this.getPresentCompare().substring(2); - try { // If this is an Integer, just parse it - right = Integer.parseInt(rightString); - } catch (final NumberFormatException e) { // Otherwise, grab it from - // the - // SVar - right = CardFactoryUtil.xCount(sa.getSourceCard(), sa.getSourceCard().getSVar(rightString)); - } - - final int left = list.size(); - - if (!Expressions.compare(left, this.getPresentCompare(), right)) { - return false; - } - } - - if (this.getPlayerContains() != null) { - List list = new ArrayList(); - if (this.getPlayerDefined() != null) { - list.addAll(AbilityUtils.getDefinedPlayers(sa.getSourceCard(), this.getPlayerDefined(), sa)); - } - List contains = AbilityUtils.getDefinedPlayers(sa.getSourceCard(), this.getPlayerContains(), sa); - if (!list.containsAll(contains)) { - return false; - } - } - - if (this.getLifeTotal() != null) { - int life = 1; - if (this.getLifeTotal().equals("You")) { - life = activator.getLife(); - } - if (this.getLifeTotal().equals("Opponent")) { - life = activator.getOpponent().getLife(); - } - - int right = 1; - final String rightString = this.getLifeAmount().substring(2); - if (rightString.equals("X")) { - right = CardFactoryUtil.xCount(sa.getSourceCard(), sa.getSourceCard().getSVar("X")); - } else { - right = Integer.parseInt(this.getLifeAmount().substring(2)); - } - - if (!Expressions.compare(life, this.getLifeAmount(), right)) { - return false; - } - } - if (this.getTargetValidTargeting() != null) { - final TargetChoices matchTgt = sa.getTargets(); - if (matchTgt == null || matchTgt.getFirstTargetedSpell() == null - || matchTgt.getFirstTargetedSpell().getTargets() == null) { - return false; - } - - boolean result = false; - - for (final GameObject o : matchTgt.getFirstTargetedSpell().getTargets().getTargets()) { - if (o.isValid(this.getTargetValidTargeting().split(","), sa.getActivatingPlayer(), sa.getSourceCard())) { - result = true; - break; - } - } - - if (!result) { - return false; - } - } - if (this.targetsSingleTarget()) { - final TargetChoices matchTgt = sa.getTargets(); - if (matchTgt == null || matchTgt.getFirstTargetedSpell() == null - || matchTgt.getFirstTargetedSpell().getTargets() == null - || matchTgt.getFirstTargetedSpell().getTargets().getNumTargeted() != 1) { - return false; - } - } - - if (StringUtils.isNotEmpty(this.getManaSpent())) { - byte manaSpent = MagicColor.fromName(getManaSpent()); // they always check for single color - if( 0 == (manaSpent & sa.getSourceCard().getColorsPaid())) // no match of colors - return false; - } - - if (this.getsVarToCheck() != null) { - final int svarValue = AbilityUtils.calculateAmount(sa.getSourceCard(), this.getsVarToCheck(), sa); - final int operandValue = AbilityUtils.calculateAmount(sa.getSourceCard(), this.getsVarOperand(), sa); - - if (!Expressions.compare(svarValue, this.getsVarOperator(), operandValue)) { - return false; - } - - } - - return true; - } - -} // end class SpellAbility_Condition +/* + * 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 . + */ +package forge.game.spellability; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import forge.card.MagicColor; +import forge.game.Game; +import forge.game.GameObject; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.card.CardFactoryUtil; +import forge.game.card.CardLists; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.zone.ZoneType; +import forge.util.Expressions; + +/** + *

+ * SpellAbility_Condition class. + *

+ * + * @author Forge + * @version $Id$ + * @since 1.0.15 + */ +public class SpellAbilityCondition extends SpellAbilityVariables { + // A class for handling SpellAbility Conditions. These restrictions include: + // Zone, Phase, OwnTurn, Speed (instant/sorcery), Amount per Turn, Player, + // Threshold, Metalcraft, LevelRange, etc + // Each value will have a default, that can be overridden (mostly by + // AbilityFactory) + // The CanPlay function will use these values to determine if the current + // game state is ok with these restrictions + + /** + *

+ * Constructor for SpellAbility_Condition. + *

+ */ + public SpellAbilityCondition() { + } + + /** + *

+ * setConditions. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + */ + public final void setConditions(final Map params) { + if (params.containsKey("Condition")) { + final String value = params.get("Condition"); + if (value.equals("Threshold")) { + this.setThreshold(true); + } + if (value.equals("Metalcraft")) { + this.setMetalcraft(true); + } + if (value.equals("Hellbent")) { + this.setHellbent(true); + } + if (value.equals("Kicked")) { + this.kicked = true; + } + if (value.equals("Kicked 1")) { + this.kicked1 = true; + } + if (value.equals("Kicked 2")) { + this.kicked2 = true; + } + if (value.equals("AllTargetsLegal")) { + this.setAllTargetsLegal(true); + } + if (value.equals("AltCost")) + this.altCostPaid = true; + } + + if (params.containsKey("ConditionZone")) { + this.setZone(ZoneType.smartValueOf(params.get("ContitionZone"))); + } + + if (params.containsKey("ConditionSorcerySpeed")) { + this.setSorcerySpeed(true); + } + + if (params.containsKey("ConditionPlayerTurn")) { + this.setPlayerTurn(true); + } + + if (params.containsKey("ConditionOpponentTurn")) { + this.setOpponentTurn(true); + } + + if (params.containsKey("ConditionPhases")) { + this.setPhases(PhaseType.parseRange(params.get("ConditionPhases"))); + } + + if (params.containsKey("ConditionChosenColor")) { + this.setColorToCheck(params.get("ConditionChosenColor")); + } + + // Condition version of IsPresent stuff + if (params.containsKey("ConditionPresent")) { + this.setIsPresent(params.get("ConditionPresent")); + if (params.containsKey("ConditionCompare")) { + this.setPresentCompare(params.get("ConditionCompare")); + } + } + + if (params.containsKey("ConditionDefined")) { + this.setPresentDefined(params.get("ConditionDefined")); + } + + if (params.containsKey("ConditionPlayerDefined")) { + this.setPlayerDefined(params.get("ConditionPlayerDefined")); + } + + if (params.containsKey("ConditionPlayerContains")) { + this.setPlayerContains(params.get("ConditionPlayerContains")); + } + + if (params.containsKey("ConditionNotPresent")) { + this.setIsPresent(params.get("ConditionNotPresent")); + this.setPresentCompare("EQ0"); + } + + // basically PresentCompare for life totals: + if (params.containsKey("ConditionLifeTotal")) { + this.setLifeTotal(params.get("ConditionLifeTotal")); + if (params.containsKey("ConditionLifeAmount")) { + this.setLifeAmount(params.get("ConditionLifeAmount")); + } + } + + if (params.containsKey("ConditionManaSpent")) { + this.setManaSpent(params.get("ConditionManaSpent")); + } + + if (params.containsKey("ConditionCheckSVar")) { + this.setSvarToCheck(params.get("ConditionCheckSVar")); + } + if (params.containsKey("ConditionSVarCompare")) { + this.setSvarOperator(params.get("ConditionSVarCompare").substring(0, 2)); + this.setSvarOperand(params.get("ConditionSVarCompare").substring(2)); + } + if (params.containsKey("ConditionTargetValidTargeting")) { + this.setTargetValidTargeting(params.get("ConditionTargetValidTargeting")); + } + if (params.containsKey("ConditionTargetsSingleTarget")) { + this.setTargetsSingleTarget(true); + } + + } // setConditions + + /** + *

+ * checkConditions. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean. + */ + public final boolean areMet(final SpellAbility sa) { + + Player activator = sa.getActivatingPlayer(); + if (activator == null) { + activator = sa.getSourceCard().getController(); + System.out.println(sa.getSourceCard().getName() + + " Did not have activator set in SpellAbility_Condition.checkConditions()"); + } + final Game game = activator.getGame(); + + if (this.isHellbent() && !activator.hasHellbent()) return false; + if (this.isThreshold() && !activator.hasThreshold()) return false; + if (this.isMetalcraft() && !activator.hasMetalcraft()) return false; + + if (this.kicked && !sa.isKicked()) return false; + if (this.kicked1 && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) return false; + if (this.kicked2 && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) return false; + if( this.altCostPaid && !sa.isOptionalCostPaid(OptionalCost.AltCost)) return false; + + if (this.isAllTargetsLegal()) { + for (Card c : sa.getTargets().getTargetCards()) { + if (!CardFactoryUtil.isTargetStillValid(sa, c)) { + return false; + } + } + } + + if (this.isSorcerySpeed() && !activator.canCastSorcery()) { + return false; + } + + if (this.isPlayerTurn() && !activator.getGame().getPhaseHandler().isPlayerTurn(activator)) { + return false; + } + + if (this.isOpponentTurn() && !activator.getGame().getPhaseHandler().getPlayerTurn().isOpponentOf(activator)) { + return false; + } + + if ((this.getActivationLimit() != -1) && (this.getNumberTurnActivations() >= this.getActivationLimit())) { + return false; + } + + if ((this.getGameActivationLimit() != -1) && (this.getNumberGameActivations() >= this.getGameActivationLimit())) { + return false; + } + + if (this.getPhases().size() > 0) { + boolean isPhase = false; + final PhaseType currPhase = game.getPhaseHandler().getPhase(); + for (final PhaseType s : this.getPhases()) { + if (s == currPhase) { + isPhase = true; + break; + } + } + + if (!isPhase) { + return false; + } + } + + if (this.getCardsInHand() != -1) { + // Can handle Library of Alexandria, or Hellbent + if (activator.getCardsIn(ZoneType.Hand).size() != this.getCardsInHand()) { + return false; + } + } + + if (this.getColorToCheck() != null) { + if (!sa.getSourceCard().getChosenColor().contains(this.getColorToCheck())) { + return false; + } + } + + if (this.getIsPresent() != null) { + List list = new ArrayList(); + if (this.getPresentDefined() != null) { + list.addAll(AbilityUtils.getDefinedCards(sa.getSourceCard(), this.getPresentDefined(), sa)); + } else { + list = game.getCardsIn(ZoneType.Battlefield); + } + + list = CardLists.getValidCards(list, this.getIsPresent().split(","), sa.getActivatingPlayer(), sa.getSourceCard()); + + int right; + final String rightString = this.getPresentCompare().substring(2); + try { // If this is an Integer, just parse it + right = Integer.parseInt(rightString); + } catch (final NumberFormatException e) { // Otherwise, grab it from + // the + // SVar + right = CardFactoryUtil.xCount(sa.getSourceCard(), sa.getSourceCard().getSVar(rightString)); + } + + final int left = list.size(); + + if (!Expressions.compare(left, this.getPresentCompare(), right)) { + return false; + } + } + + if (this.getPlayerContains() != null) { + List list = new ArrayList(); + if (this.getPlayerDefined() != null) { + list.addAll(AbilityUtils.getDefinedPlayers(sa.getSourceCard(), this.getPlayerDefined(), sa)); + } + List contains = AbilityUtils.getDefinedPlayers(sa.getSourceCard(), this.getPlayerContains(), sa); + if (!list.containsAll(contains)) { + return false; + } + } + + if (this.getLifeTotal() != null) { + int life = 1; + if (this.getLifeTotal().equals("You")) { + life = activator.getLife(); + } + if (this.getLifeTotal().equals("Opponent")) { + life = activator.getOpponent().getLife(); + } + + int right = 1; + final String rightString = this.getLifeAmount().substring(2); + if (rightString.equals("X")) { + right = CardFactoryUtil.xCount(sa.getSourceCard(), sa.getSourceCard().getSVar("X")); + } else { + right = Integer.parseInt(this.getLifeAmount().substring(2)); + } + + if (!Expressions.compare(life, this.getLifeAmount(), right)) { + return false; + } + } + if (this.getTargetValidTargeting() != null) { + final TargetChoices matchTgt = sa.getTargets(); + if (matchTgt == null || matchTgt.getFirstTargetedSpell() == null + || matchTgt.getFirstTargetedSpell().getTargets() == null) { + return false; + } + + boolean result = false; + + for (final GameObject o : matchTgt.getFirstTargetedSpell().getTargets().getTargets()) { + if (o.isValid(this.getTargetValidTargeting().split(","), sa.getActivatingPlayer(), sa.getSourceCard())) { + result = true; + break; + } + } + + if (!result) { + return false; + } + } + if (this.targetsSingleTarget()) { + final TargetChoices matchTgt = sa.getTargets(); + if (matchTgt == null || matchTgt.getFirstTargetedSpell() == null + || matchTgt.getFirstTargetedSpell().getTargets() == null + || matchTgt.getFirstTargetedSpell().getTargets().getNumTargeted() != 1) { + return false; + } + } + + if (StringUtils.isNotEmpty(this.getManaSpent())) { + byte manaSpent = MagicColor.fromName(getManaSpent()); // they always check for single color + if( 0 == (manaSpent & sa.getSourceCard().getColorsPaid())) // no match of colors + return false; + } + + if (this.getsVarToCheck() != null) { + final int svarValue = AbilityUtils.calculateAmount(sa.getSourceCard(), this.getsVarToCheck(), sa); + final int operandValue = AbilityUtils.calculateAmount(sa.getSourceCard(), this.getsVarOperand(), sa); + + if (!Expressions.compare(svarValue, this.getsVarOperator(), operandValue)) { + return false; + } + + } + + return true; + } + +} // end class SpellAbility_Condition diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java b/forge-gui/src/main/java/forge/game/spellability/SpellAbilityRestriction.java similarity index 96% rename from forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java rename to forge-gui/src/main/java/forge/game/spellability/SpellAbilityRestriction.java index 7fe006f457e..26ec65dff4a 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java +++ b/forge-gui/src/main/java/forge/game/spellability/SpellAbilityRestriction.java @@ -1,471 +1,471 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import forge.game.Game; -import forge.game.ability.AbilityUtils; -import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CardLists; -import forge.game.phase.PhaseType; -import forge.game.player.Player; -import forge.game.zone.Zone; -import forge.game.zone.ZoneType; -import forge.util.Expressions; - -/** - *

- * SpellAbilityRestriction class. - *

- * - * @author Forge - * @version $Id: SpellAbilityRestriction.java 23788 2013-11-24 07:17:43Z Max mtg $ - */ -public class SpellAbilityRestriction extends SpellAbilityVariables { - // A class for handling SpellAbility Restrictions. These restrictions - // include: - // Zone, Phase, OwnTurn, Speed (instant/sorcery), Amount per Turn, Player, - // Threshold, Metalcraft, LevelRange, etc - // Each value will have a default, that can be overridden (mostly by - // AbilityFactory) - // The canPlay function will use these values to determine if the current - // game state is ok with these restrictions - - /** - *

- * Constructor for SpellAbilityRestriction. - *

- */ - public SpellAbilityRestriction() { - } - - /** - *

- * setRestrictions. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @since 1.0.15 - */ - public final void setRestrictions(final Map params) { - if (params.containsKey("Activation")) { - final String value = params.get("Activation"); - if (value.equals("Threshold")) { - this.setThreshold(true); - } - if (value.equals("Metalcraft")) { - this.setMetalcraft(true); - } - if (value.equals("Hellbent")) { - this.setHellbent(true); - } - if (value.startsWith("Prowl")) { - final ArrayList prowlTypes = new ArrayList(); - prowlTypes.add("Rogue"); - if (value.split("Prowl").length > 1) { - prowlTypes.add(value.split("Prowl")[1]); - } - this.setProwlTypes(prowlTypes); - } - } - - if (params.containsKey("ActivationZone")) { - this.setZone(ZoneType.smartValueOf(params.get("ActivationZone"))); - } - - if (params.containsKey("Flashback")) { - this.setZone(ZoneType.Graveyard); - } - - if (params.containsKey("SorcerySpeed")) { - this.setSorcerySpeed(true); - } - - if (params.containsKey("InstantSpeed")) { - this.setInstantSpeed(true); - } - - if (params.containsKey("PlayerTurn")) { - this.setPlayerTurn(true); - } - - if (params.containsKey("OpponentTurn")) { - this.setOpponentTurn(true); - } - - if (params.containsKey("AnyPlayer")) { - this.setAnyPlayer(true); - } - - if (params.containsKey("AnyOpponent")) { - this.setOpponentOnly(true); - } - - if (params.containsKey("OwnerOnly")) { - this.setOwnerOnly(true); - } - - if (params.containsKey("ActivationLimit")) { - this.setLimitToCheck(params.get("ActivationLimit")); - } - - if (params.containsKey("GameActivationLimit")) { - this.setGameLimitToCheck(params.get("GameActivationLimit")); - } - - if (params.containsKey("ActivationNumberSacrifice")) { - this.setActivationNumberSacrifice(Integer.parseInt(params.get("ActivationNumberSacrifice"))); - } - - if (params.containsKey("ActivationPhases")) { - this.setPhases(PhaseType.parseRange(params.get("ActivationPhases"))); - } - - if (params.containsKey("ActivationCardsInHand")) { - this.setActivateCardsInHand(Integer.parseInt(params.get("ActivationCardsInHand"))); - } - - if (params.containsKey("ActivationChosenColor")) { - this.setColorToCheck(params.get("ActivationChosenColor")); - } - - if (params.containsKey("Planeswalker")) { - this.setPlaneswalker(true); - this.setSorcerySpeed(true); - } - - if (params.containsKey("IsPresent")) { - this.setIsPresent(params.get("IsPresent")); - if (params.containsKey("PresentCompare")) { - this.setPresentCompare(params.get("PresentCompare")); - } - if (params.containsKey("PresentZone")) { - this.setPresentZone(ZoneType.smartValueOf(params.get("PresentZone"))); - } - } - - if (params.containsKey("IsNotPresent")) { - this.setIsPresent(params.get("IsNotPresent")); - this.setPresentCompare("EQ0"); - } - - // basically PresentCompare for life totals: - if (params.containsKey("ActivationLifeTotal")) { - this.setLifeTotal(params.get("ActivationLifeTotal")); - if (params.containsKey("ActivationLifeAmount")) { - this.setLifeAmount(params.get("ActivationLifeAmount")); - } - } - - if (params.containsKey("CheckSVar")) { - this.setSvarToCheck(params.get("CheckSVar")); - } - if (params.containsKey("SVarCompare")) { - this.setSvarOperator(params.get("SVarCompare").substring(0, 2)); - this.setSvarOperand(params.get("SVarCompare").substring(2)); - } - } // end setRestrictions() - - /** - *

- * checkZoneRestrictions. - *

- * @param c - * a {@link forge.game.card.Card} object. - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean. - */ - public final boolean checkZoneRestrictions(final Card c, final SpellAbility sa) { - if (this.getZone() == null) { - return true; - } - - Player activator = sa.getActivatingPlayer(); - Zone cardZone = activator.getGame().getZoneOf(c); - - if (cardZone == null || !cardZone.is(this.getZone())) { - // If Card is not in the default activating zone, do some additional checks - // Not a Spell, or on Battlefield, return false - if (!sa.isSpell() || (cardZone != null && ZoneType.Battlefield.equals(cardZone.getZoneType())) - || !this.getZone().equals(ZoneType.Hand)) { - return false; - } - if (c.hasKeyword("May be played") && activator.equals(c.getController())) { - return true; - } - if (c.hasKeyword("May be played by your opponent") && !activator.equals(c.getController())) { - return true; - } - return false; - } - - return true; - } - - /** - *

- * checkTimingRestrictions. - *

- * @param c - * a {@link forge.game.card.Card} object. - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean. - */ - public final boolean checkTimingRestrictions(final Card c, final SpellAbility sa) { - Player activator = sa.getActivatingPlayer(); - final Game game = activator.getGame(); - - if (this.isPlayerTurn() && !game.getPhaseHandler().isPlayerTurn(activator)) { - return false; - } - - if (this.isOpponentTurn() && !game.getPhaseHandler().getPlayerTurn().isOpponentOf(activator)) { - return false; - } - - if (this.getPhases().size() > 0) { - boolean isPhase = false; - final PhaseType currPhase = game.getPhaseHandler().getPhase(); - for (final PhaseType s : this.getPhases()) { - if (s == currPhase) { - isPhase = true; - break; - } - } - - if (!isPhase) { - return false; - } - } - return true; - } - - /** - *

- * checkActivatorRestrictions. - *

- * @param c - * a {@link forge.game.card.Card} object. - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean. - */ - public final boolean checkActivatorRestrictions(final Card c, final SpellAbility sa) { - Player activator = sa.getActivatingPlayer(); - if (this.isAnyPlayer()) { - return true; - } - - if (this.isOwnerOnly()) { - return activator.equals(c.getOwner()); - } - - if (activator.equals(c.getController()) && !this.isOpponentOnly()) { - return true; - } - - if (activator.isOpponentOf(c.getController()) && this.isOpponentOnly()) { - return true; - } - if (sa.isSpell() && activator.isOpponentOf(c.getController()) && c.hasKeyword("May be played by your opponent")) { - return true; - } - - return false; - } - - /** - *

- * canPlay. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean. - */ - public final boolean canPlay(final Card c, final SpellAbility sa) { - if (c.isPhasedOut() || c.isUsedToPay()) { - return false; - } - - Player activator = sa.getActivatingPlayer(); - if (activator == null) { - activator = c.getController(); - sa.setActivatingPlayer(activator); - System.out.println(c.getName() + " Did not have activator set in SpellAbilityRestriction.canPlay()"); - } - - final Game game = activator.getGame(); - - if (this.isSorcerySpeed() && !activator.canCastSorcery()) { - return false; - } - - if (!checkTimingRestrictions(c, sa)) { - return false; - } - - if (!checkActivatorRestrictions(c, sa)) { - return false; - } - - if (!checkZoneRestrictions(c, sa)) { - return false; - } - - if (this.getLimitToCheck() != null) { - String limit = this.getLimitToCheck(); - int activationLimit = AbilityUtils.calculateAmount(c, limit, sa); - this.setActivationLimit(activationLimit); - - if ((this.getActivationLimit() != -1) && (this.getNumberTurnActivations() >= this.getActivationLimit())) { - return false; - } - } - - if (this.getGameLimitToCheck() != null) { - String limit = this.getGameLimitToCheck(); - int gameActivationLimit = AbilityUtils.calculateAmount(c, limit, sa); - this.setGameActivationLimit(gameActivationLimit); - - if ((this.getGameActivationLimit() != -1) && (this.getNumberGameActivations() >= this.getGameActivationLimit())) { - return false; - } - } - - if (this.getCardsInHand() != -1) { - if (activator.getCardsIn(ZoneType.Hand).size() != this.getCardsInHand()) { - return false; - } - } - - if (this.getColorToCheck() != null) { - if (!sa.getSourceCard().getChosenColor().contains(this.getColorToCheck())) { - return false; - } - } - if (this.isHellbent()) { - if (!activator.hasHellbent()) { - return false; - } - } - if (this.isThreshold()) { - if (!activator.hasThreshold()) { - return false; - } - } - if (this.isMetalcraft()) { - if (!activator.hasMetalcraft()) { - return false; - } - } - if (this.getProwlTypes() != null && !this.getProwlTypes().isEmpty()) { - // only true if the activating player has damaged the opponent with - // one of the specified types - boolean prowlFlag = false; - for (final String type : this.getProwlTypes()) { - if (activator.hasProwl(type)) { - prowlFlag = true; - } - } - if (!prowlFlag) { - return false; - } - } - if (this.getIsPresent() != null) { - List list = game.getCardsIn(this.getPresentZone()); - - list = CardLists.getValidCards(list, this.getIsPresent().split(","), activator, c); - - int right = 1; - final String rightString = this.getPresentCompare().substring(2); - right = AbilityUtils.calculateAmount(c, rightString, sa); - final int left = list.size(); - - if (!Expressions.compare(left, this.getPresentCompare(), right)) { - return false; - } - } - - if (this.getLifeTotal() != null) { - int life = 1; - if (this.getLifeTotal().equals("You")) { - life = activator.getLife(); - } - if (this.getLifeTotal().equals("Opponent")) { - life = activator.getOpponent().getLife(); - } - - int right = 1; - final String rightString = this.getLifeAmount().substring(2); - if (rightString.equals("X")) { - right = CardFactoryUtil.xCount(sa.getSourceCard(), sa.getSourceCard().getSVar("X")); - } else { - right = Integer.parseInt(this.getLifeAmount().substring(2)); - } - - if (!Expressions.compare(life, this.getLifeAmount(), right)) { - return false; - } - } - - if (this.isPwAbility()) { - - for (final SpellAbility pwAbs : c.getAllSpellAbilities()) { - // check all abilities on card that have their planeswalker - // restriction set to confirm they haven't been activated - final SpellAbilityRestriction restrict = pwAbs.getRestrictions(); - if (restrict.getPlaneswalker() && (restrict.getNumberTurnActivations() > 0)) { - return false; - } - } - } - - if (this.getsVarToCheck() != null) { - final int svarValue = AbilityUtils.calculateAmount(c, this.getsVarToCheck(), sa); - final int operandValue = AbilityUtils.calculateAmount(c, this.getsVarOperand(), sa); - - if (!Expressions.compare(svarValue, this.getsVarOperator(), operandValue)) { - return false; - } - - } - - if (this.getsVarToCheck() != null) { - final int svarValue = AbilityUtils.calculateAmount(sa.getSourceCard(), this.getsVarToCheck(), sa); - final int operandValue = AbilityUtils.calculateAmount(sa.getSourceCard(), this.getsVarOperand(), sa); - - if (!Expressions.compare(svarValue, this.getsVarOperator(), operandValue)) { - return false; - } - - } - - return true; - } // canPlay() - -} // end class SpellAbilityRestriction +/* + * 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 . + */ +package forge.game.spellability; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import forge.game.Game; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.card.CardFactoryUtil; +import forge.game.card.CardLists; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.zone.Zone; +import forge.game.zone.ZoneType; +import forge.util.Expressions; + +/** + *

+ * SpellAbilityRestriction class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class SpellAbilityRestriction extends SpellAbilityVariables { + // A class for handling SpellAbility Restrictions. These restrictions + // include: + // Zone, Phase, OwnTurn, Speed (instant/sorcery), Amount per Turn, Player, + // Threshold, Metalcraft, LevelRange, etc + // Each value will have a default, that can be overridden (mostly by + // AbilityFactory) + // The canPlay function will use these values to determine if the current + // game state is ok with these restrictions + + /** + *

+ * Constructor for SpellAbilityRestriction. + *

+ */ + public SpellAbilityRestriction() { + } + + /** + *

+ * setRestrictions. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @since 1.0.15 + */ + public final void setRestrictions(final Map params) { + if (params.containsKey("Activation")) { + final String value = params.get("Activation"); + if (value.equals("Threshold")) { + this.setThreshold(true); + } + if (value.equals("Metalcraft")) { + this.setMetalcraft(true); + } + if (value.equals("Hellbent")) { + this.setHellbent(true); + } + if (value.startsWith("Prowl")) { + final ArrayList prowlTypes = new ArrayList(); + prowlTypes.add("Rogue"); + if (value.split("Prowl").length > 1) { + prowlTypes.add(value.split("Prowl")[1]); + } + this.setProwlTypes(prowlTypes); + } + } + + if (params.containsKey("ActivationZone")) { + this.setZone(ZoneType.smartValueOf(params.get("ActivationZone"))); + } + + if (params.containsKey("Flashback")) { + this.setZone(ZoneType.Graveyard); + } + + if (params.containsKey("SorcerySpeed")) { + this.setSorcerySpeed(true); + } + + if (params.containsKey("InstantSpeed")) { + this.setInstantSpeed(true); + } + + if (params.containsKey("PlayerTurn")) { + this.setPlayerTurn(true); + } + + if (params.containsKey("OpponentTurn")) { + this.setOpponentTurn(true); + } + + if (params.containsKey("AnyPlayer")) { + this.setAnyPlayer(true); + } + + if (params.containsKey("AnyOpponent")) { + this.setOpponentOnly(true); + } + + if (params.containsKey("OwnerOnly")) { + this.setOwnerOnly(true); + } + + if (params.containsKey("ActivationLimit")) { + this.setLimitToCheck(params.get("ActivationLimit")); + } + + if (params.containsKey("GameActivationLimit")) { + this.setGameLimitToCheck(params.get("GameActivationLimit")); + } + + if (params.containsKey("ActivationNumberSacrifice")) { + this.setActivationNumberSacrifice(Integer.parseInt(params.get("ActivationNumberSacrifice"))); + } + + if (params.containsKey("ActivationPhases")) { + this.setPhases(PhaseType.parseRange(params.get("ActivationPhases"))); + } + + if (params.containsKey("ActivationCardsInHand")) { + this.setActivateCardsInHand(Integer.parseInt(params.get("ActivationCardsInHand"))); + } + + if (params.containsKey("ActivationChosenColor")) { + this.setColorToCheck(params.get("ActivationChosenColor")); + } + + if (params.containsKey("Planeswalker")) { + this.setPlaneswalker(true); + this.setSorcerySpeed(true); + } + + if (params.containsKey("IsPresent")) { + this.setIsPresent(params.get("IsPresent")); + if (params.containsKey("PresentCompare")) { + this.setPresentCompare(params.get("PresentCompare")); + } + if (params.containsKey("PresentZone")) { + this.setPresentZone(ZoneType.smartValueOf(params.get("PresentZone"))); + } + } + + if (params.containsKey("IsNotPresent")) { + this.setIsPresent(params.get("IsNotPresent")); + this.setPresentCompare("EQ0"); + } + + // basically PresentCompare for life totals: + if (params.containsKey("ActivationLifeTotal")) { + this.setLifeTotal(params.get("ActivationLifeTotal")); + if (params.containsKey("ActivationLifeAmount")) { + this.setLifeAmount(params.get("ActivationLifeAmount")); + } + } + + if (params.containsKey("CheckSVar")) { + this.setSvarToCheck(params.get("CheckSVar")); + } + if (params.containsKey("SVarCompare")) { + this.setSvarOperator(params.get("SVarCompare").substring(0, 2)); + this.setSvarOperand(params.get("SVarCompare").substring(2)); + } + } // end setRestrictions() + + /** + *

+ * checkZoneRestrictions. + *

+ * @param c + * a {@link forge.game.card.Card} object. + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean. + */ + public final boolean checkZoneRestrictions(final Card c, final SpellAbility sa) { + if (this.getZone() == null) { + return true; + } + + Player activator = sa.getActivatingPlayer(); + Zone cardZone = activator.getGame().getZoneOf(c); + + if (cardZone == null || !cardZone.is(this.getZone())) { + // If Card is not in the default activating zone, do some additional checks + // Not a Spell, or on Battlefield, return false + if (!sa.isSpell() || (cardZone != null && ZoneType.Battlefield.equals(cardZone.getZoneType())) + || !this.getZone().equals(ZoneType.Hand)) { + return false; + } + if (c.hasKeyword("May be played") && activator.equals(c.getController())) { + return true; + } + if (c.hasKeyword("May be played by your opponent") && !activator.equals(c.getController())) { + return true; + } + return false; + } + + return true; + } + + /** + *

+ * checkTimingRestrictions. + *

+ * @param c + * a {@link forge.game.card.Card} object. + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean. + */ + public final boolean checkTimingRestrictions(final Card c, final SpellAbility sa) { + Player activator = sa.getActivatingPlayer(); + final Game game = activator.getGame(); + + if (this.isPlayerTurn() && !game.getPhaseHandler().isPlayerTurn(activator)) { + return false; + } + + if (this.isOpponentTurn() && !game.getPhaseHandler().getPlayerTurn().isOpponentOf(activator)) { + return false; + } + + if (this.getPhases().size() > 0) { + boolean isPhase = false; + final PhaseType currPhase = game.getPhaseHandler().getPhase(); + for (final PhaseType s : this.getPhases()) { + if (s == currPhase) { + isPhase = true; + break; + } + } + + if (!isPhase) { + return false; + } + } + return true; + } + + /** + *

+ * checkActivatorRestrictions. + *

+ * @param c + * a {@link forge.game.card.Card} object. + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean. + */ + public final boolean checkActivatorRestrictions(final Card c, final SpellAbility sa) { + Player activator = sa.getActivatingPlayer(); + if (this.isAnyPlayer()) { + return true; + } + + if (this.isOwnerOnly()) { + return activator.equals(c.getOwner()); + } + + if (activator.equals(c.getController()) && !this.isOpponentOnly()) { + return true; + } + + if (activator.isOpponentOf(c.getController()) && this.isOpponentOnly()) { + return true; + } + if (sa.isSpell() && activator.isOpponentOf(c.getController()) && c.hasKeyword("May be played by your opponent")) { + return true; + } + + return false; + } + + /** + *

+ * canPlay. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean. + */ + public final boolean canPlay(final Card c, final SpellAbility sa) { + if (c.isPhasedOut() || c.isUsedToPay()) { + return false; + } + + Player activator = sa.getActivatingPlayer(); + if (activator == null) { + activator = c.getController(); + sa.setActivatingPlayer(activator); + System.out.println(c.getName() + " Did not have activator set in SpellAbilityRestriction.canPlay()"); + } + + final Game game = activator.getGame(); + + if (this.isSorcerySpeed() && !activator.canCastSorcery()) { + return false; + } + + if (!checkTimingRestrictions(c, sa)) { + return false; + } + + if (!checkActivatorRestrictions(c, sa)) { + return false; + } + + if (!checkZoneRestrictions(c, sa)) { + return false; + } + + if (this.getLimitToCheck() != null) { + String limit = this.getLimitToCheck(); + int activationLimit = AbilityUtils.calculateAmount(c, limit, sa); + this.setActivationLimit(activationLimit); + + if ((this.getActivationLimit() != -1) && (this.getNumberTurnActivations() >= this.getActivationLimit())) { + return false; + } + } + + if (this.getGameLimitToCheck() != null) { + String limit = this.getGameLimitToCheck(); + int gameActivationLimit = AbilityUtils.calculateAmount(c, limit, sa); + this.setGameActivationLimit(gameActivationLimit); + + if ((this.getGameActivationLimit() != -1) && (this.getNumberGameActivations() >= this.getGameActivationLimit())) { + return false; + } + } + + if (this.getCardsInHand() != -1) { + if (activator.getCardsIn(ZoneType.Hand).size() != this.getCardsInHand()) { + return false; + } + } + + if (this.getColorToCheck() != null) { + if (!sa.getSourceCard().getChosenColor().contains(this.getColorToCheck())) { + return false; + } + } + if (this.isHellbent()) { + if (!activator.hasHellbent()) { + return false; + } + } + if (this.isThreshold()) { + if (!activator.hasThreshold()) { + return false; + } + } + if (this.isMetalcraft()) { + if (!activator.hasMetalcraft()) { + return false; + } + } + if (this.getProwlTypes() != null && !this.getProwlTypes().isEmpty()) { + // only true if the activating player has damaged the opponent with + // one of the specified types + boolean prowlFlag = false; + for (final String type : this.getProwlTypes()) { + if (activator.hasProwl(type)) { + prowlFlag = true; + } + } + if (!prowlFlag) { + return false; + } + } + if (this.getIsPresent() != null) { + List list = game.getCardsIn(this.getPresentZone()); + + list = CardLists.getValidCards(list, this.getIsPresent().split(","), activator, c); + + int right = 1; + final String rightString = this.getPresentCompare().substring(2); + right = AbilityUtils.calculateAmount(c, rightString, sa); + final int left = list.size(); + + if (!Expressions.compare(left, this.getPresentCompare(), right)) { + return false; + } + } + + if (this.getLifeTotal() != null) { + int life = 1; + if (this.getLifeTotal().equals("You")) { + life = activator.getLife(); + } + if (this.getLifeTotal().equals("Opponent")) { + life = activator.getOpponent().getLife(); + } + + int right = 1; + final String rightString = this.getLifeAmount().substring(2); + if (rightString.equals("X")) { + right = CardFactoryUtil.xCount(sa.getSourceCard(), sa.getSourceCard().getSVar("X")); + } else { + right = Integer.parseInt(this.getLifeAmount().substring(2)); + } + + if (!Expressions.compare(life, this.getLifeAmount(), right)) { + return false; + } + } + + if (this.isPwAbility()) { + + for (final SpellAbility pwAbs : c.getAllSpellAbilities()) { + // check all abilities on card that have their planeswalker + // restriction set to confirm they haven't been activated + final SpellAbilityRestriction restrict = pwAbs.getRestrictions(); + if (restrict.getPlaneswalker() && (restrict.getNumberTurnActivations() > 0)) { + return false; + } + } + } + + if (this.getsVarToCheck() != null) { + final int svarValue = AbilityUtils.calculateAmount(c, this.getsVarToCheck(), sa); + final int operandValue = AbilityUtils.calculateAmount(c, this.getsVarOperand(), sa); + + if (!Expressions.compare(svarValue, this.getsVarOperator(), operandValue)) { + return false; + } + + } + + if (this.getsVarToCheck() != null) { + final int svarValue = AbilityUtils.calculateAmount(sa.getSourceCard(), this.getsVarToCheck(), sa); + final int operandValue = AbilityUtils.calculateAmount(sa.getSourceCard(), this.getsVarOperand(), sa); + + if (!Expressions.compare(svarValue, this.getsVarOperator(), operandValue)) { + return false; + } + + } + + return true; + } // canPlay() + +} // end class SpellAbilityRestriction diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java b/forge-gui/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java similarity index 95% rename from forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java rename to forge-gui/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java index 85e86c5d435..ea4589387d8 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java +++ b/forge-gui/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java @@ -1,305 +1,305 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - -import forge.game.card.Card; -import forge.game.player.Player; -import forge.game.trigger.TriggerType; - -/** - *

- * SpellAbility_StackInstance class. - *

- * - * @author Forge - * @version $Id: SpellAbilityStackInstance.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class SpellAbilityStackInstance { - // At some point I want this functioning more like Target/Target Choices - // where the SA has an "active" - // Stack Instance, and instead of having duplicate parameters, it adds - // changes directly to the "active" one - // When hitting the Stack, the active SI gets "applied" to the Stack and - // gets cleared from the base SI - // Coming off the Stack would work similarly, except it would just add the - // full active SI instead of each of the parts - /** The ability. */ - private SpellAbility ability = null; - - /** The sub instace. */ - private SpellAbilityStackInstance subInstace = null; - private final Player activator; - - // When going to a SubAbility that SA has a Instance Choice object - /** The tc. */ - private TargetChoices tc = new TargetChoices(); - private List splicedCards = null; - - /** The stack description. */ - private String stackDescription = null; - - // Adjusted Mana Cost - // private String adjustedManaCost = ""; - - // Paid Mana Cost - // private ArrayList payingMana = new ArrayList(); - // private ArrayList paidAbilities = new - // ArrayList(); - private int xManaPaid = 0; - - // Other Paid things - private HashMap> paidHash = new HashMap>(); - - // Additional info - // is Kicked, is Buyback - - // Triggers - private HashMap triggeringObjects = new HashMap(); - - private final HashMap storedSVars = new HashMap(); - - /** - *

- * Constructor for SpellAbility_StackInstance. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - */ - public SpellAbilityStackInstance(final SpellAbility sa) { - // Base SA info - this.ability = sa; - this.stackDescription = this.ability.getStackDescription(); - this.activator = sa.getActivatingPlayer(); - - // Payment info - this.paidHash = this.ability.getPaidHash(); - this.ability.resetPaidHash(); - this.splicedCards = sa.getSplicedCards(); - - // TODO getXManaCostPaid should be on the SA, not the Card - this.xManaPaid = sa.getSourceCard().getXManaCostPaid(); - - // Triggering info - this.triggeringObjects = sa.getTriggeringObjects(); - - final AbilitySub subAb = this.ability.getSubAbility(); - if (subAb != null) { - this.subInstace = new SpellAbilityStackInstance(subAb); - } - - // Targeting info -- 29/06/11 Moved to after taking care of SubAbilities - // because otherwise AF_DealDamage SubAbilities that use Defined$ - // Targeted breaks (since it's parents target is reset) - if (sa.usesTargeting()) { - this.tc = ability.getTargets(); - this.ability.resetTargets(); - } - - final Card source = this.ability.getSourceCard(); - - // Store SVars and Clear - for (final String store : Card.getStorableSVars()) { - final String value = source.getSVar(store); - if (value.length() > 0) { - this.storedSVars.put(store, value); - source.setSVar(store, ""); - } - } - } - - /** - *

- * getSpellAbility. - *

- * - * @return a {@link forge.game.spellability.SpellAbility} object. - */ - public final SpellAbility getSpellAbility() { - this.ability.resetTargets(); - this.ability.setTargets(tc); - this.ability.setActivatingPlayer(activator); - - // Saved sub-SA needs to be reset on the way out - if (this.subInstace != null) { - this.ability.setSubAbility((AbilitySub) this.subInstace.getSpellAbility()); - } - - // Set Cost specific things here - this.ability.resetPaidHash(); - this.ability.setPaidHash(this.paidHash); - this.ability.setSplicedCards(splicedCards); - this.ability.getSourceCard().setXManaCostPaid(this.xManaPaid); - - // Triggered - this.ability.setAllTriggeringObjects(this.triggeringObjects); - - // Add SVars back in - final Card source = this.ability.getSourceCard(); - for (final String store : this.storedSVars.keySet()) { - final String value = this.storedSVars.get(store); - if (value.length() > 0) { - source.setSVar(store, value); - } - } - - return this.ability; - } - - // A bit of SA shared abilities to restrict conflicts - /** - *

- * Getter for the field stackDescription. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getStackDescription() { - return this.stackDescription; - } - - /** - *

- * getSourceCard. - *

- * - * @return a {@link forge.game.card.Card} object. - */ - public final Card getSourceCard() { - return this.ability.getSourceCard(); - } - - /** - *

- * isSpell. - *

- * - * @return a boolean. - */ - public final boolean isSpell() { - return this.ability.isSpell(); - } - - /** - *

- * isAbility. - *

- * - * @return a boolean. - */ - public final boolean isAbility() { - return this.ability.isAbility(); - } - - /** - *

- * isTrigger. - *

- * - * @return a boolean. - */ - public final boolean isTrigger() { - return this.ability.isTrigger(); - } - - /** - *

- * isStateTrigger. - *

- * - * @param id - * a int. - * @return a boolean. - */ - public final boolean isStateTrigger(final int id) { - return this.ability.getSourceTrigger() == id; - } - - /** - * Checks if is optional trigger. - * - * @return true, if is optional trigger - */ - public final boolean isOptionalTrigger() { - return this.ability.isOptionalTrigger(); - } - - public final SpellAbilityStackInstance getSubInstace() { - return this.subInstace; - } - - public final TargetChoices getTargetChoices() { - return this.tc; - } - - public void updateTarget(TargetChoices target) { - if (target != null) { - this.tc = target; - this.ability.setTargets(tc); - this.stackDescription = this.ability.getStackDescription(); - // Run BecomesTargetTrigger - HashMap runParams = new HashMap(); - runParams.put("SourceSA", this.ability); - HashSet distinctObjects = new HashSet(); - for (final Object tgt : target.getTargets()) { - if (distinctObjects.contains(tgt)) { - continue; - } - distinctObjects.add(tgt); - if (tgt instanceof Card && !((Card) tgt).hasBecomeTargetThisTurn()) { - runParams.put("FirstTime", null); - ((Card) tgt).setBecameTargetThisTurn(true); - } - runParams.put("Target", tgt); - this.getSourceCard().getGame().getTriggerHandler().runTrigger(TriggerType.BecomesTarget, runParams, false); - } - } - } - - public boolean compareToSpellAbility(SpellAbility sa) { - // Compare my target choices to the SA passed in - // TODO? Compare other data points in the SI to the passed SpellAbility for confirmation - SpellAbility compare = sa; - SpellAbilityStackInstance sub = this; - - if (!compare.equals(sub.ability)){ - return false; - } - - while (compare != null && sub != null) { - TargetChoices choices = compare.getTargetRestrictions() != null ? compare.getTargets() : null; - - if (choices != null && !choices.equals(sub.getTargetChoices())) { - return false; - } - compare = compare.getSubAbility(); - sub = sub.getSubInstace(); - } - - return true; - } - - @Override - public String toString() { - return String.format("%s->%s", getSourceCard(), stackDescription); - } -} +/* + * 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 . + */ +package forge.game.spellability; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.trigger.TriggerType; + +/** + *

+ * SpellAbility_StackInstance class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class SpellAbilityStackInstance { + // At some point I want this functioning more like Target/Target Choices + // where the SA has an "active" + // Stack Instance, and instead of having duplicate parameters, it adds + // changes directly to the "active" one + // When hitting the Stack, the active SI gets "applied" to the Stack and + // gets cleared from the base SI + // Coming off the Stack would work similarly, except it would just add the + // full active SI instead of each of the parts + /** The ability. */ + private SpellAbility ability = null; + + /** The sub instace. */ + private SpellAbilityStackInstance subInstace = null; + private final Player activator; + + // When going to a SubAbility that SA has a Instance Choice object + /** The tc. */ + private TargetChoices tc = new TargetChoices(); + private List splicedCards = null; + + /** The stack description. */ + private String stackDescription = null; + + // Adjusted Mana Cost + // private String adjustedManaCost = ""; + + // Paid Mana Cost + // private ArrayList payingMana = new ArrayList(); + // private ArrayList paidAbilities = new + // ArrayList(); + private int xManaPaid = 0; + + // Other Paid things + private HashMap> paidHash = new HashMap>(); + + // Additional info + // is Kicked, is Buyback + + // Triggers + private HashMap triggeringObjects = new HashMap(); + + private final HashMap storedSVars = new HashMap(); + + /** + *

+ * Constructor for SpellAbility_StackInstance. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + */ + public SpellAbilityStackInstance(final SpellAbility sa) { + // Base SA info + this.ability = sa; + this.stackDescription = this.ability.getStackDescription(); + this.activator = sa.getActivatingPlayer(); + + // Payment info + this.paidHash = this.ability.getPaidHash(); + this.ability.resetPaidHash(); + this.splicedCards = sa.getSplicedCards(); + + // TODO getXManaCostPaid should be on the SA, not the Card + this.xManaPaid = sa.getSourceCard().getXManaCostPaid(); + + // Triggering info + this.triggeringObjects = sa.getTriggeringObjects(); + + final AbilitySub subAb = this.ability.getSubAbility(); + if (subAb != null) { + this.subInstace = new SpellAbilityStackInstance(subAb); + } + + // Targeting info -- 29/06/11 Moved to after taking care of SubAbilities + // because otherwise AF_DealDamage SubAbilities that use Defined$ + // Targeted breaks (since it's parents target is reset) + if (sa.usesTargeting()) { + this.tc = ability.getTargets(); + this.ability.resetTargets(); + } + + final Card source = this.ability.getSourceCard(); + + // Store SVars and Clear + for (final String store : Card.getStorableSVars()) { + final String value = source.getSVar(store); + if (value.length() > 0) { + this.storedSVars.put(store, value); + source.setSVar(store, ""); + } + } + } + + /** + *

+ * getSpellAbility. + *

+ * + * @return a {@link forge.game.spellability.SpellAbility} object. + */ + public final SpellAbility getSpellAbility() { + this.ability.resetTargets(); + this.ability.setTargets(tc); + this.ability.setActivatingPlayer(activator); + + // Saved sub-SA needs to be reset on the way out + if (this.subInstace != null) { + this.ability.setSubAbility((AbilitySub) this.subInstace.getSpellAbility()); + } + + // Set Cost specific things here + this.ability.resetPaidHash(); + this.ability.setPaidHash(this.paidHash); + this.ability.setSplicedCards(splicedCards); + this.ability.getSourceCard().setXManaCostPaid(this.xManaPaid); + + // Triggered + this.ability.setAllTriggeringObjects(this.triggeringObjects); + + // Add SVars back in + final Card source = this.ability.getSourceCard(); + for (final String store : this.storedSVars.keySet()) { + final String value = this.storedSVars.get(store); + if (value.length() > 0) { + source.setSVar(store, value); + } + } + + return this.ability; + } + + // A bit of SA shared abilities to restrict conflicts + /** + *

+ * Getter for the field stackDescription. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getStackDescription() { + return this.stackDescription; + } + + /** + *

+ * getSourceCard. + *

+ * + * @return a {@link forge.game.card.Card} object. + */ + public final Card getSourceCard() { + return this.ability.getSourceCard(); + } + + /** + *

+ * isSpell. + *

+ * + * @return a boolean. + */ + public final boolean isSpell() { + return this.ability.isSpell(); + } + + /** + *

+ * isAbility. + *

+ * + * @return a boolean. + */ + public final boolean isAbility() { + return this.ability.isAbility(); + } + + /** + *

+ * isTrigger. + *

+ * + * @return a boolean. + */ + public final boolean isTrigger() { + return this.ability.isTrigger(); + } + + /** + *

+ * isStateTrigger. + *

+ * + * @param id + * a int. + * @return a boolean. + */ + public final boolean isStateTrigger(final int id) { + return this.ability.getSourceTrigger() == id; + } + + /** + * Checks if is optional trigger. + * + * @return true, if is optional trigger + */ + public final boolean isOptionalTrigger() { + return this.ability.isOptionalTrigger(); + } + + public final SpellAbilityStackInstance getSubInstace() { + return this.subInstace; + } + + public final TargetChoices getTargetChoices() { + return this.tc; + } + + public void updateTarget(TargetChoices target) { + if (target != null) { + this.tc = target; + this.ability.setTargets(tc); + this.stackDescription = this.ability.getStackDescription(); + // Run BecomesTargetTrigger + HashMap runParams = new HashMap(); + runParams.put("SourceSA", this.ability); + HashSet distinctObjects = new HashSet(); + for (final Object tgt : target.getTargets()) { + if (distinctObjects.contains(tgt)) { + continue; + } + distinctObjects.add(tgt); + if (tgt instanceof Card && !((Card) tgt).hasBecomeTargetThisTurn()) { + runParams.put("FirstTime", null); + ((Card) tgt).setBecameTargetThisTurn(true); + } + runParams.put("Target", tgt); + this.getSourceCard().getGame().getTriggerHandler().runTrigger(TriggerType.BecomesTarget, runParams, false); + } + } + } + + public boolean compareToSpellAbility(SpellAbility sa) { + // Compare my target choices to the SA passed in + // TODO? Compare other data points in the SI to the passed SpellAbility for confirmation + SpellAbility compare = sa; + SpellAbilityStackInstance sub = this; + + if (!compare.equals(sub.ability)){ + return false; + } + + while (compare != null && sub != null) { + TargetChoices choices = compare.getTargetRestrictions() != null ? compare.getTargets() : null; + + if (choices != null && !choices.equals(sub.getTargetChoices())) { + return false; + } + compare = compare.getSubAbility(); + sub = sub.getSubInstace(); + } + + return true; + } + + @Override + public String toString() { + return String.format("%s->%s", getSourceCard(), stackDescription); + } +} diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java b/forge-gui/src/main/java/forge/game/spellability/SpellAbilityVariables.java similarity index 95% rename from forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java rename to forge-gui/src/main/java/forge/game/spellability/SpellAbilityVariables.java index 7022b7df1d7..d530e2f1dd7 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java +++ b/forge-gui/src/main/java/forge/game/spellability/SpellAbilityVariables.java @@ -1,1070 +1,1070 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import java.util.ArrayList; -import java.util.List; - -import forge.game.phase.PhaseType; -import forge.game.zone.ZoneType; - -/** - *

- * SpellAbilityVariables class. - *

- * - * @author Forge - * @version $Id: SpellAbilityVariables.java 23787 2013-11-24 07:09:23Z Max mtg $ - * @since 1.0.15 - */ -public class SpellAbilityVariables { - // A class for handling SpellAbility Variables. These restrictions include: - // Zone, Phase, OwnTurn, Speed (instant/sorcery), Amount per Turn, Player, - // Threshold, Metalcraft, Hellbent, LevelRange, etc - // Each value will have a default, that can be overridden (mostly by - // AbilityFactory) - - /** - *

- * Constructor for SpellAbility_Variables. - *

- */ - public SpellAbilityVariables() { - } - - /** - * - * @param sav - * SpellAbilityVariables - */ - public void setVariables(SpellAbilityVariables sav) { - this.zone = sav.getZone(); - this.phases = new ArrayList(sav.getPhases()); - this.sorcerySpeed = sav.isSorcerySpeed(); - this.instantSpeed = sav.isInstantSpeed(); - this.anyPlayer = sav.isAnyPlayer(); - this.opponentOnly = sav.isOpponentOnly(); - this.ownerOnly = sav.isOwnerOnly(); - this.opponentTurn = sav.isOpponentTurn(); - this.playerTurn = sav.isPlayerTurn(); - this.activationLimit = sav.getActivationLimit(); - this.gameActivationLimit = sav.getGameActivationLimit(); - this.numberTurnActivations = sav.getNumberTurnActivations(); - this.numberGameActivations = sav.getNumberGameActivations(); - this.activationNumberSacrifice = sav.getActivationNumberSacrifice(); - this.cardsInHand = sav.getCardsInHand(); - this.chosenColors = sav.getColorToCheck(); - this.threshold = sav.isThreshold(); - this.metalcraft = sav.isThreshold(); - this.hellbent = sav.isHellbent(); - this.allTargetsLegal = sav.isAllTargetsLegal(); - this.prowlTypes = new ArrayList(sav.getProwlTypes()); - this.isPresent = sav.getIsPresent(); - this.presentCompare = sav.getPresentCompare(); - this.presentDefined = sav.getPresentDefined(); - this.playerDefined = sav.getPlayerDefined(); - this.playerContains = sav.getPlayerContains(); - this.presentZone = sav.getPresentZone(); - this.sVarToCheck = sav.getsVarToCheck(); - this.sVarOperator = sav.getsVarOperator(); - this.sVarOperand = sav.getsVarOperand(); - this.lifeTotal = sav.getLifeTotal(); - this.lifeAmount = sav.getLifeAmount(); - this.manaSpent = sav.getManaSpent(); - this.targetValidTargeting = sav.getTargetValidTargeting(); - this.targetsSingleTarget = sav.targetsSingleTarget(); - this.pwAbility = sav.isPwAbility(); - } - - // default values for Sorcery speed abilities - /** The zone. */ - private ZoneType zone = ZoneType.Battlefield; - - /** The phases. */ - private List phases = new ArrayList(); - - /** The b sorcery speed. */ - private boolean sorcerySpeed = false; - - /** The b instant speed. */ - private boolean instantSpeed = false; - - /** The b any player. */ - private boolean anyPlayer = false; - - /** The b opponent only. */ - private boolean opponentOnly = false; - - /** The b owner only. */ - private boolean ownerOnly = false; - - /** The b opponent turn. */ - private boolean opponentTurn = false; - - /** The b player turn. */ - private boolean playerTurn = false; - - /** The activation limit. */ - private int activationLimit = -1; - - /** The game activation limit. */ - private int gameActivationLimit = -1; - - /** The limitToCheck to check. */ - private String limitToCheck = null; - - /** The gameLimitToCheck to check. */ - private String gameLimitToCheck = null; - - /** The number turn activations. */ - private int numberTurnActivations = 0; - - /** The number game activations. */ - private int numberGameActivations = 0; - - /** The activation number sacrifice. */ - private int activationNumberSacrifice = -1; - - /** The n cards in hand. */ - private int cardsInHand = -1; - - /** The threshold. */ - private boolean threshold = false; - - /** The metalcraft. */ - private boolean metalcraft = false; - - /** The hellbent. */ - private boolean hellbent = false; - - private boolean allTargetsLegal = false; - - /** The prowl. */ - private ArrayList prowlTypes = new ArrayList(); - - /** The s is present. */ - private String isPresent = null; - - /** The present compare. */ - private String presentCompare = "GE1"; // Default Compare to Greater or - // Equal to 1 - - /** The present defined. */ - private String presentDefined = null; - - /** The player defined. */ - private String playerDefined = null; - - /** The player contains. */ - private String playerContains = null; - - /** The present zone. */ - private ZoneType presentZone = ZoneType.Battlefield; - - /** The svar to check. */ - private String sVarToCheck = null; - - /** The svar operator. */ - private String sVarOperator = "GE"; - - /** The svar operand. */ - private String sVarOperand = "1"; - - /** The life total. */ - private String lifeTotal = null; - - /** The life amount. */ - private String lifeAmount = "GE1"; - - /** The mana spent. */ - private String manaSpent = ""; - - /** The pw ability. */ - private boolean pwAbility = false; - - /** The chosen colors string. */ - private String chosenColors = null; - - /** The target valid targeting */ - private String targetValidTargeting = null; - - /** The b targetsSingleTargeting */ - private boolean targetsSingleTarget = false; - - /** - *

- * Setter for the field manaSpent. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void setManaSpent(final String s) { - this.manaSpent = s; - } - - /** - *

- * Getter for the field manaSpent. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getManaSpent() { - return this.manaSpent; - } - - /** - *

- * Setter for the field zone. - *

- * - * @param zone - * a {@link java.lang.String} object. - */ - public final void setZone(final ZoneType zone) { - this.zone = zone; - } - - /** - *

- * Getter for the field zone. - *

- * - * @return a {@link java.lang.String} object. - */ - public final ZoneType getZone() { - return this.zone; - } - - /** - *

- * setSorcerySpeed. - *

- * - * @param bSpeed - * a boolean. - */ - public final void setSorcerySpeed(final boolean bSpeed) { - this.sorcerySpeed = bSpeed; - } - - /** - *

- * getSorcerySpeed. - *

- * - * @return a boolean. - */ - public final boolean isSorcerySpeed() { - return this.sorcerySpeed; - } - - /** - *

- * setInstantSpeed. - *

- * - * @param bSpeed - * a boolean. - */ - public final void setInstantSpeed(final boolean bSpeed) { - this.instantSpeed = bSpeed; - } - - /** - *

- * getInstantSpeed. - *

- * - * @return a boolean. - */ - public final boolean isInstantSpeed() { - return this.instantSpeed; - } - - /** - *

- * setAnyPlayer. - *

- * - * @param anyPlayer - * a boolean. - */ - public final void setAnyPlayer(final boolean anyPlayer) { - this.anyPlayer = anyPlayer; - } - - /** - *

- * setPlayerTurn. - *

- * - * @param bTurn - * a boolean. - */ - public final void setPlayerTurn(final boolean bTurn) { - this.playerTurn = bTurn; - } - - /** - *

- * getPlayerTurn. - *

- * - * @return a boolean. - */ - public final boolean getPlayerTurn() { - return this.isPlayerTurn(); - } - - /** - *

- * setOpponentTurn. - *

- * - * @param bTurn - * a boolean. - */ - public final void setOpponentTurn(final boolean bTurn) { - this.opponentTurn = bTurn; - } - - /** - *

- * getOpponentTurn. - *

- * - * @return a boolean. - */ - public final boolean getOpponentTurn() { - return this.isOpponentTurn(); - } - - /** - *

- * Setter for the field activationLimit. - *

- * - * @param limit - * a int. - */ - public final void setActivationLimit(final int limit) { - this.activationLimit = limit; - } - - /** - *

- * Setter for the field gameActivationLimit. - *

- * - * @param limit - * a int. - */ - public final void setGameActivationLimit(final int limit) { - this.gameActivationLimit = limit; - } - - /** - *

- * abilityActivated. - *

- */ - public final void abilityActivated() { - this.numberTurnActivations++; - this.numberGameActivations++; - } - - /** - *

- * Getter for the field numberTurnActivations. - *

- * - * @return a int. - */ - public final int getNumberTurnActivations() { - return this.numberTurnActivations; - } - - /** - *

- * Getter for the field numberTurnActivations. - *

- * - * @return a int. - */ - public final int getNumberGameActivations() { - return this.numberGameActivations; - } - - /** - *

- * resetTurnActivations. - *

- */ - public final void resetTurnActivations() { - this.numberTurnActivations = 0; - } - - /** - *

- * Setter for the field activationNumberSacrifice. - *

- * - * @param num - * a int. - */ - public final void setActivationNumberSacrifice(final int num) { - this.activationNumberSacrifice = num; - } - - /** - *

- * Getter for the field activationNumberSacrifice. - *

- * - * @return a int. - */ - public final int getActivationNumberSacrifice() { - return this.activationNumberSacrifice; - } - - /** - *

- * Setter for the field phases. - *

- * - * @param phases - * a {@link java.lang.String} object. - */ - public final void setPhases(final List phases) { - this.phases.addAll(phases); - } - - /** - *

- * setActivateCardsInHand. - *

- * - * @param cards - * a int. - */ - public final void setActivateCardsInHand(final int cards) { - this.setCardsInHand(cards); - } - - // specific named conditions - /** - *

- * Setter for the field hellbent. - *

- * - * @param bHellbent - * a boolean. - */ - public final void setHellbent(final boolean bHellbent) { - this.hellbent = bHellbent; - } - - /** - *

- * Setter for the field threshold. - *

- * - * @param bThreshold - * a boolean. - */ - public final void setThreshold(final boolean bThreshold) { - this.threshold = bThreshold; - } - - /** - *

- * Setter for the field metalcraft. - *

- * - * @param bMetalcraft - * a boolean. - */ - public final void setMetalcraft(final boolean bMetalcraft) { - this.metalcraft = bMetalcraft; - } - - /** The Kicked. */ - protected boolean kicked = false; - protected boolean kicked1 = false; // http://magiccards.info/query?q=o%3A%22kicker%22+not+o%3A%22multikicker%22+o%3A%22and%2For+{%22 - protected boolean kicked2 = false; // Some spells have 2 kickers with different effects - protected boolean altCostPaid = false; - - /** - * @return the allTargetsLegal - */ - public boolean isAllTargetsLegal() { - return allTargetsLegal; - } - - /** - * @param allTargetsLegal0 the allTargetsLegal to set - */ - public void setAllTargetsLegal(boolean allTargets) { - this.allTargetsLegal = allTargets; - } - - - /** - *

- * Setter for the field prowl. - *

- * - * @param types - * the new prowl - */ - public final void setProwlTypes(final ArrayList types) { - this.prowlTypes = types; - } - - // IsPresent for Valid battlefield stuff - - /** - *

- * setIsPresent. - *

- * - * @param present - * a {@link java.lang.String} object. - */ - public final void setIsPresent(final String present) { - this.isPresent = present; - } - - /** - *

- * Setter for the field presentCompare. - *

- * - * @param compare - * a {@link java.lang.String} object. - */ - public final void setPresentCompare(final String compare) { - this.presentCompare = compare; - } - - /** - * Gets the present zone. - * - * @return the present zone - */ - public final ZoneType getPresentZone() { - return this.presentZone; - } - - /** - * Sets the present zone. - * - * @param presentZone - * the new present zone - */ - public final void setPresentZone(final ZoneType presentZone) { - this.presentZone = presentZone; - } - - /** - *

- * Setter for the field presentDefined. - *

- * - * @param defined - * a {@link java.lang.String} object. - */ - public final void setPresentDefined(final String defined) { - this.presentDefined = defined; - } - - // used to define as a Planeswalker ability - /** - *

- * setPlaneswalker. - *

- * - * @param bPlaneswalker - * a boolean. - */ - public final void setPlaneswalker(final boolean bPlaneswalker) { - this.setPwAbility(bPlaneswalker); - } - - /** - *

- * getPlaneswalker. - *

- * - * @return a boolean. - */ - public final boolean getPlaneswalker() { - return this.isPwAbility(); - } - - // Checking the values of SVars (Mostly for Traps) - /** - *

- * Setter for the field svarToCheck. - *

- * - * @param sVar - * a {@link java.lang.String} object. - */ - public final void setSvarToCheck(final String sVar) { - this.setsVarToCheck(sVar); - } - - /** - *

- * Setter for the field svarOperator. - *

- * - * @param operator - * a {@link java.lang.String} object. - */ - public final void setSvarOperator(final String operator) { - this.setsVarOperator(operator); - } - - /** - *

- * Setter for the field svarOperand. - *

- * - * @param operand - * a {@link java.lang.String} object. - */ - public final void setSvarOperand(final String operand) { - this.setsVarOperand(operand); - } - - /** - * Gets the activation limit. - * - * @return the activationLimit - */ - public final int getActivationLimit() { - return this.activationLimit; - } - - /** - * Gets the activation limit. - * - * @return the activationLimit - */ - public final int getGameActivationLimit() { - return this.gameActivationLimit; - } - - /** - *

- * Setter for the field limitToCheck. - *

- * - * @param limit - * a {@link java.lang.String} object. - */ - public final void setLimitToCheck(final String limit) { - this.limitToCheck = limit; - } - - /** - *

- * Setter for the field GamelimitToCheck. - *

- * - * @param limit - * a {@link java.lang.String} object. - */ - public final void setGameLimitToCheck(final String limit) { - this.gameLimitToCheck = limit; - } - - /** - *

- * Getter for the field limitToCheck. - *

- * - * @return the limitToCheck - * a {@link java.lang.String} object. - */ - public final String getLimitToCheck() { - return this.limitToCheck; - } - - /** - *

- * Getter for the field getGameLimitToCheck. - *

- * - * @return the getGameLimitToCheck - * a {@link java.lang.String} object. - */ - public final String getGameLimitToCheck() { - return this.gameLimitToCheck; - } - - /** - * Checks if is threshold. - * - * @return the threshold - */ - public final boolean isThreshold() { - return this.threshold; - } - - /** - * Checks if is metalcraft. - * - * @return the metalcraft - */ - public final boolean isMetalcraft() { - return this.metalcraft; - } - - /** - * Checks if is hellbent. - * - * @return the hellbent - */ - public final boolean isHellbent() { - return this.hellbent; - } - - /** - * Checks if is pw ability. - * - * @return the pwAbility - */ - public final boolean isPwAbility() { - return this.pwAbility; - } - - /** - * Sets the pw ability. - * - * @param pwAbility0 - * the new pw ability - */ - public final void setPwAbility(final boolean pwAbility0) { - this.pwAbility = pwAbility0; - } - - /** - * Checks if is player turn. - * - * @return the playerTurn - */ - public final boolean isPlayerTurn() { - return this.playerTurn; - } - - /** - * Gets the prowl. - * - * @return the prowl - */ - public final ArrayList getProwlTypes() { - return this.prowlTypes; - } - - /** - * Gets the present compare. - * - * @return the presentCompare - */ - public final String getPresentCompare() { - return this.presentCompare; - } - - /** - * Gets the life total. - * - * @return the lifeTotal - */ - public final String getLifeTotal() { - return this.lifeTotal; - } - - /** - * Sets the life total. - * - * @param lifeTotal0 - * the lifeTotal to set - */ - public final void setLifeTotal(final String lifeTotal0) { - this.lifeTotal = lifeTotal0; - } - - /** - * Gets the life amount. - * - * @return the lifeAmount - */ - public final String getLifeAmount() { - return this.lifeAmount; - } - - /** - * Sets the life amount. - * - * @param lifeAmount0 - * the lifeAmount to set - */ - public final void setLifeAmount(final String lifeAmount0) { - this.lifeAmount = lifeAmount0; - } - - /** - * Gets the phases. - * - * @return the phases - */ - public final List getPhases() { - return this.phases; - } - - - /** - * Gets the present defined. - * - * @return the presentDefined - */ - public final String getPresentDefined() { - return this.presentDefined; - } - - /** - * Set the player defined. - * - */ - public final void setPlayerDefined(final String b) { - this.playerDefined = b; - } - - /** - * Gets the player defined. - * - * @return the playerDefined - */ - public final String getPlayerDefined() { - return this.playerDefined; - } - - /** - * Gets the player contains. - * - * @return the playerContains - */ - public final String getPlayerContains() { - return this.playerContains; - } - - /** - * Set the player contains. - * - */ - public final void setPlayerContains(final String contains) { - this.playerContains = contains; - } - - /** - * Gets the s var operand. - * - * @return the sVarOperand - */ - public final String getsVarOperand() { - return this.sVarOperand; - } - - /** - * Sets the s var operand. - * - * @param sVarOperand0 - * the sVarOperand to set - */ - public final void setsVarOperand(final String sVarOperand0) { - this.sVarOperand = sVarOperand0; - } - - /** - * Gets the s var to check. - * - * @return the sVarToCheck - */ - public final String getsVarToCheck() { - return this.sVarToCheck; - } - - /** - * Sets the s var to check. - * - * @param sVarToCheck - * the sVarToCheck to set - */ - public final void setsVarToCheck(final String sVarToCheck) { - this.sVarToCheck = sVarToCheck; - } - - /** - * Gets the s var operator. - * - * @return the sVarOperator - */ - public final String getsVarOperator() { - return this.sVarOperator; - } - - /** - * Sets the s var operator. - * - * @param sVarOperator0 - * the sVarOperator to set - */ - public final void setsVarOperator(final String sVarOperator0) { - this.sVarOperator = sVarOperator0; - } - - /** - * Checks if is opponent turn. - * - * @return the opponentTurn - */ - public final boolean isOpponentTurn() { - return this.opponentTurn; - } - - /** - * Gets the cards in hand. - * - * @return the cardsInHand - */ - public final int getCardsInHand() { - return this.cardsInHand; - } - - /** - * Sets the cards in hand. - * - * @param cardsInHand0 - * the cardsInHand to set - */ - public final void setCardsInHand(final int cardsInHand0) { - this.cardsInHand = cardsInHand0; - } - - /** - * Gets the checks if is present. - * - * @return the isPresent - */ - public final String getIsPresent() { - return this.isPresent; - } - - /** - * Checks if is any player. - * - * @return the anyPlayer - */ - public final boolean isAnyPlayer() { - return this.anyPlayer; - } - - /** - * @return the opponentOnly - */ - public boolean isOpponentOnly() { - return opponentOnly; - } - - /** - * @param opponentOnly the opponentOnly to set - */ - public void setOpponentOnly(boolean opponentOnly) { - this.opponentOnly = opponentOnly; - } - - /** - * @return the ownerOnly - */ - public boolean isOwnerOnly() { - return ownerOnly; - } - - /** - * @param ownerOnly the ownerOnly to set - */ - public void setOwnerOnly(boolean ownerOnly) { - this.ownerOnly = ownerOnly; - } - /** - *

- * Setter for the field ColorToCheck. - *

- * - * @param s - * a {@link java.lang.String} object. - */ - public final void setColorToCheck(final String s) { - this.chosenColors = s; - } - - /** - *

- * Getter for the field ColorToCheck. - *

- * - * @return the String, chosenColors. - */ - public final String getColorToCheck() { - return this.chosenColors; - } - - /** - * @return the targetValidTargeting - */ - public String getTargetValidTargeting() { - return targetValidTargeting; - } - - /** - * @param targetValidTargeting the targetValidTargeting to set - */ - public void setTargetValidTargeting(String targetValidTargeting) { - this.targetValidTargeting = targetValidTargeting; - } - - /** - * @return the targetsSingleTarget - */ - public boolean targetsSingleTarget() { - return targetsSingleTarget; - } - - /** - * @param b the targetsSingleTarget to set - */ - public void setTargetsSingleTarget(boolean b) { - this.targetsSingleTarget = b; - } - -} // end class SpellAbilityVariables +/* + * 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 . + */ +package forge.game.spellability; + +import java.util.ArrayList; +import java.util.List; + +import forge.game.phase.PhaseType; +import forge.game.zone.ZoneType; + +/** + *

+ * SpellAbilityVariables class. + *

+ * + * @author Forge + * @version $Id$ + * @since 1.0.15 + */ +public class SpellAbilityVariables { + // A class for handling SpellAbility Variables. These restrictions include: + // Zone, Phase, OwnTurn, Speed (instant/sorcery), Amount per Turn, Player, + // Threshold, Metalcraft, Hellbent, LevelRange, etc + // Each value will have a default, that can be overridden (mostly by + // AbilityFactory) + + /** + *

+ * Constructor for SpellAbility_Variables. + *

+ */ + public SpellAbilityVariables() { + } + + /** + * + * @param sav + * SpellAbilityVariables + */ + public void setVariables(SpellAbilityVariables sav) { + this.zone = sav.getZone(); + this.phases = new ArrayList(sav.getPhases()); + this.sorcerySpeed = sav.isSorcerySpeed(); + this.instantSpeed = sav.isInstantSpeed(); + this.anyPlayer = sav.isAnyPlayer(); + this.opponentOnly = sav.isOpponentOnly(); + this.ownerOnly = sav.isOwnerOnly(); + this.opponentTurn = sav.isOpponentTurn(); + this.playerTurn = sav.isPlayerTurn(); + this.activationLimit = sav.getActivationLimit(); + this.gameActivationLimit = sav.getGameActivationLimit(); + this.numberTurnActivations = sav.getNumberTurnActivations(); + this.numberGameActivations = sav.getNumberGameActivations(); + this.activationNumberSacrifice = sav.getActivationNumberSacrifice(); + this.cardsInHand = sav.getCardsInHand(); + this.chosenColors = sav.getColorToCheck(); + this.threshold = sav.isThreshold(); + this.metalcraft = sav.isThreshold(); + this.hellbent = sav.isHellbent(); + this.allTargetsLegal = sav.isAllTargetsLegal(); + this.prowlTypes = new ArrayList(sav.getProwlTypes()); + this.isPresent = sav.getIsPresent(); + this.presentCompare = sav.getPresentCompare(); + this.presentDefined = sav.getPresentDefined(); + this.playerDefined = sav.getPlayerDefined(); + this.playerContains = sav.getPlayerContains(); + this.presentZone = sav.getPresentZone(); + this.sVarToCheck = sav.getsVarToCheck(); + this.sVarOperator = sav.getsVarOperator(); + this.sVarOperand = sav.getsVarOperand(); + this.lifeTotal = sav.getLifeTotal(); + this.lifeAmount = sav.getLifeAmount(); + this.manaSpent = sav.getManaSpent(); + this.targetValidTargeting = sav.getTargetValidTargeting(); + this.targetsSingleTarget = sav.targetsSingleTarget(); + this.pwAbility = sav.isPwAbility(); + } + + // default values for Sorcery speed abilities + /** The zone. */ + private ZoneType zone = ZoneType.Battlefield; + + /** The phases. */ + private List phases = new ArrayList(); + + /** The b sorcery speed. */ + private boolean sorcerySpeed = false; + + /** The b instant speed. */ + private boolean instantSpeed = false; + + /** The b any player. */ + private boolean anyPlayer = false; + + /** The b opponent only. */ + private boolean opponentOnly = false; + + /** The b owner only. */ + private boolean ownerOnly = false; + + /** The b opponent turn. */ + private boolean opponentTurn = false; + + /** The b player turn. */ + private boolean playerTurn = false; + + /** The activation limit. */ + private int activationLimit = -1; + + /** The game activation limit. */ + private int gameActivationLimit = -1; + + /** The limitToCheck to check. */ + private String limitToCheck = null; + + /** The gameLimitToCheck to check. */ + private String gameLimitToCheck = null; + + /** The number turn activations. */ + private int numberTurnActivations = 0; + + /** The number game activations. */ + private int numberGameActivations = 0; + + /** The activation number sacrifice. */ + private int activationNumberSacrifice = -1; + + /** The n cards in hand. */ + private int cardsInHand = -1; + + /** The threshold. */ + private boolean threshold = false; + + /** The metalcraft. */ + private boolean metalcraft = false; + + /** The hellbent. */ + private boolean hellbent = false; + + private boolean allTargetsLegal = false; + + /** The prowl. */ + private ArrayList prowlTypes = new ArrayList(); + + /** The s is present. */ + private String isPresent = null; + + /** The present compare. */ + private String presentCompare = "GE1"; // Default Compare to Greater or + // Equal to 1 + + /** The present defined. */ + private String presentDefined = null; + + /** The player defined. */ + private String playerDefined = null; + + /** The player contains. */ + private String playerContains = null; + + /** The present zone. */ + private ZoneType presentZone = ZoneType.Battlefield; + + /** The svar to check. */ + private String sVarToCheck = null; + + /** The svar operator. */ + private String sVarOperator = "GE"; + + /** The svar operand. */ + private String sVarOperand = "1"; + + /** The life total. */ + private String lifeTotal = null; + + /** The life amount. */ + private String lifeAmount = "GE1"; + + /** The mana spent. */ + private String manaSpent = ""; + + /** The pw ability. */ + private boolean pwAbility = false; + + /** The chosen colors string. */ + private String chosenColors = null; + + /** The target valid targeting */ + private String targetValidTargeting = null; + + /** The b targetsSingleTargeting */ + private boolean targetsSingleTarget = false; + + /** + *

+ * Setter for the field manaSpent. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void setManaSpent(final String s) { + this.manaSpent = s; + } + + /** + *

+ * Getter for the field manaSpent. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getManaSpent() { + return this.manaSpent; + } + + /** + *

+ * Setter for the field zone. + *

+ * + * @param zone + * a {@link java.lang.String} object. + */ + public final void setZone(final ZoneType zone) { + this.zone = zone; + } + + /** + *

+ * Getter for the field zone. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final ZoneType getZone() { + return this.zone; + } + + /** + *

+ * setSorcerySpeed. + *

+ * + * @param bSpeed + * a boolean. + */ + public final void setSorcerySpeed(final boolean bSpeed) { + this.sorcerySpeed = bSpeed; + } + + /** + *

+ * getSorcerySpeed. + *

+ * + * @return a boolean. + */ + public final boolean isSorcerySpeed() { + return this.sorcerySpeed; + } + + /** + *

+ * setInstantSpeed. + *

+ * + * @param bSpeed + * a boolean. + */ + public final void setInstantSpeed(final boolean bSpeed) { + this.instantSpeed = bSpeed; + } + + /** + *

+ * getInstantSpeed. + *

+ * + * @return a boolean. + */ + public final boolean isInstantSpeed() { + return this.instantSpeed; + } + + /** + *

+ * setAnyPlayer. + *

+ * + * @param anyPlayer + * a boolean. + */ + public final void setAnyPlayer(final boolean anyPlayer) { + this.anyPlayer = anyPlayer; + } + + /** + *

+ * setPlayerTurn. + *

+ * + * @param bTurn + * a boolean. + */ + public final void setPlayerTurn(final boolean bTurn) { + this.playerTurn = bTurn; + } + + /** + *

+ * getPlayerTurn. + *

+ * + * @return a boolean. + */ + public final boolean getPlayerTurn() { + return this.isPlayerTurn(); + } + + /** + *

+ * setOpponentTurn. + *

+ * + * @param bTurn + * a boolean. + */ + public final void setOpponentTurn(final boolean bTurn) { + this.opponentTurn = bTurn; + } + + /** + *

+ * getOpponentTurn. + *

+ * + * @return a boolean. + */ + public final boolean getOpponentTurn() { + return this.isOpponentTurn(); + } + + /** + *

+ * Setter for the field activationLimit. + *

+ * + * @param limit + * a int. + */ + public final void setActivationLimit(final int limit) { + this.activationLimit = limit; + } + + /** + *

+ * Setter for the field gameActivationLimit. + *

+ * + * @param limit + * a int. + */ + public final void setGameActivationLimit(final int limit) { + this.gameActivationLimit = limit; + } + + /** + *

+ * abilityActivated. + *

+ */ + public final void abilityActivated() { + this.numberTurnActivations++; + this.numberGameActivations++; + } + + /** + *

+ * Getter for the field numberTurnActivations. + *

+ * + * @return a int. + */ + public final int getNumberTurnActivations() { + return this.numberTurnActivations; + } + + /** + *

+ * Getter for the field numberTurnActivations. + *

+ * + * @return a int. + */ + public final int getNumberGameActivations() { + return this.numberGameActivations; + } + + /** + *

+ * resetTurnActivations. + *

+ */ + public final void resetTurnActivations() { + this.numberTurnActivations = 0; + } + + /** + *

+ * Setter for the field activationNumberSacrifice. + *

+ * + * @param num + * a int. + */ + public final void setActivationNumberSacrifice(final int num) { + this.activationNumberSacrifice = num; + } + + /** + *

+ * Getter for the field activationNumberSacrifice. + *

+ * + * @return a int. + */ + public final int getActivationNumberSacrifice() { + return this.activationNumberSacrifice; + } + + /** + *

+ * Setter for the field phases. + *

+ * + * @param phases + * a {@link java.lang.String} object. + */ + public final void setPhases(final List phases) { + this.phases.addAll(phases); + } + + /** + *

+ * setActivateCardsInHand. + *

+ * + * @param cards + * a int. + */ + public final void setActivateCardsInHand(final int cards) { + this.setCardsInHand(cards); + } + + // specific named conditions + /** + *

+ * Setter for the field hellbent. + *

+ * + * @param bHellbent + * a boolean. + */ + public final void setHellbent(final boolean bHellbent) { + this.hellbent = bHellbent; + } + + /** + *

+ * Setter for the field threshold. + *

+ * + * @param bThreshold + * a boolean. + */ + public final void setThreshold(final boolean bThreshold) { + this.threshold = bThreshold; + } + + /** + *

+ * Setter for the field metalcraft. + *

+ * + * @param bMetalcraft + * a boolean. + */ + public final void setMetalcraft(final boolean bMetalcraft) { + this.metalcraft = bMetalcraft; + } + + /** The Kicked. */ + protected boolean kicked = false; + protected boolean kicked1 = false; // http://magiccards.info/query?q=o%3A%22kicker%22+not+o%3A%22multikicker%22+o%3A%22and%2For+{%22 + protected boolean kicked2 = false; // Some spells have 2 kickers with different effects + protected boolean altCostPaid = false; + + /** + * @return the allTargetsLegal + */ + public boolean isAllTargetsLegal() { + return allTargetsLegal; + } + + /** + * @param allTargetsLegal0 the allTargetsLegal to set + */ + public void setAllTargetsLegal(boolean allTargets) { + this.allTargetsLegal = allTargets; + } + + + /** + *

+ * Setter for the field prowl. + *

+ * + * @param types + * the new prowl + */ + public final void setProwlTypes(final ArrayList types) { + this.prowlTypes = types; + } + + // IsPresent for Valid battlefield stuff + + /** + *

+ * setIsPresent. + *

+ * + * @param present + * a {@link java.lang.String} object. + */ + public final void setIsPresent(final String present) { + this.isPresent = present; + } + + /** + *

+ * Setter for the field presentCompare. + *

+ * + * @param compare + * a {@link java.lang.String} object. + */ + public final void setPresentCompare(final String compare) { + this.presentCompare = compare; + } + + /** + * Gets the present zone. + * + * @return the present zone + */ + public final ZoneType getPresentZone() { + return this.presentZone; + } + + /** + * Sets the present zone. + * + * @param presentZone + * the new present zone + */ + public final void setPresentZone(final ZoneType presentZone) { + this.presentZone = presentZone; + } + + /** + *

+ * Setter for the field presentDefined. + *

+ * + * @param defined + * a {@link java.lang.String} object. + */ + public final void setPresentDefined(final String defined) { + this.presentDefined = defined; + } + + // used to define as a Planeswalker ability + /** + *

+ * setPlaneswalker. + *

+ * + * @param bPlaneswalker + * a boolean. + */ + public final void setPlaneswalker(final boolean bPlaneswalker) { + this.setPwAbility(bPlaneswalker); + } + + /** + *

+ * getPlaneswalker. + *

+ * + * @return a boolean. + */ + public final boolean getPlaneswalker() { + return this.isPwAbility(); + } + + // Checking the values of SVars (Mostly for Traps) + /** + *

+ * Setter for the field svarToCheck. + *

+ * + * @param sVar + * a {@link java.lang.String} object. + */ + public final void setSvarToCheck(final String sVar) { + this.setsVarToCheck(sVar); + } + + /** + *

+ * Setter for the field svarOperator. + *

+ * + * @param operator + * a {@link java.lang.String} object. + */ + public final void setSvarOperator(final String operator) { + this.setsVarOperator(operator); + } + + /** + *

+ * Setter for the field svarOperand. + *

+ * + * @param operand + * a {@link java.lang.String} object. + */ + public final void setSvarOperand(final String operand) { + this.setsVarOperand(operand); + } + + /** + * Gets the activation limit. + * + * @return the activationLimit + */ + public final int getActivationLimit() { + return this.activationLimit; + } + + /** + * Gets the activation limit. + * + * @return the activationLimit + */ + public final int getGameActivationLimit() { + return this.gameActivationLimit; + } + + /** + *

+ * Setter for the field limitToCheck. + *

+ * + * @param limit + * a {@link java.lang.String} object. + */ + public final void setLimitToCheck(final String limit) { + this.limitToCheck = limit; + } + + /** + *

+ * Setter for the field GamelimitToCheck. + *

+ * + * @param limit + * a {@link java.lang.String} object. + */ + public final void setGameLimitToCheck(final String limit) { + this.gameLimitToCheck = limit; + } + + /** + *

+ * Getter for the field limitToCheck. + *

+ * + * @return the limitToCheck + * a {@link java.lang.String} object. + */ + public final String getLimitToCheck() { + return this.limitToCheck; + } + + /** + *

+ * Getter for the field getGameLimitToCheck. + *

+ * + * @return the getGameLimitToCheck + * a {@link java.lang.String} object. + */ + public final String getGameLimitToCheck() { + return this.gameLimitToCheck; + } + + /** + * Checks if is threshold. + * + * @return the threshold + */ + public final boolean isThreshold() { + return this.threshold; + } + + /** + * Checks if is metalcraft. + * + * @return the metalcraft + */ + public final boolean isMetalcraft() { + return this.metalcraft; + } + + /** + * Checks if is hellbent. + * + * @return the hellbent + */ + public final boolean isHellbent() { + return this.hellbent; + } + + /** + * Checks if is pw ability. + * + * @return the pwAbility + */ + public final boolean isPwAbility() { + return this.pwAbility; + } + + /** + * Sets the pw ability. + * + * @param pwAbility0 + * the new pw ability + */ + public final void setPwAbility(final boolean pwAbility0) { + this.pwAbility = pwAbility0; + } + + /** + * Checks if is player turn. + * + * @return the playerTurn + */ + public final boolean isPlayerTurn() { + return this.playerTurn; + } + + /** + * Gets the prowl. + * + * @return the prowl + */ + public final ArrayList getProwlTypes() { + return this.prowlTypes; + } + + /** + * Gets the present compare. + * + * @return the presentCompare + */ + public final String getPresentCompare() { + return this.presentCompare; + } + + /** + * Gets the life total. + * + * @return the lifeTotal + */ + public final String getLifeTotal() { + return this.lifeTotal; + } + + /** + * Sets the life total. + * + * @param lifeTotal0 + * the lifeTotal to set + */ + public final void setLifeTotal(final String lifeTotal0) { + this.lifeTotal = lifeTotal0; + } + + /** + * Gets the life amount. + * + * @return the lifeAmount + */ + public final String getLifeAmount() { + return this.lifeAmount; + } + + /** + * Sets the life amount. + * + * @param lifeAmount0 + * the lifeAmount to set + */ + public final void setLifeAmount(final String lifeAmount0) { + this.lifeAmount = lifeAmount0; + } + + /** + * Gets the phases. + * + * @return the phases + */ + public final List getPhases() { + return this.phases; + } + + + /** + * Gets the present defined. + * + * @return the presentDefined + */ + public final String getPresentDefined() { + return this.presentDefined; + } + + /** + * Set the player defined. + * + */ + public final void setPlayerDefined(final String b) { + this.playerDefined = b; + } + + /** + * Gets the player defined. + * + * @return the playerDefined + */ + public final String getPlayerDefined() { + return this.playerDefined; + } + + /** + * Gets the player contains. + * + * @return the playerContains + */ + public final String getPlayerContains() { + return this.playerContains; + } + + /** + * Set the player contains. + * + */ + public final void setPlayerContains(final String contains) { + this.playerContains = contains; + } + + /** + * Gets the s var operand. + * + * @return the sVarOperand + */ + public final String getsVarOperand() { + return this.sVarOperand; + } + + /** + * Sets the s var operand. + * + * @param sVarOperand0 + * the sVarOperand to set + */ + public final void setsVarOperand(final String sVarOperand0) { + this.sVarOperand = sVarOperand0; + } + + /** + * Gets the s var to check. + * + * @return the sVarToCheck + */ + public final String getsVarToCheck() { + return this.sVarToCheck; + } + + /** + * Sets the s var to check. + * + * @param sVarToCheck + * the sVarToCheck to set + */ + public final void setsVarToCheck(final String sVarToCheck) { + this.sVarToCheck = sVarToCheck; + } + + /** + * Gets the s var operator. + * + * @return the sVarOperator + */ + public final String getsVarOperator() { + return this.sVarOperator; + } + + /** + * Sets the s var operator. + * + * @param sVarOperator0 + * the sVarOperator to set + */ + public final void setsVarOperator(final String sVarOperator0) { + this.sVarOperator = sVarOperator0; + } + + /** + * Checks if is opponent turn. + * + * @return the opponentTurn + */ + public final boolean isOpponentTurn() { + return this.opponentTurn; + } + + /** + * Gets the cards in hand. + * + * @return the cardsInHand + */ + public final int getCardsInHand() { + return this.cardsInHand; + } + + /** + * Sets the cards in hand. + * + * @param cardsInHand0 + * the cardsInHand to set + */ + public final void setCardsInHand(final int cardsInHand0) { + this.cardsInHand = cardsInHand0; + } + + /** + * Gets the checks if is present. + * + * @return the isPresent + */ + public final String getIsPresent() { + return this.isPresent; + } + + /** + * Checks if is any player. + * + * @return the anyPlayer + */ + public final boolean isAnyPlayer() { + return this.anyPlayer; + } + + /** + * @return the opponentOnly + */ + public boolean isOpponentOnly() { + return opponentOnly; + } + + /** + * @param opponentOnly the opponentOnly to set + */ + public void setOpponentOnly(boolean opponentOnly) { + this.opponentOnly = opponentOnly; + } + + /** + * @return the ownerOnly + */ + public boolean isOwnerOnly() { + return ownerOnly; + } + + /** + * @param ownerOnly the ownerOnly to set + */ + public void setOwnerOnly(boolean ownerOnly) { + this.ownerOnly = ownerOnly; + } + /** + *

+ * Setter for the field ColorToCheck. + *

+ * + * @param s + * a {@link java.lang.String} object. + */ + public final void setColorToCheck(final String s) { + this.chosenColors = s; + } + + /** + *

+ * Getter for the field ColorToCheck. + *

+ * + * @return the String, chosenColors. + */ + public final String getColorToCheck() { + return this.chosenColors; + } + + /** + * @return the targetValidTargeting + */ + public String getTargetValidTargeting() { + return targetValidTargeting; + } + + /** + * @param targetValidTargeting the targetValidTargeting to set + */ + public void setTargetValidTargeting(String targetValidTargeting) { + this.targetValidTargeting = targetValidTargeting; + } + + /** + * @return the targetsSingleTarget + */ + public boolean targetsSingleTarget() { + return targetsSingleTarget; + } + + /** + * @param b the targetsSingleTarget to set + */ + public void setTargetsSingleTarget(boolean b) { + this.targetsSingleTarget = b; + } + +} // end class SpellAbilityVariables diff --git a/forge-game/src/main/java/forge/game/spellability/SpellPermanent.java b/forge-gui/src/main/java/forge/game/spellability/SpellPermanent.java similarity index 96% rename from forge-game/src/main/java/forge/game/spellability/SpellPermanent.java rename to forge-gui/src/main/java/forge/game/spellability/SpellPermanent.java index ce539df13a4..8f578094199 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellPermanent.java +++ b/forge-gui/src/main/java/forge/game/spellability/SpellPermanent.java @@ -1,373 +1,373 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import java.security.InvalidParameterException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; - -import com.google.common.collect.Iterables; - -import forge.ai.ComputerUtil; -import forge.ai.ComputerUtilCost; -import forge.ai.ComputerUtilMana; -import forge.card.mana.ManaCost; -import forge.game.Game; -import forge.game.GlobalRuleChange; -import forge.game.ability.AbilityFactory; -import forge.game.ability.ApiType; -import forge.game.card.Card; -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.replacement.ReplaceMoved; -import forge.game.replacement.ReplacementEffect; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerType; -import forge.game.zone.ZoneType; - -/** - *

- * Spell_Permanent class. - *

- * - * @author Forge - * @version $Id: SpellPermanent.java 23792 2013-11-24 10:02:58Z Max mtg $ - */ -public class SpellPermanent extends Spell { - /** Constant serialVersionUID=2413495058630644447L. */ - private static final long serialVersionUID = 2413495058630644447L; - - /** - *

- * Constructor for Spell_Permanent. - *

- * - * @param sourceCard - * a {@link forge.game.card.Card} object. - */ - public SpellPermanent(final Card sourceCard) { - super(sourceCard, new Cost(sourceCard.getManaCost(), false)); - - if (sourceCard.isCreature()) { - final StringBuilder sb = new StringBuilder(); - sb.append(sourceCard.getName()).append(" - Creature ").append(sourceCard.getNetAttack()); - sb.append(" / ").append(sourceCard.getNetDefense()); - this.setStackDescription(sb.toString()); - } else { - this.setStackDescription(sourceCard.getName()); - } - - - this.setDescription(this.getStackDescription()); - - if (this.getPayCosts().getTotalMana().countX() > 0 && StringUtils.isNotBlank(getSourceCard().getSVar("X"))) { - this.setSVar("X", this.getSourceCard().getSVar("X")); - } - - } // Spell_Permanent() - - /** {@inheritDoc} */ - @Override - public boolean canPlayAI(Player aiPlayer) { - - final Card card = this.getSourceCard(); - ManaCost mana = this.getPayCosts().getTotalMana(); - final Game game = aiPlayer.getGame(); - if (mana.countX() > 0) { - // Set PayX here to maximum value. - final int xPay = ComputerUtilMana.determineLeftoverMana(this, aiPlayer); - if (xPay <= 0) { - return false; - } - card.setSVar("PayX", Integer.toString(xPay)); - } - // Prevent the computer from summoning Ball Lightning type creatures after attacking - if (card.hasKeyword("At the beginning of the end step, sacrifice CARDNAME.") - && (game.getPhaseHandler().isPlayerTurn(aiPlayer.getOpponent()) - || game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS))) { - return false; - } - - // Prevent the computer from summoning Ball Lightning type creatures after attacking - if (card.hasStartOfKeyword("You may cast CARDNAME as though it had flash. If") - && !card.getController().couldCastSorcery(this)) { - return false; - } - - // Wait for Main2 if possible - if (game.getPhaseHandler().is(PhaseType.MAIN1) - && game.getPhaseHandler().isPlayerTurn(aiPlayer) - && aiPlayer.getManaPool().totalMana() <= 0 - && !ComputerUtil.castPermanentInMain1(aiPlayer, this)) { - return false; - } - // save cards with flash for surprise blocking - if (card.hasKeyword("Flash") - && (aiPlayer.isUnlimitedHandSize() || aiPlayer.getCardsIn(ZoneType.Hand).size() <= aiPlayer.getMaxHandSize()) - && aiPlayer.getManaPool().totalMana() <= 0 - && (game.getPhaseHandler().isPlayerTurn(aiPlayer) - || game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) - && !card.hasETBTrigger()) - && !ComputerUtil.castPermanentInMain1(aiPlayer, this)) { - return false; - } - - return canPlayFromEffectAI(aiPlayer, false, true); - } // canPlayAI() - - /** {@inheritDoc} */ - @Override - public boolean canPlayFromEffectAI(Player aiPlayer, final boolean mandatory, final boolean withOutManaCost) { - if (mandatory) { - return true; - } - final Card card = this.getSourceCard(); - ManaCost mana = this.getPayCosts().getTotalMana(); - final Cost cost = this.getPayCosts(); - - if (cost != null) { - // AI currently disabled for these costs - if (!ComputerUtilCost.checkLifeCost(aiPlayer, cost, card, 4, null)) { - return false; - } - - if (!ComputerUtilCost.checkDiscardCost(aiPlayer, cost, card)) { - return false; - } - - if (!ComputerUtilCost.checkSacrificeCost(aiPlayer, cost, card)) { - return false; - } - - if (!ComputerUtilCost.checkRemoveCounterCost(cost, card)) { - return false; - } - } - - // check on legendary - if (card.isType("Legendary") - && !aiPlayer.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) { - final List list = aiPlayer.getCardsIn(ZoneType.Battlefield); - if (Iterables.any(list, CardPredicates.nameEquals(card.getName()))) { - return false; - } - } - if (card.isPlaneswalker()) { - List list = aiPlayer.getCardsIn(ZoneType.Battlefield); - list = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS); - - for (int i = 0; i < list.size(); i++) { - List type = card.getType(); - final String subtype = type.get(type.size() - 1); - final List cl = CardLists.getType(list, subtype); - - if (cl.size() > 0) { - return false; - } - } - } - if (card.isType("World")) { - List list = aiPlayer.getCardsIn(ZoneType.Battlefield); - list = CardLists.getType(list, "World"); - if (list.size() > 0) { - return false; - } - } - - if (card.isCreature() && (card.getNetDefense() <= 0) && !card.hasStartOfKeyword("etbCounter") - && mana.countX() == 0 && !card.hasETBTrigger() - && !card.hasETBReplacement()) { - return false; - } - - if (!SpellPermanent.checkETBEffects(card, this, null, aiPlayer)) { - return false; - } - if (ComputerUtil.damageFromETB(aiPlayer, card) >= aiPlayer.getLife() && aiPlayer.canLoseLife()) { - return false; - } - return super.canPlayAI(aiPlayer); - } - - public static boolean checkETBEffects(final Card card, final Player ai) { - return checkETBEffects(card, null, null, ai); - } - - public static boolean checkETBEffects(final Card card, final SpellAbility sa, final ApiType api, final Player ai) { - boolean rightapi = false; - final Game game = ai.getGame(); - - if (card.isCreature() - && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCreatureETBTriggers)) { - return api == null; - } - - // Trigger play improvements - for (final Trigger tr : card.getTriggers()) { - // These triggers all care for ETB effects - - final HashMap params = tr.getMapParams(); - if (tr.getMode() != TriggerType.ChangesZone) { - continue; - } - - if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) { - continue; - } - - if (params.containsKey("ValidCard")) { - if (!params.get("ValidCard").contains("Self")) { - continue; - } - if (params.get("ValidCard").contains("notkicked")) { - if (sa.isKicked()) { - continue; - } - } else if (params.get("ValidCard").contains("kicked")) { - if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker - String s = params.get("ValidCard").split("kicked ")[1]; - if ( "1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue; - if ( "2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue; - } else if (!sa.isKicked()) { - continue; - } - } - } - - if (!tr.requirementsCheck(game)) { - continue; - } - - if (tr.getOverridingAbility() != null) { - // Abilities yet - continue; - } - - // if trigger is not mandatory - no problem - if (params.get("OptionalDecider") != null) { - continue; - } - - // Maybe better considerations - final String execute = params.get("Execute"); - if (execute == null) { - continue; - } - final SpellAbility exSA = AbilityFactory.getAbility(card.getSVar(execute), card); - - if (api != null) { - if (exSA.getApi() != api) { - continue; - } else { - rightapi = true; - } - } - - if (sa != null) { - exSA.setActivatingPlayer(sa.getActivatingPlayer()); - } - else { - exSA.setActivatingPlayer(ai); - } - exSA.setTrigger(true); - - // Run non-mandatory trigger. - // These checks only work if the Executing SpellAbility is an Ability_Sub. - if ((exSA instanceof AbilitySub) && !exSA.doTrigger(false, ai)) { - // AI would not run this trigger if given the chance - return false; - } - } - if (api != null && !rightapi) { - return false; - } - - // Replacement effects - for (final ReplacementEffect re : card.getReplacementEffects()) { - // These Replacements all care for ETB effects - - final Map params = re.getMapParams(); - if (!(re instanceof ReplaceMoved)) { - continue; - } - - if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) { - continue; - } - - if (params.containsKey("ValidCard")) { - if (!params.get("ValidCard").contains("Self")) { - continue; - } - if (params.get("ValidCard").contains("notkicked")) { - if (sa.isKicked()) { - continue; - } - } else if (params.get("ValidCard").contains("kicked")) { - if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker - String s = params.get("ValidCard").split("kicked ")[1]; - if ( "1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue; - if ( "2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue; - } else if (!sa.isKicked()) { // otherwise just any must be present - continue; - } - } - } - - if (!re.requirementsCheck(game)) { - continue; - } - final SpellAbility exSA = re.getOverridingAbility(); - - if (exSA != null) { - if (sa != null) { - exSA.setActivatingPlayer(sa.getActivatingPlayer()); - } - else { - exSA.setActivatingPlayer(ai); - } - - if (exSA.getActivatingPlayer() == null) { - throw new InvalidParameterException("Executing SpellAbility for Replacement Effect has no activating player"); - } - } - - // ETBReplacement uses overriding abilities. - // These checks only work if the Executing SpellAbility is an Ability_Sub. - if (exSA != null && (exSA instanceof AbilitySub) && !exSA.doTrigger(false, ai)) { - return false; - } - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public void resolve() { - Card c = this.getSourceCard(); - c.setController(this.getActivatingPlayer(), 0); - this.getActivatingPlayer().getGame().getAction().moveTo(ZoneType.Battlefield, c); - } -} +/* + * 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 . + */ +package forge.game.spellability; + +import java.security.InvalidParameterException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.Iterables; + +import forge.ai.ComputerUtil; +import forge.ai.ComputerUtilCost; +import forge.ai.ComputerUtilMana; +import forge.card.mana.ManaCost; +import forge.game.Game; +import forge.game.GlobalRuleChange; +import forge.game.ability.AbilityFactory; +import forge.game.ability.ApiType; +import forge.game.card.Card; +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.replacement.ReplaceMoved; +import forge.game.replacement.ReplacementEffect; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; + +/** + *

+ * Spell_Permanent class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class SpellPermanent extends Spell { + /** Constant serialVersionUID=2413495058630644447L. */ + private static final long serialVersionUID = 2413495058630644447L; + + /** + *

+ * Constructor for Spell_Permanent. + *

+ * + * @param sourceCard + * a {@link forge.game.card.Card} object. + */ + public SpellPermanent(final Card sourceCard) { + super(sourceCard, new Cost(sourceCard.getManaCost(), false)); + + if (sourceCard.isCreature()) { + final StringBuilder sb = new StringBuilder(); + sb.append(sourceCard.getName()).append(" - Creature ").append(sourceCard.getNetAttack()); + sb.append(" / ").append(sourceCard.getNetDefense()); + this.setStackDescription(sb.toString()); + } else { + this.setStackDescription(sourceCard.getName()); + } + + + this.setDescription(this.getStackDescription()); + + if (this.getPayCosts().getTotalMana().countX() > 0 && StringUtils.isNotBlank(getSourceCard().getSVar("X"))) { + this.setSVar("X", this.getSourceCard().getSVar("X")); + } + + } // Spell_Permanent() + + /** {@inheritDoc} */ + @Override + public boolean canPlayAI(Player aiPlayer) { + + final Card card = this.getSourceCard(); + ManaCost mana = this.getPayCosts().getTotalMana(); + final Game game = aiPlayer.getGame(); + if (mana.countX() > 0) { + // Set PayX here to maximum value. + final int xPay = ComputerUtilMana.determineLeftoverMana(this, aiPlayer); + if (xPay <= 0) { + return false; + } + card.setSVar("PayX", Integer.toString(xPay)); + } + // Prevent the computer from summoning Ball Lightning type creatures after attacking + if (card.hasKeyword("At the beginning of the end step, sacrifice CARDNAME.") + && (game.getPhaseHandler().isPlayerTurn(aiPlayer.getOpponent()) + || game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS))) { + return false; + } + + // Prevent the computer from summoning Ball Lightning type creatures after attacking + if (card.hasStartOfKeyword("You may cast CARDNAME as though it had flash. If") + && !card.getController().couldCastSorcery(this)) { + return false; + } + + // Wait for Main2 if possible + if (game.getPhaseHandler().is(PhaseType.MAIN1) + && game.getPhaseHandler().isPlayerTurn(aiPlayer) + && aiPlayer.getManaPool().totalMana() <= 0 + && !ComputerUtil.castPermanentInMain1(aiPlayer, this)) { + return false; + } + // save cards with flash for surprise blocking + if (card.hasKeyword("Flash") + && (aiPlayer.isUnlimitedHandSize() || aiPlayer.getCardsIn(ZoneType.Hand).size() <= aiPlayer.getMaxHandSize()) + && aiPlayer.getManaPool().totalMana() <= 0 + && (game.getPhaseHandler().isPlayerTurn(aiPlayer) + || game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) + && !card.hasETBTrigger()) + && !ComputerUtil.castPermanentInMain1(aiPlayer, this)) { + return false; + } + + return canPlayFromEffectAI(aiPlayer, false, true); + } // canPlayAI() + + /** {@inheritDoc} */ + @Override + public boolean canPlayFromEffectAI(Player aiPlayer, final boolean mandatory, final boolean withOutManaCost) { + if (mandatory) { + return true; + } + final Card card = this.getSourceCard(); + ManaCost mana = this.getPayCosts().getTotalMana(); + final Cost cost = this.getPayCosts(); + + if (cost != null) { + // AI currently disabled for these costs + if (!ComputerUtilCost.checkLifeCost(aiPlayer, cost, card, 4, null)) { + return false; + } + + if (!ComputerUtilCost.checkDiscardCost(aiPlayer, cost, card)) { + return false; + } + + if (!ComputerUtilCost.checkSacrificeCost(aiPlayer, cost, card)) { + return false; + } + + if (!ComputerUtilCost.checkRemoveCounterCost(cost, card)) { + return false; + } + } + + // check on legendary + if (card.isType("Legendary") + && !aiPlayer.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) { + final List list = aiPlayer.getCardsIn(ZoneType.Battlefield); + if (Iterables.any(list, CardPredicates.nameEquals(card.getName()))) { + return false; + } + } + if (card.isPlaneswalker()) { + List list = aiPlayer.getCardsIn(ZoneType.Battlefield); + list = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS); + + for (int i = 0; i < list.size(); i++) { + List type = card.getType(); + final String subtype = type.get(type.size() - 1); + final List cl = CardLists.getType(list, subtype); + + if (cl.size() > 0) { + return false; + } + } + } + if (card.isType("World")) { + List list = aiPlayer.getCardsIn(ZoneType.Battlefield); + list = CardLists.getType(list, "World"); + if (list.size() > 0) { + return false; + } + } + + if (card.isCreature() && (card.getNetDefense() <= 0) && !card.hasStartOfKeyword("etbCounter") + && mana.countX() == 0 && !card.hasETBTrigger() + && !card.hasETBReplacement()) { + return false; + } + + if (!SpellPermanent.checkETBEffects(card, this, null, aiPlayer)) { + return false; + } + if (ComputerUtil.damageFromETB(aiPlayer, card) >= aiPlayer.getLife() && aiPlayer.canLoseLife()) { + return false; + } + return super.canPlayAI(aiPlayer); + } + + public static boolean checkETBEffects(final Card card, final Player ai) { + return checkETBEffects(card, null, null, ai); + } + + public static boolean checkETBEffects(final Card card, final SpellAbility sa, final ApiType api, final Player ai) { + boolean rightapi = false; + final Game game = ai.getGame(); + + if (card.isCreature() + && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCreatureETBTriggers)) { + return api == null; + } + + // Trigger play improvements + for (final Trigger tr : card.getTriggers()) { + // These triggers all care for ETB effects + + final HashMap params = tr.getMapParams(); + if (tr.getMode() != TriggerType.ChangesZone) { + continue; + } + + if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) { + continue; + } + + if (params.containsKey("ValidCard")) { + if (!params.get("ValidCard").contains("Self")) { + continue; + } + if (params.get("ValidCard").contains("notkicked")) { + if (sa.isKicked()) { + continue; + } + } else if (params.get("ValidCard").contains("kicked")) { + if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker + String s = params.get("ValidCard").split("kicked ")[1]; + if ( "1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue; + if ( "2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue; + } else if (!sa.isKicked()) { + continue; + } + } + } + + if (!tr.requirementsCheck(game)) { + continue; + } + + if (tr.getOverridingAbility() != null) { + // Abilities yet + continue; + } + + // if trigger is not mandatory - no problem + if (params.get("OptionalDecider") != null) { + continue; + } + + // Maybe better considerations + final String execute = params.get("Execute"); + if (execute == null) { + continue; + } + final SpellAbility exSA = AbilityFactory.getAbility(card.getSVar(execute), card); + + if (api != null) { + if (exSA.getApi() != api) { + continue; + } else { + rightapi = true; + } + } + + if (sa != null) { + exSA.setActivatingPlayer(sa.getActivatingPlayer()); + } + else { + exSA.setActivatingPlayer(ai); + } + exSA.setTrigger(true); + + // Run non-mandatory trigger. + // These checks only work if the Executing SpellAbility is an Ability_Sub. + if ((exSA instanceof AbilitySub) && !exSA.doTrigger(false, ai)) { + // AI would not run this trigger if given the chance + return false; + } + } + if (api != null && !rightapi) { + return false; + } + + // Replacement effects + for (final ReplacementEffect re : card.getReplacementEffects()) { + // These Replacements all care for ETB effects + + final Map params = re.getMapParams(); + if (!(re instanceof ReplaceMoved)) { + continue; + } + + if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) { + continue; + } + + if (params.containsKey("ValidCard")) { + if (!params.get("ValidCard").contains("Self")) { + continue; + } + if (params.get("ValidCard").contains("notkicked")) { + if (sa.isKicked()) { + continue; + } + } else if (params.get("ValidCard").contains("kicked")) { + if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker + String s = params.get("ValidCard").split("kicked ")[1]; + if ( "1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue; + if ( "2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue; + } else if (!sa.isKicked()) { // otherwise just any must be present + continue; + } + } + } + + if (!re.requirementsCheck(game)) { + continue; + } + final SpellAbility exSA = re.getOverridingAbility(); + + if (exSA != null) { + if (sa != null) { + exSA.setActivatingPlayer(sa.getActivatingPlayer()); + } + else { + exSA.setActivatingPlayer(ai); + } + + if (exSA.getActivatingPlayer() == null) { + throw new InvalidParameterException("Executing SpellAbility for Replacement Effect has no activating player"); + } + } + + // ETBReplacement uses overriding abilities. + // These checks only work if the Executing SpellAbility is an Ability_Sub. + if (exSA != null && (exSA instanceof AbilitySub) && !exSA.doTrigger(false, ai)) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public void resolve() { + Card c = this.getSourceCard(); + c.setController(this.getActivatingPlayer(), 0); + this.getActivatingPlayer().getGame().getAction().moveTo(ZoneType.Battlefield, c); + } +} diff --git a/forge-game/src/main/java/forge/game/spellability/TargetChoices.java b/forge-gui/src/main/java/forge/game/spellability/TargetChoices.java similarity index 95% rename from forge-game/src/main/java/forge/game/spellability/TargetChoices.java rename to forge-gui/src/main/java/forge/game/spellability/TargetChoices.java index 693371013e5..642f85a44c1 100644 --- a/forge-game/src/main/java/forge/game/spellability/TargetChoices.java +++ b/forge-gui/src/main/java/forge/game/spellability/TargetChoices.java @@ -1,266 +1,266 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import java.util.ArrayList; -import java.util.List; - -import com.google.common.collect.Iterables; - -import forge.game.GameObject; -import forge.game.card.Card; -import forge.game.player.Player; - -/** - *

- * Target_Choices class. - *

- * - * @author Forge - * @version $Id: TargetChoices.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TargetChoices implements Cloneable { - private int numTargeted = 0; - - // Card or Player are legal targets. - private final List targetCards = new ArrayList(); - private final List targetPlayers = new ArrayList(); - private final List targetSpells = new ArrayList(); - - /** - *

- * Getter for the field numTargeted. - *

- * - * @return a int. - */ - public final int getNumTargeted() { - return this.numTargeted; - } - - /** - *

- * addTarget. - *

- * - * @param o - * a {@link java.lang.Object} object. - * @return a boolean. - */ - public final boolean add(final GameObject o) { - if (o instanceof Player) { - return this.addTarget((Player) o); - } else if (o instanceof Card) { - return this.addTarget((Card) o); - } else if (o instanceof SpellAbility) { - return this.addTarget((SpellAbility) o); - } - - return false; - } - - /** - *

- * addTarget. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - private final boolean addTarget(final Card c) { - if (!this.targetCards.contains(c)) { - this.targetCards.add(c); - this.numTargeted++; - return true; - } - return false; - } - - /** - *

- * addTarget. - *

- * - * @param p - * a {@link forge.game.player.Player} object. - * @return a boolean. - */ - private final boolean addTarget(final Player p) { - if (!this.targetPlayers.contains(p)) { - this.targetPlayers.add(p); - this.numTargeted++; - return true; - } - return false; - } - - /** - *

- * addTarget. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean. - */ - private final boolean addTarget(final SpellAbility sa) { - if (!this.targetSpells.contains(sa)) { - this.targetSpells.add(sa); - this.numTargeted++; - return true; - } - return false; - } - - /** - *

- * removeTarget. - *

- * - * @param card - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public final boolean remove(final GameObject target) { - // remove returns true if element was found in given list - if (this.targetCards.remove(target) || this.targetPlayers.remove(target) || this.targetSpells.remove(target)) { - this.numTargeted--; - return true; - } - return false; - } - - /** - *

- * Getter for the field targetCards. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final Iterable getTargetCards() { - return this.targetCards; - } - - /** - *

- * Getter for the field targetPlayers. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final Iterable getTargetPlayers() { - return this.targetPlayers; - } - - /** - *

- * Getter for the field targetSAs. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final Iterable getTargetSpells() { - return this.targetSpells; - } - - /** - *

- * getTargets. - *

- * - * @return a {@link java.util.ArrayList} object. - */ - public final List getTargets() { - final ArrayList tgts = new ArrayList(); - tgts.addAll(this.targetPlayers); - tgts.addAll(this.targetCards); - tgts.addAll(this.targetSpells); - - return tgts; - } - - - public final String getTargetedString() { - final List tgts = this.getTargets(); - final StringBuilder sb = new StringBuilder(); - for (final Object o : tgts) { - if (o instanceof Player) { - final Player p = (Player) o; - sb.append(p.getName()); - } - if (o instanceof Card) { - final Card c = (Card) o; - sb.append(c); - } - if (o instanceof SpellAbility) { - final SpellAbility sa = (SpellAbility) o; - sb.append(sa); - } - sb.append(" "); - } - - return sb.toString(); - } - - public final boolean isTargetingAnyCard() { - return !targetCards.isEmpty(); - } - - public final boolean isTargetingAnyPlayer() { - return !targetPlayers.isEmpty(); - } - - - public final boolean isTargetingAnySpell() { - return !targetSpells.isEmpty(); - } - - public final boolean isTargeting(GameObject e) { - return targetCards.contains(e) || targetSpells.contains(e) || targetPlayers.contains(e); - } - - public final Card getFirstTargetedCard() { - return Iterables.getFirst(targetCards, null); - } - - public final Player getFirstTargetedPlayer() { - return Iterables.getFirst(targetPlayers, null); - } - - public final SpellAbility getFirstTargetedSpell() { - return Iterables.getFirst(targetSpells, null); - } - - /* (non-Javadoc) - * @see forge.card.spellability.ITargetsChosen#isEmpty() - */ - public final boolean isEmpty() { - return targetCards.isEmpty() && targetSpells.isEmpty() && targetPlayers.isEmpty(); - } - - @Override - public TargetChoices clone() { - TargetChoices tc = new TargetChoices(); - tc.targetCards.addAll(this.targetCards); - tc.targetPlayers.addAll(this.targetPlayers); - tc.targetSpells.addAll(this.targetSpells); - tc.numTargeted = this.numTargeted; - return tc; - } -} +/* + * 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 . + */ +package forge.game.spellability; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.collect.Iterables; + +import forge.game.GameObject; +import forge.game.card.Card; +import forge.game.player.Player; + +/** + *

+ * Target_Choices class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TargetChoices implements Cloneable { + private int numTargeted = 0; + + // Card or Player are legal targets. + private final List targetCards = new ArrayList(); + private final List targetPlayers = new ArrayList(); + private final List targetSpells = new ArrayList(); + + /** + *

+ * Getter for the field numTargeted. + *

+ * + * @return a int. + */ + public final int getNumTargeted() { + return this.numTargeted; + } + + /** + *

+ * addTarget. + *

+ * + * @param o + * a {@link java.lang.Object} object. + * @return a boolean. + */ + public final boolean add(final GameObject o) { + if (o instanceof Player) { + return this.addTarget((Player) o); + } else if (o instanceof Card) { + return this.addTarget((Card) o); + } else if (o instanceof SpellAbility) { + return this.addTarget((SpellAbility) o); + } + + return false; + } + + /** + *

+ * addTarget. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + private final boolean addTarget(final Card c) { + if (!this.targetCards.contains(c)) { + this.targetCards.add(c); + this.numTargeted++; + return true; + } + return false; + } + + /** + *

+ * addTarget. + *

+ * + * @param p + * a {@link forge.game.player.Player} object. + * @return a boolean. + */ + private final boolean addTarget(final Player p) { + if (!this.targetPlayers.contains(p)) { + this.targetPlayers.add(p); + this.numTargeted++; + return true; + } + return false; + } + + /** + *

+ * addTarget. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean. + */ + private final boolean addTarget(final SpellAbility sa) { + if (!this.targetSpells.contains(sa)) { + this.targetSpells.add(sa); + this.numTargeted++; + return true; + } + return false; + } + + /** + *

+ * removeTarget. + *

+ * + * @param card + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + public final boolean remove(final GameObject target) { + // remove returns true if element was found in given list + if (this.targetCards.remove(target) || this.targetPlayers.remove(target) || this.targetSpells.remove(target)) { + this.numTargeted--; + return true; + } + return false; + } + + /** + *

+ * Getter for the field targetCards. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final Iterable getTargetCards() { + return this.targetCards; + } + + /** + *

+ * Getter for the field targetPlayers. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final Iterable getTargetPlayers() { + return this.targetPlayers; + } + + /** + *

+ * Getter for the field targetSAs. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final Iterable getTargetSpells() { + return this.targetSpells; + } + + /** + *

+ * getTargets. + *

+ * + * @return a {@link java.util.ArrayList} object. + */ + public final List getTargets() { + final ArrayList tgts = new ArrayList(); + tgts.addAll(this.targetPlayers); + tgts.addAll(this.targetCards); + tgts.addAll(this.targetSpells); + + return tgts; + } + + + public final String getTargetedString() { + final List tgts = this.getTargets(); + final StringBuilder sb = new StringBuilder(); + for (final Object o : tgts) { + if (o instanceof Player) { + final Player p = (Player) o; + sb.append(p.getName()); + } + if (o instanceof Card) { + final Card c = (Card) o; + sb.append(c); + } + if (o instanceof SpellAbility) { + final SpellAbility sa = (SpellAbility) o; + sb.append(sa); + } + sb.append(" "); + } + + return sb.toString(); + } + + public final boolean isTargetingAnyCard() { + return !targetCards.isEmpty(); + } + + public final boolean isTargetingAnyPlayer() { + return !targetPlayers.isEmpty(); + } + + + public final boolean isTargetingAnySpell() { + return !targetSpells.isEmpty(); + } + + public final boolean isTargeting(GameObject e) { + return targetCards.contains(e) || targetSpells.contains(e) || targetPlayers.contains(e); + } + + public final Card getFirstTargetedCard() { + return Iterables.getFirst(targetCards, null); + } + + public final Player getFirstTargetedPlayer() { + return Iterables.getFirst(targetPlayers, null); + } + + public final SpellAbility getFirstTargetedSpell() { + return Iterables.getFirst(targetSpells, null); + } + + /* (non-Javadoc) + * @see forge.card.spellability.ITargetsChosen#isEmpty() + */ + public final boolean isEmpty() { + return targetCards.isEmpty() && targetSpells.isEmpty() && targetPlayers.isEmpty(); + } + + @Override + public TargetChoices clone() { + TargetChoices tc = new TargetChoices(); + tc.targetCards.addAll(this.targetCards); + tc.targetPlayers.addAll(this.targetPlayers); + tc.targetSpells.addAll(this.targetSpells); + tc.numTargeted = this.numTargeted; + return tc; + } +} diff --git a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java b/forge-gui/src/main/java/forge/game/spellability/TargetRestrictions.java similarity index 95% rename from forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java rename to forge-gui/src/main/java/forge/game/spellability/TargetRestrictions.java index 782cd7f33e9..0d1842cb55e 100644 --- a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java +++ b/forge-gui/src/main/java/forge/game/spellability/TargetRestrictions.java @@ -1,729 +1,729 @@ -/* - * 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 . - */ -package forge.game.spellability; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; - -import forge.card.CardType; -import forge.game.Game; -import forge.game.GameObject; -import forge.game.ability.AbilityUtils; -import forge.game.card.Card; -import forge.game.player.Player; -import forge.game.zone.ZoneType; - -/** - *

- * Target class. - *

- * - * @author Forge - * @version $Id: TargetRestrictions.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TargetRestrictions { - // Target has two things happening: - // Targeting restrictions (Creature, Min/Maxm etc) which are true for this - - // Target Choices (which is specific for the StackInstance) - - // What this Object is restricted to targeting - private boolean tgtValid = false; - private String[] validTgts; - private String uiPrompt = ""; - private List tgtZone = Arrays.asList(ZoneType.Battlefield); - - // The target SA of this SA must be targeting a Valid X - private String saValidTargeting = null; - - // Additional restrictions that may not fit into Valid - private boolean uniqueTargets = false; - private boolean singleZone = false; - private boolean differentZone = false; - private boolean differentControllers = false; - private boolean sameController = false; - private boolean withoutSameCreatureType = false; - private boolean singleTarget = false; - private boolean randomTarget = false; - private String relatedProperty = null; - - // How many can be targeted? - private String minTargets; - private String maxTargets; - - // For "Divided" cards. Is this better in TargetChoices? - private boolean dividedAsYouChoose = false; - private HashMap dividedMap = new HashMap(); - private int stillToDivide = 0; - - // Not sure what's up with Mandatory? Why wouldn't targeting be mandatory? - private boolean bMandatory = false; - - /** - *

- * Copy Constructor for Target. - *

- * - * @param target - * a {@link forge.game.spellability.TargetRestrictions} object. - */ - public TargetRestrictions(final TargetRestrictions target) { - this.tgtValid = true; - this.uiPrompt = target.getVTSelection(); - this.validTgts = target.getValidTgts(); - this.minTargets = target.getMinTargets(); - this.maxTargets = target.getMaxTargets(); - this.tgtZone = target.getZone(); - this.saValidTargeting = target.getSAValidTargeting(); - this.dividedAsYouChoose = target.isDividedAsYouChoose(); - this.uniqueTargets = target.isUniqueTargets(); - this.singleZone = target.isSingleZone(); - this.differentControllers = target.isDifferentControllers(); - this.differentZone = target.isDifferentZone(); - this.sameController = target.isSameController(); - this.withoutSameCreatureType = target.isWithoutSameCreatureType(); - this.relatedProperty = target.getRelatedProperty(); - this.singleTarget = target.isSingleTarget(); - this.randomTarget = target.isRandomTarget(); - } - - /** - *

- * Constructor for Target. - *

- * - * @param src - * a {@link forge.game.card.Card} object. - * @param prompt - * a {@link java.lang.String} object. - * @param valid - * an array of {@link java.lang.String} objects. - * @param min - * a {@link java.lang.String} object. - * @param max - * a {@link java.lang.String} object. - */ - public TargetRestrictions(final String prompt, final String[] valid, final String min, final String max) { - this.tgtValid = true; - this.uiPrompt = prompt; - this.validTgts = valid; - this.minTargets = min; - this.maxTargets = max; - } - - /** - *

- * getMandatory. - *

- * - * @return a boolean. - */ - public final boolean getMandatory() { - return this.bMandatory; - } - - /** - *

- * setMandatory. - *

- * - * @param m - * a boolean. - */ - public final void setMandatory(final boolean m) { - this.bMandatory = m; - } - - /** - *

- * doesTarget. - *

- * - * @return a boolean. - */ - public final boolean doesTarget() { - return this.tgtValid; - } - - /** - *

- * getValidTgts. - *

- * - * @return an array of {@link java.lang.String} objects. - */ - public final String[] getValidTgts() { - return this.validTgts; - } - - /** - *

- * getVTSelection. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getVTSelection() { - return this.uiPrompt; - } - - /** - * Gets the min targets. - * - * @return the min targets - */ - private final String getMinTargets() { - return this.minTargets; - } - - /** - * Gets the max targets. - * - * @return the max targets - */ - private final String getMaxTargets() { - return this.maxTargets; - } - - /** - *

- * Getter for the field minTargets. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a int. - */ - public final int getMinTargets(final Card c, final SpellAbility sa) { - return AbilityUtils.calculateAmount(c, this.minTargets, sa); - } - - /** - *

- * Getter for the field maxTargets. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a int. - */ - public final int getMaxTargets(final Card c, final SpellAbility sa) { - return AbilityUtils.calculateAmount(c, this.maxTargets, sa); - } - - /** - *

- * isMaxTargetsChosen. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean. - */ - public final boolean isMaxTargetsChosen(final Card c, final SpellAbility sa) { - TargetChoices choice = sa.getTargets(); - return this.getMaxTargets(c, sa) == choice.getNumTargeted(); - } - - /** - *

- * isMinTargetsChosen. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @return a boolean. - */ - public final boolean isMinTargetsChosen(final Card c, final SpellAbility sa) { - if (this.getMinTargets(c, sa) == 0) { - return true; - } - TargetChoices choice = sa.getTargets(); - return this.getMinTargets(c, sa) <= choice.getNumTargeted(); - } - - /** - *

- * setZone. - *

- * - * @param tZone - * a {@link java.lang.String} object. - */ - public final void setZone(final ZoneType tZone) { - this.tgtZone = Arrays.asList(tZone); - } - - /** - * Sets the zone. - * - * @param tZone - * the new zone - */ - public final void setZone(final List tZone) { - this.tgtZone = tZone; - } - - /** - *

- * getZone. - *

- * - * @return a {@link java.lang.String} object. - */ - public final List getZone() { - return this.tgtZone; - } - - /** - *

- * setSAValidTargeting. - *

- * - * @param saValidTgting - * a {@link java.lang.String} object. - */ - public final void setSAValidTargeting(final String saValidTgting) { - this.saValidTargeting = saValidTgting; - } - - /** - *

- * getSAValidTargeting. - *

- * - * @return a {@link java.lang.String} object. - */ - public final String getSAValidTargeting() { - return this.saValidTargeting; - } - - - /** - *

- * canOnlyTgtOpponent. - *

- * - * @return a boolean. - */ - public final boolean canOnlyTgtOpponent() { - boolean player = false; - boolean opponent = false; - for (final String s : this.validTgts) { - if (s.startsWith("Opponent")) { - opponent = true; - } else if (s.startsWith("Player")) { - player = true; - } - } - return opponent && !player; - } - - /** - *

- * canTgtPlayer. - *

- * - * @return a boolean. - */ - public final boolean canTgtPlayer() { - for (final String s : this.validTgts) { - if (s.startsWith("Player") || s.startsWith("Opponent")) { - return true; - } - } - return false; - } - - /** - *

- * canTgtCreature. - *

- * - * @return a boolean. - */ - - public final boolean canTgtPermanent() { - for (final String s : this.validTgts) { - if (s.contains("Permanent")) { - return true; - } - } - return false; - } - - /** - * Can tgt creature. - * - * @return true, if successful - */ - public final boolean canTgtCreature() { - for (final String s : this.validTgts) { - if ((s.contains("Creature") || CardType.isACreatureType(s) || s.startsWith("Permanent")) - && !s.contains("nonCreature")) { - return true; - } - } - return false; - } - - /** - *

- * canTgtCreatureAndPlayer. - *

- * - * @return a boolean. - */ - public final boolean canTgtCreatureAndPlayer() { - return this.canTgtPlayer() && this.canTgtCreature(); - } - - /** - *

- * hasCandidates. - *

- * - * @param sa - * the sa - * @param isTargeted - * Check Valid Candidates and Targeting - * @return a boolean. - */ - public final boolean hasCandidates(final SpellAbility sa, final boolean isTargeted) { - final Game game = sa.getSourceCard().getGame(); - for (Player player : game.getPlayers()) { - if (sa.canTarget(player)) { - return true; - } - } - - final Card srcCard = sa.getSourceCard(); // should there be OrginalHost at any moment? - if (this.tgtZone.contains(ZoneType.Stack)) { - // Stack Zone targets are considered later - return true; - } else { - for (final Card c : game.getCardsIn(this.tgtZone)) { - if (!c.isValid(this.validTgts, srcCard.getController(), srcCard)) { - continue; - } - if (isTargeted && !sa.canTarget(c)) { - continue; - } - if (sa.getTargets().isTargeting(c)) { - continue; - } - return true; - } - } - - return false; - } - - /** - *

- * getNumCandidates. - *

- * - * @param sa - * the sa - * @param isTargeted - * Check Valid Candidates and Targeting - * @return a int. - */ - public final int getNumCandidates(final SpellAbility sa, final boolean isTargeted) { - return getAllCandidates(sa, isTargeted).size(); - } - - /** - *

- * getAllCandidates. - *

- * - * @param sa - * the sa - * @param isTargeted - * Check Valid Candidates and Targeting - * @return a List. - */ - public final List getAllCandidates(final SpellAbility sa, final boolean isTargeted) { - final Game game = sa.getActivatingPlayer().getGame(); - List candidates = new ArrayList(); - for (Player player : game.getPlayers()) { - if (sa.canTarget(player)) { - candidates.add(player); - } - } - - final Card srcCard = sa.getSourceCard(); // should there be OrginalHost at any moment? - if (this.tgtZone.contains(ZoneType.Stack)) { - for (final Card c : game.getStackZone().getCards()) { - if (c.isValid(this.validTgts, srcCard.getController(), srcCard) - && (!isTargeted || sa.canTarget(c)) - && !sa.getTargets().isTargeting(c)) { - candidates.add(c); - } - } - } else { - for (final Card c : game.getCardsIn(this.tgtZone)) { - if (c.isValid(this.validTgts, srcCard.getController(), srcCard) - && (!isTargeted || sa.canTarget(c)) - && !sa.getTargets().isTargeting(c)) { - candidates.add(c); - } - } - } - - return candidates; - } - - /** - * Checks if is unique targets. - * - * @return true, if is unique targets - */ - public final boolean isUniqueTargets() { - return this.uniqueTargets; - } - - /** - * Sets the unique targets. - * - * @param unique - * the new unique targets - */ - public final void setUniqueTargets(final boolean unique) { - this.uniqueTargets = unique; - } - - /** - * Checks if targets must be from a single zone. - * - * @return true, if singleZone - */ - public final boolean isSingleZone() { - return this.singleZone; - } - - /** - * Sets if targets must be from a single zone. - * - * @param single - * the new singleZone - */ - public final void setSingleZone(final boolean single) { - this.singleZone = single; - } - - /** - * @return the withoutSameCreatureType - */ - public boolean isWithoutSameCreatureType() { - return withoutSameCreatureType; - } - - /** - * @param b the withoutSameCreatureType to set - */ - public void setWithoutSameCreatureType(boolean b) { - this.withoutSameCreatureType = b; - } - - /** - *

- * copy. - *

- * - * @return a {@link forge.game.spellability.TargetRestrictions} object. - */ - public TargetRestrictions copy() { - TargetRestrictions clone = null; - try { - clone = (TargetRestrictions) this.clone(); - } catch (final CloneNotSupportedException e) { - System.err.println(e); - } - return clone; - } - - /** - * @return the differentZone - */ - public boolean isDifferentZone() { - return differentZone; - } - - /** - * @param different the differentZone to set - */ - public void setDifferentZone(boolean different) { - this.differentZone = different; - } - - /** - * @return the randomTarget - */ - public boolean isRandomTarget() { - return randomTarget; - } - - /** - * @param random the randomTarget to set - */ - public void setRandomTarget(boolean random) { - this.randomTarget = random; - } - - /** - * @return the differentControllers - */ - public boolean isDifferentControllers() { - return differentControllers; - } - - /** - * @param different the differentControllers to set - */ - public void setDifferentControllers(boolean different) { - this.differentControllers = different; - } - /** - * Checks if is same controller. - * - * @return true, if it targets same controller - */ - public final boolean isSameController() { - return this.sameController; - } - - /** - * Sets the same controller. - * - * @param same - * the new unique targets - */ - public final void setSameController(final boolean same) { - this.sameController = same; - } - - /** - * @return the relatedProperty - */ - public String getRelatedProperty() { - return relatedProperty; - } - - /** - * @param related the relatedProperty to set - */ - public void setRelatedProperty(String related) { - this.relatedProperty = related; - } - - /** - * @return the singleTarget - */ - public boolean isSingleTarget() { - return singleTarget; - } - - /** - * @param singleTarget the singleTarget to set - */ - public void setSingleTarget(boolean singleTarget) { - this.singleTarget = singleTarget; - } - - /** - * @return a boolean dividedAsYouChoose - */ - public boolean isDividedAsYouChoose() { - return this.dividedAsYouChoose; - } - - /** - * @param divided the boolean to set - */ - public void setDividedAsYouChoose(boolean divided) { - this.dividedAsYouChoose = divided; - } - - /** - * Get the amount remaining to distribute. - * @return int stillToDivide - */ - public int getStillToDivide() { - return this.stillToDivide; - } - - /** - * @param remaining set the amount still to be divided - */ - public void setStillToDivide(final int remaining) { - this.stillToDivide = remaining; - } - - public void calculateStillToDivide(String toDistribute, Card source, SpellAbility sa) { - // Recalculate this value just in case it's variable - if (!this.dividedAsYouChoose) { - return; - } - - if (StringUtils.isNumeric(toDistribute)) { - this.setStillToDivide(Integer.parseInt(toDistribute)); - } else if ( source == null ) { - return; // such calls come from AbilityFactory.readTarget - at this moment we don't yet know X or any other variables - } else if (source.getSVar(toDistribute).equals("xPaid")) { - this.setStillToDivide(source.getXManaCostPaid()); - } else { - this.setStillToDivide(AbilityUtils.calculateAmount(source, toDistribute, sa)); - } - } - - /** - * Store divided amount relative to a specific card/player. - * @param tgt the targeted object - * @param portionAllocated the divided portion allocated - */ - public final void addDividedAllocation(final Object tgt, final Integer portionAllocated) { - if (this.dividedMap.containsKey(tgt)) { - this.dividedMap.remove(tgt); - } - this.dividedMap.put(tgt, portionAllocated); - } - - /** - * Get the divided amount relative to a specific card/player. - * @param tgt the targeted object - * @return an int. - */ - public int getDividedValue(Object tgt) { - return this.dividedMap.get(tgt); - } -} +/* + * 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 . + */ +package forge.game.spellability; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +import forge.card.CardType; +import forge.game.Game; +import forge.game.GameObject; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.zone.ZoneType; + +/** + *

+ * Target class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TargetRestrictions { + // Target has two things happening: + // Targeting restrictions (Creature, Min/Maxm etc) which are true for this + + // Target Choices (which is specific for the StackInstance) + + // What this Object is restricted to targeting + private boolean tgtValid = false; + private String[] validTgts; + private String uiPrompt = ""; + private List tgtZone = Arrays.asList(ZoneType.Battlefield); + + // The target SA of this SA must be targeting a Valid X + private String saValidTargeting = null; + + // Additional restrictions that may not fit into Valid + private boolean uniqueTargets = false; + private boolean singleZone = false; + private boolean differentZone = false; + private boolean differentControllers = false; + private boolean sameController = false; + private boolean withoutSameCreatureType = false; + private boolean singleTarget = false; + private boolean randomTarget = false; + private String relatedProperty = null; + + // How many can be targeted? + private String minTargets; + private String maxTargets; + + // For "Divided" cards. Is this better in TargetChoices? + private boolean dividedAsYouChoose = false; + private HashMap dividedMap = new HashMap(); + private int stillToDivide = 0; + + // Not sure what's up with Mandatory? Why wouldn't targeting be mandatory? + private boolean bMandatory = false; + + /** + *

+ * Copy Constructor for Target. + *

+ * + * @param target + * a {@link forge.game.spellability.TargetRestrictions} object. + */ + public TargetRestrictions(final TargetRestrictions target) { + this.tgtValid = true; + this.uiPrompt = target.getVTSelection(); + this.validTgts = target.getValidTgts(); + this.minTargets = target.getMinTargets(); + this.maxTargets = target.getMaxTargets(); + this.tgtZone = target.getZone(); + this.saValidTargeting = target.getSAValidTargeting(); + this.dividedAsYouChoose = target.isDividedAsYouChoose(); + this.uniqueTargets = target.isUniqueTargets(); + this.singleZone = target.isSingleZone(); + this.differentControllers = target.isDifferentControllers(); + this.differentZone = target.isDifferentZone(); + this.sameController = target.isSameController(); + this.withoutSameCreatureType = target.isWithoutSameCreatureType(); + this.relatedProperty = target.getRelatedProperty(); + this.singleTarget = target.isSingleTarget(); + this.randomTarget = target.isRandomTarget(); + } + + /** + *

+ * Constructor for Target. + *

+ * + * @param src + * a {@link forge.game.card.Card} object. + * @param prompt + * a {@link java.lang.String} object. + * @param valid + * an array of {@link java.lang.String} objects. + * @param min + * a {@link java.lang.String} object. + * @param max + * a {@link java.lang.String} object. + */ + public TargetRestrictions(final String prompt, final String[] valid, final String min, final String max) { + this.tgtValid = true; + this.uiPrompt = prompt; + this.validTgts = valid; + this.minTargets = min; + this.maxTargets = max; + } + + /** + *

+ * getMandatory. + *

+ * + * @return a boolean. + */ + public final boolean getMandatory() { + return this.bMandatory; + } + + /** + *

+ * setMandatory. + *

+ * + * @param m + * a boolean. + */ + public final void setMandatory(final boolean m) { + this.bMandatory = m; + } + + /** + *

+ * doesTarget. + *

+ * + * @return a boolean. + */ + public final boolean doesTarget() { + return this.tgtValid; + } + + /** + *

+ * getValidTgts. + *

+ * + * @return an array of {@link java.lang.String} objects. + */ + public final String[] getValidTgts() { + return this.validTgts; + } + + /** + *

+ * getVTSelection. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getVTSelection() { + return this.uiPrompt; + } + + /** + * Gets the min targets. + * + * @return the min targets + */ + private final String getMinTargets() { + return this.minTargets; + } + + /** + * Gets the max targets. + * + * @return the max targets + */ + private final String getMaxTargets() { + return this.maxTargets; + } + + /** + *

+ * Getter for the field minTargets. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a int. + */ + public final int getMinTargets(final Card c, final SpellAbility sa) { + return AbilityUtils.calculateAmount(c, this.minTargets, sa); + } + + /** + *

+ * Getter for the field maxTargets. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a int. + */ + public final int getMaxTargets(final Card c, final SpellAbility sa) { + return AbilityUtils.calculateAmount(c, this.maxTargets, sa); + } + + /** + *

+ * isMaxTargetsChosen. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean. + */ + public final boolean isMaxTargetsChosen(final Card c, final SpellAbility sa) { + TargetChoices choice = sa.getTargets(); + return this.getMaxTargets(c, sa) == choice.getNumTargeted(); + } + + /** + *

+ * isMinTargetsChosen. + *

+ * + * @param c + * a {@link forge.game.card.Card} object. + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @return a boolean. + */ + public final boolean isMinTargetsChosen(final Card c, final SpellAbility sa) { + if (this.getMinTargets(c, sa) == 0) { + return true; + } + TargetChoices choice = sa.getTargets(); + return this.getMinTargets(c, sa) <= choice.getNumTargeted(); + } + + /** + *

+ * setZone. + *

+ * + * @param tZone + * a {@link java.lang.String} object. + */ + public final void setZone(final ZoneType tZone) { + this.tgtZone = Arrays.asList(tZone); + } + + /** + * Sets the zone. + * + * @param tZone + * the new zone + */ + public final void setZone(final List tZone) { + this.tgtZone = tZone; + } + + /** + *

+ * getZone. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final List getZone() { + return this.tgtZone; + } + + /** + *

+ * setSAValidTargeting. + *

+ * + * @param saValidTgting + * a {@link java.lang.String} object. + */ + public final void setSAValidTargeting(final String saValidTgting) { + this.saValidTargeting = saValidTgting; + } + + /** + *

+ * getSAValidTargeting. + *

+ * + * @return a {@link java.lang.String} object. + */ + public final String getSAValidTargeting() { + return this.saValidTargeting; + } + + + /** + *

+ * canOnlyTgtOpponent. + *

+ * + * @return a boolean. + */ + public final boolean canOnlyTgtOpponent() { + boolean player = false; + boolean opponent = false; + for (final String s : this.validTgts) { + if (s.startsWith("Opponent")) { + opponent = true; + } else if (s.startsWith("Player")) { + player = true; + } + } + return opponent && !player; + } + + /** + *

+ * canTgtPlayer. + *

+ * + * @return a boolean. + */ + public final boolean canTgtPlayer() { + for (final String s : this.validTgts) { + if (s.startsWith("Player") || s.startsWith("Opponent")) { + return true; + } + } + return false; + } + + /** + *

+ * canTgtCreature. + *

+ * + * @return a boolean. + */ + + public final boolean canTgtPermanent() { + for (final String s : this.validTgts) { + if (s.contains("Permanent")) { + return true; + } + } + return false; + } + + /** + * Can tgt creature. + * + * @return true, if successful + */ + public final boolean canTgtCreature() { + for (final String s : this.validTgts) { + if ((s.contains("Creature") || CardType.isACreatureType(s) || s.startsWith("Permanent")) + && !s.contains("nonCreature")) { + return true; + } + } + return false; + } + + /** + *

+ * canTgtCreatureAndPlayer. + *

+ * + * @return a boolean. + */ + public final boolean canTgtCreatureAndPlayer() { + return this.canTgtPlayer() && this.canTgtCreature(); + } + + /** + *

+ * hasCandidates. + *

+ * + * @param sa + * the sa + * @param isTargeted + * Check Valid Candidates and Targeting + * @return a boolean. + */ + public final boolean hasCandidates(final SpellAbility sa, final boolean isTargeted) { + final Game game = sa.getSourceCard().getGame(); + for (Player player : game.getPlayers()) { + if (sa.canTarget(player)) { + return true; + } + } + + final Card srcCard = sa.getSourceCard(); // should there be OrginalHost at any moment? + if (this.tgtZone.contains(ZoneType.Stack)) { + // Stack Zone targets are considered later + return true; + } else { + for (final Card c : game.getCardsIn(this.tgtZone)) { + if (!c.isValid(this.validTgts, srcCard.getController(), srcCard)) { + continue; + } + if (isTargeted && !sa.canTarget(c)) { + continue; + } + if (sa.getTargets().isTargeting(c)) { + continue; + } + return true; + } + } + + return false; + } + + /** + *

+ * getNumCandidates. + *

+ * + * @param sa + * the sa + * @param isTargeted + * Check Valid Candidates and Targeting + * @return a int. + */ + public final int getNumCandidates(final SpellAbility sa, final boolean isTargeted) { + return getAllCandidates(sa, isTargeted).size(); + } + + /** + *

+ * getAllCandidates. + *

+ * + * @param sa + * the sa + * @param isTargeted + * Check Valid Candidates and Targeting + * @return a List. + */ + public final List getAllCandidates(final SpellAbility sa, final boolean isTargeted) { + final Game game = sa.getActivatingPlayer().getGame(); + List candidates = new ArrayList(); + for (Player player : game.getPlayers()) { + if (sa.canTarget(player)) { + candidates.add(player); + } + } + + final Card srcCard = sa.getSourceCard(); // should there be OrginalHost at any moment? + if (this.tgtZone.contains(ZoneType.Stack)) { + for (final Card c : game.getStackZone().getCards()) { + if (c.isValid(this.validTgts, srcCard.getController(), srcCard) + && (!isTargeted || sa.canTarget(c)) + && !sa.getTargets().isTargeting(c)) { + candidates.add(c); + } + } + } else { + for (final Card c : game.getCardsIn(this.tgtZone)) { + if (c.isValid(this.validTgts, srcCard.getController(), srcCard) + && (!isTargeted || sa.canTarget(c)) + && !sa.getTargets().isTargeting(c)) { + candidates.add(c); + } + } + } + + return candidates; + } + + /** + * Checks if is unique targets. + * + * @return true, if is unique targets + */ + public final boolean isUniqueTargets() { + return this.uniqueTargets; + } + + /** + * Sets the unique targets. + * + * @param unique + * the new unique targets + */ + public final void setUniqueTargets(final boolean unique) { + this.uniqueTargets = unique; + } + + /** + * Checks if targets must be from a single zone. + * + * @return true, if singleZone + */ + public final boolean isSingleZone() { + return this.singleZone; + } + + /** + * Sets if targets must be from a single zone. + * + * @param single + * the new singleZone + */ + public final void setSingleZone(final boolean single) { + this.singleZone = single; + } + + /** + * @return the withoutSameCreatureType + */ + public boolean isWithoutSameCreatureType() { + return withoutSameCreatureType; + } + + /** + * @param b the withoutSameCreatureType to set + */ + public void setWithoutSameCreatureType(boolean b) { + this.withoutSameCreatureType = b; + } + + /** + *

+ * copy. + *

+ * + * @return a {@link forge.game.spellability.TargetRestrictions} object. + */ + public TargetRestrictions copy() { + TargetRestrictions clone = null; + try { + clone = (TargetRestrictions) this.clone(); + } catch (final CloneNotSupportedException e) { + System.err.println(e); + } + return clone; + } + + /** + * @return the differentZone + */ + public boolean isDifferentZone() { + return differentZone; + } + + /** + * @param different the differentZone to set + */ + public void setDifferentZone(boolean different) { + this.differentZone = different; + } + + /** + * @return the randomTarget + */ + public boolean isRandomTarget() { + return randomTarget; + } + + /** + * @param random the randomTarget to set + */ + public void setRandomTarget(boolean random) { + this.randomTarget = random; + } + + /** + * @return the differentControllers + */ + public boolean isDifferentControllers() { + return differentControllers; + } + + /** + * @param different the differentControllers to set + */ + public void setDifferentControllers(boolean different) { + this.differentControllers = different; + } + /** + * Checks if is same controller. + * + * @return true, if it targets same controller + */ + public final boolean isSameController() { + return this.sameController; + } + + /** + * Sets the same controller. + * + * @param same + * the new unique targets + */ + public final void setSameController(final boolean same) { + this.sameController = same; + } + + /** + * @return the relatedProperty + */ + public String getRelatedProperty() { + return relatedProperty; + } + + /** + * @param related the relatedProperty to set + */ + public void setRelatedProperty(String related) { + this.relatedProperty = related; + } + + /** + * @return the singleTarget + */ + public boolean isSingleTarget() { + return singleTarget; + } + + /** + * @param singleTarget the singleTarget to set + */ + public void setSingleTarget(boolean singleTarget) { + this.singleTarget = singleTarget; + } + + /** + * @return a boolean dividedAsYouChoose + */ + public boolean isDividedAsYouChoose() { + return this.dividedAsYouChoose; + } + + /** + * @param divided the boolean to set + */ + public void setDividedAsYouChoose(boolean divided) { + this.dividedAsYouChoose = divided; + } + + /** + * Get the amount remaining to distribute. + * @return int stillToDivide + */ + public int getStillToDivide() { + return this.stillToDivide; + } + + /** + * @param remaining set the amount still to be divided + */ + public void setStillToDivide(final int remaining) { + this.stillToDivide = remaining; + } + + public void calculateStillToDivide(String toDistribute, Card source, SpellAbility sa) { + // Recalculate this value just in case it's variable + if (!this.dividedAsYouChoose) { + return; + } + + if (StringUtils.isNumeric(toDistribute)) { + this.setStillToDivide(Integer.parseInt(toDistribute)); + } else if ( source == null ) { + return; // such calls come from AbilityFactory.readTarget - at this moment we don't yet know X or any other variables + } else if (source.getSVar(toDistribute).equals("xPaid")) { + this.setStillToDivide(source.getXManaCostPaid()); + } else { + this.setStillToDivide(AbilityUtils.calculateAmount(source, toDistribute, sa)); + } + } + + /** + * Store divided amount relative to a specific card/player. + * @param tgt the targeted object + * @param portionAllocated the divided portion allocated + */ + public final void addDividedAllocation(final Object tgt, final Integer portionAllocated) { + if (this.dividedMap.containsKey(tgt)) { + this.dividedMap.remove(tgt); + } + this.dividedMap.put(tgt, portionAllocated); + } + + /** + * Get the divided amount relative to a specific card/player. + * @param tgt the targeted object + * @return an int. + */ + public int getDividedValue(Object tgt) { + return this.dividedMap.get(tgt); + } +} diff --git a/forge-game/src/main/java/forge/game/spellability/package-info.java b/forge-gui/src/main/java/forge/game/spellability/package-info.java similarity index 95% rename from forge-game/src/main/java/forge/game/spellability/package-info.java rename to forge-gui/src/main/java/forge/game/spellability/package-info.java index 80c84795b61..d676566e1ca 100644 --- a/forge-game/src/main/java/forge/game/spellability/package-info.java +++ b/forge-gui/src/main/java/forge/game/spellability/package-info.java @@ -1,3 +1,3 @@ -/** Forge Card Game. */ -package forge.game.spellability; - +/** Forge Card Game. */ +package forge.game.spellability; + diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-gui/src/main/java/forge/game/staticability/StaticAbility.java similarity index 97% rename from forge-game/src/main/java/forge/game/staticability/StaticAbility.java rename to forge-gui/src/main/java/forge/game/staticability/StaticAbility.java index f9d9a4ec531..22fcfe2c276 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-gui/src/main/java/forge/game/staticability/StaticAbility.java @@ -1,645 +1,645 @@ -/* - * 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 . - */ -package forge.game.staticability; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import forge.card.MagicColor; -import forge.game.GameEntity; -import forge.game.ability.AbilityUtils; -import forge.game.card.Card; -import forge.game.cost.Cost; -import forge.game.mana.ManaCostBeingPaid; -import forge.game.phase.PhaseType; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; -import forge.game.zone.ZoneType; -import forge.util.Expressions; - -/** - * The Class StaticAbility. - */ -public class StaticAbility { - - private Card hostCard = null; - - private HashMap params = new HashMap(); - - private int layer = 0; - - /** The temporarily suppressed. */ - private boolean temporarilySuppressed = false; - - /** The suppressed. */ - private final boolean suppressed = false; - - private boolean temporary = false; - - /** - *

- * getHostCard. - *

- * - * @return a {@link forge.game.card.Card} object. - */ - public final Card getHostCard() { - return this.hostCard; - } - - /** - *

- * Getter for the field mapParams. - *

- * - * @return a {@link java.util.HashMap} object. - */ - public final HashMap getMapParams() { - return this.params; - } - - // ******************************************************* - - /** - *

- * Getter for the field mapParams. - *

- * - * @param abString - * a {@link java.lang.String} object. - * @param hostCard - * a {@link forge.game.card.Card} object. - * @return a {@link java.util.HashMap} object. - */ - public final HashMap getMapParams(final String abString, final Card hostCard) { - final HashMap mapParameters = new HashMap(); - - if (!(abString.length() > 0)) { - throw new RuntimeException("StaticEffectFactory : getAbility -- abString too short in " - + hostCard.getName() + ": [" + abString + "]"); - } - - final String[] a = abString.split("\\|"); - - for (int aCnt = 0; aCnt < a.length; aCnt++) { - a[aCnt] = a[aCnt].trim(); - } - - if (!(a.length > 0)) { - throw new RuntimeException("StaticEffectFactory : getAbility -- a[] too short in " + hostCard.getName()); - } - - for (final String element : a) { - final String[] aa = element.split("\\$"); - - for (int aaCnt = 0; aaCnt < aa.length; aaCnt++) { - aa[aaCnt] = aa[aaCnt].trim(); - } - - if (aa.length != 2) { - final StringBuilder sb = new StringBuilder(); - sb.append("StaticEffectFactory Parsing Error: Split length of "); - sb.append(element).append(" in ").append(hostCard.getName()).append(" is not 2."); - throw new RuntimeException(sb.toString()); - } - - mapParameters.put(aa[0], aa[1]); - } - - return mapParameters; - } - - // In which layer should the ability be applied (for continuous effects - // only) - /** - * Gets the layer. - * - * @return the layer - */ - public final int generateLayer() { - - if (!this.params.get("Mode").equals("Continuous")) { - return 0; - } - - if (this.params.containsKey("GainControl")) { - return 2; - } - - if (this.params.containsKey("AddType") || this.params.containsKey("RemoveType") - || this.params.containsKey("RemoveCardTypes") || this.params.containsKey("RemoveSubTypes") - || this.params.containsKey("RemoveSuperTypes") || this.params.containsKey("RemoveCreatureTypes")) { - return 4; - } - - if (this.params.containsKey("AddColor") || this.params.containsKey("RemoveColor") - || this.params.containsKey("SetColor")) { - return 5; - } - - if (this.params.containsKey("RemoveAllAbilities") || this.params.containsKey("GainsAbilitiesOf")) { - return 6; // Layer 6 - } - - if (this.params.containsKey("AddKeyword") || this.params.containsKey("AddAbility") - || this.params.containsKey("AddTrigger") || this.params.containsKey("RemoveTriggers") - || this.params.containsKey("RemoveKeyword") || this.params.containsKey("AddReplacementEffects")) { - return 7; // Layer 6 (dependent) - } - - if (this.params.containsKey("CharacteristicDefining")) { - return 8; // Layer 7a - } - - if (this.params.containsKey("AddPower") || this.params.containsKey("AddToughness") - || this.params.containsKey("SetPower") || this.params.containsKey("SetToughness")) { - return 9; // This is the collection of 7b and 7c - } - - return 10; // rules change - } - - /** - *

- * toString. - *

- * - * @return a {@link java.lang.String} object. - */ - @Override - public final String toString() { - if (this.params.containsKey("Description") && !this.isSuppressed()) { - return this.params.get("Description").replace("CARDNAME", this.hostCard.getName()); - } else { - return ""; - } - } - - // main constructor - /** - * Instantiates a new static ability. - * - * @param params - * the params - * @param host - * the host - */ - public StaticAbility(final String params, final Card host) { - this.params = this.getMapParams(params, host); - this.hostCard = host; - this.layer = this.generateLayer(); - } - - /** - * Instantiates a new static ability. - * - * @param params - * the params - * @param host - * the host - */ - public StaticAbility(final HashMap params, final Card host) { - this.params = new HashMap(); - for (final Map.Entry entry : params.entrySet()) { - this.params.put(entry.getKey(), entry.getValue()); - } - this.layer = this.generateLayer(); - this.hostCard = host; - } - - // apply the ability if it has the right mode - /** - * Apply ability. - * - * @param mode - * the mode - * @return - */ - public final List applyAbility(final String mode) { - - // don't apply the ability if it hasn't got the right mode - if (!this.params.get("Mode").equals(mode)) { - return null; - } - - if (this.isSuppressed() || !this.checkConditions()) { - return null; - } - - if (mode.equals("Continuous")) { - return StaticAbilityContinuous.applyContinuousAbility(this); - } - - return null; - } - - // apply the ability if it has the right mode - /** - * Apply ability. - * - * @param mode - * the mode - * @param source - * the source - * @param target - * the target - * @param in - * the in - * @param isCombat - * the b - * @return the int - */ - public final int applyAbility(final String mode, final Card source, final GameEntity target, final int in, - final boolean isCombat, final boolean isTest) { - - // don't apply the ability if it hasn't got the right mode - if (!this.params.get("Mode").equals(mode)) { - return in; - } - - if (this.isSuppressed() || !this.checkConditions()) { - return in; - } - - if (mode.equals("PreventDamage")) { - return StaticAbilityPreventDamage.applyPreventDamageAbility(this, source, target, in, isCombat, isTest); - } - - return in; - } - - // apply the ability if it has the right mode - /** - * Apply ability. - * - * @param mode - * the mode - * @param card - * the card - * @param player - * the activator - * @return true, if successful - */ - public final boolean applyAbility(final String mode, final Card card, final Player player) { - - // don't apply the ability if it hasn't got the right mode - if (!this.params.get("Mode").equals(mode)) { - return false; - } - - if (this.isSuppressed() || !this.checkConditions()) { - return false; - } - - if (mode.equals("CantBeCast")) { - return StaticAbilityCantBeCast.applyCantBeCastAbility(this, card, player); - } - - if (mode.equals("CantPlayLand")) { - return StaticAbilityCantBeCast.applyCantPlayLandAbility(this, card, player); - } - - if (mode.equals("MayLookAt")) { - return StaticAbilityMayLookAt.applyMayLookAtAbility(this, card, player); - } - - return false; - } - - /** - * Apply ability. - * - * @param mode - * the mode - * @param card - * the card - * @param spellAbility - * the ability - * @return true, if successful - */ - public final boolean applyAbility(final String mode, final Card card, final SpellAbility spellAbility) { - - // don't apply the ability if it hasn't got the right mode - if (!this.params.get("Mode").equals(mode)) { - return false; - } - - if (this.isSuppressed() || !this.checkConditions()) { - return false; - } - - if (mode.equals("CantBeActivated")) { - return StaticAbilityCantBeCast.applyCantBeActivatedAbility(this, card, spellAbility); - } - - if (mode.equals("CantTarget")) { - return StaticAbilityCantTarget.applyCantTargetAbility(this, card, spellAbility); - } - - return false; - } - - /** - * Apply ability. - * - * @param mode - * the mode - * @param sa - * the SpellAbility - * @param originalCost - * the originalCost - * @return the modified ManaCost - */ - public final void applyAbility(final String mode, final SpellAbility sa, final ManaCostBeingPaid originalCost) { - - // don't apply the ability if it hasn't got the right mode - if (!this.params.get("Mode").equals(mode)) { - return; - } - - if (this.isSuppressed() || !this.checkConditions()) { - return; - } - - if (mode.equals("RaiseCost")) { - StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost); - } - if (mode.equals("ReduceCost")) { - StaticAbilityCostChange.applyReduceCostAbility(this, sa, originalCost); - } - if (mode.equals("SetCost")) { //Set cost is only used by Trinisphere - StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost); - } - } - - /** - * Apply ability. - * - * @param mode - * the mode - * @param card - * the card - * @return true, if successful - */ - public final boolean applyAbility(final String mode, final Card card) { - - // don't apply the ability if it hasn't got the right mode - if (!this.params.get("Mode").equals(mode)) { - return false; - } - - if (this.isSuppressed() || !this.checkConditions()) { - return false; - } - - if (mode.equals("ETBTapped")) { - return StaticAbilityETBTapped.applyETBTappedAbility(this, card); - } - - if (mode.equals("GainAbilitiesOf")) { - - } - - return false; - } - - /** - * Apply ability. - * - * @param mode - * the mode - * @param card - * the card - * @param target - * the target - * @return true, if successful - */ - public final boolean applyAbility(final String mode, final Card card, final GameEntity target) { - - // don't apply the ability if it hasn't got the right mode - if (!this.params.get("Mode").equals(mode)) { - return false; - } - - if (this.isSuppressed() || !this.checkConditions()) { - return false; - } - - if (mode.equals("CantAttack")) { - return StaticAbilityCantAttackBlock.applyCantAttackAbility(this, card, target); - } - - return false; - } - - public final Cost getAttackCost(final Card attacker, final GameEntity target) { - if (this.isSuppressed() || !params.get("Mode").equals("CantAttackUnless") || !this.checkConditions()) { - return null; - } - return StaticAbilityCantAttackBlock.getAttackCost(this, attacker, target); - } - - public final Cost getBlockCost(final Card blocker, final Card attacker) { - if (this.isSuppressed() || !params.get("Mode").equals("CantBlockUnless") || !this.checkConditions()) { - return null; - } - return StaticAbilityCantAttackBlock.getBlockCost(this, blocker, attacker); - } - - /** - * Check conditions. - * - * @return true, if successful - */ - public final boolean checkConditions() { - final Player controller = this.hostCard.getController(); - - if (this.hostCard.isPhasedOut()) { - return false; - } - - if (this.params.containsKey("EffectZone")) { - if (!this.params.get("EffectZone").equals("All") - && !ZoneType.listValueOf(this.params.get("EffectZone")) - .contains(controller.getGame().getZoneOf(this.hostCard).getZoneType())) { - return false; - } - } else { - if (!this.hostCard.isInZone(ZoneType.Battlefield)) { // default - return false; - } - } - - String condition = params.get("Condition"); - if (null != condition) { - if (condition.equals("Threshold") && !controller.hasThreshold()) return false; - if (condition.equals("Hellbent") && !controller.hasHellbent()) return false; - if (condition.equals("Metalcraft") && !controller.hasMetalcraft()) return false; - - if (condition.equals("PlayerTurn")) { - if (!controller.getGame().getPhaseHandler().isPlayerTurn(controller)) { - return false; - } - } else if (condition.equals("NotPlayerTurn")) { - if (controller.getGame().getPhaseHandler().isPlayerTurn(controller)) { - return false; - } - } else if (condition.equals("PermanentOfEachColor")) { - if ((controller.getColoredCardsInPlay(MagicColor.Constant.BLACK).isEmpty() - || controller.getColoredCardsInPlay(MagicColor.Constant.BLUE).isEmpty() - || controller.getColoredCardsInPlay(MagicColor.Constant.GREEN).isEmpty() - || controller.getColoredCardsInPlay(MagicColor.Constant.RED).isEmpty() - || controller.getColoredCardsInPlay(MagicColor.Constant.WHITE).isEmpty())) { - return false; - } - } else if (condition.equals("FatefulHour")) { - if (controller.getLife() > 5) { - return false; - } - } - } - - if (this.params.containsKey("OpponentAttackedWithCreatureThisTurn") - && !controller.getOpponent().getAttackedWithCreatureThisTurn()) { - return false; - } - - if (this.params.containsKey("Phases")) { - List phases = PhaseType.parseRange(this.params.get("Phases")); - if (!phases.contains(controller.getGame().getPhaseHandler().getPhase())) { - return false; - } - } - - if (this.params.containsKey("TopCardOfLibraryIs")) { - if (controller.getCardsIn(ZoneType.Library).isEmpty()) { - return false; - } - final Card topCard = controller.getCardsIn(ZoneType.Library).get(0); - if (!topCard.isValid(this.params.get("TopCardOfLibraryIs").split(","), controller, this.hostCard)) { - return false; - } - } - - if (this.params.containsKey("CheckSVar")) { - final int sVar = AbilityUtils.calculateAmount(this.hostCard, this.params.get("CheckSVar"), null); - String comparator = "GE1"; - if (this.params.containsKey("SVarCompare")) { - comparator = this.params.get("SVarCompare"); - } - final String svarOperator = comparator.substring(0, 2); - final String svarOperand = comparator.substring(2); - final int operandValue = AbilityUtils.calculateAmount(this.hostCard, svarOperand, null); - if (!Expressions.compare(sVar, svarOperator, operandValue)) { - return false; - } - } else { //no need to check the others - return true; - } - - if (this.params.containsKey("CheckSecondSVar")) { - final int sVar = AbilityUtils.calculateAmount(this.hostCard, this.params.get("CheckSecondSVar"), null); - String comparator = "GE1"; - if (this.params.containsKey("SecondSVarCompare")) { - comparator = this.params.get("SecondSVarCompare"); - } - final String svarOperator = comparator.substring(0, 2); - final String svarOperand = comparator.substring(2); - final int operandValue = AbilityUtils.calculateAmount(this.hostCard, svarOperand, null); - if (!Expressions.compare(sVar, svarOperator, operandValue)) { - return false; - } - } else { //no need to check the others - return true; - } - - if (this.params.containsKey("CheckThirdSVar")) { - final int sVar = AbilityUtils.calculateAmount(this.hostCard, this.params.get("CheckThirdSVar"), null); - String comparator = "GE1"; - if (this.params.containsKey("ThirdSVarCompare")) { - comparator = this.params.get("ThirdSVarCompare"); - } - final String svarOperator = comparator.substring(0, 2); - final String svarOperand = comparator.substring(2); - final int operandValue = AbilityUtils.calculateAmount(this.hostCard, svarOperand, null); - if (!Expressions.compare(sVar, svarOperator, operandValue)) { - return false; - } - } else { //no need to check the others - return true; - } - - if (this.params.containsKey("CheckFourthSVar")) { - final int sVar = AbilityUtils.calculateAmount(this.hostCard, this.params.get("CheckFourthSVar"), null); - String comparator = "GE1"; - if (this.params.containsKey("FourthSVarCompare")) { - comparator = this.params.get("FourthSVarCompare"); - } - final String svarOperator = comparator.substring(0, 2); - final String svarOperand = comparator.substring(2); - final int operandValue = AbilityUtils.calculateAmount(this.hostCard, svarOperand, null); - if (!Expressions.compare(sVar, svarOperator, operandValue)) { - return false; - } - } - - return true; - } - - /** - * Sets the temporarily suppressed. - * - * @param supp - * the new temporarily suppressed - */ - public final void setTemporarilySuppressed(final boolean supp) { - this.temporarilySuppressed = supp; - } - - /** - * Checks if is suppressed. - * - * @return true, if is suppressed - */ - public final boolean isSuppressed() { - return (this.suppressed || this.temporarilySuppressed); - } - - /** - * @return the layer - */ - public int getLayer() { - return layer; - } - - /** - * @param layer the layer to set - */ - public void setLayer(int layer) { - this.layer = layer; - } - - public void setTemporarily(boolean b) { - this.temporary = b; - } - public boolean isTemporary() { - return this.temporary; - } - -} // end class StaticEffectFactory +/* + * 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 . + */ +package forge.game.staticability; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import forge.card.MagicColor; +import forge.game.GameEntity; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.cost.Cost; +import forge.game.mana.ManaCostBeingPaid; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; +import forge.util.Expressions; + +/** + * The Class StaticAbility. + */ +public class StaticAbility { + + private Card hostCard = null; + + private HashMap params = new HashMap(); + + private int layer = 0; + + /** The temporarily suppressed. */ + private boolean temporarilySuppressed = false; + + /** The suppressed. */ + private final boolean suppressed = false; + + private boolean temporary = false; + + /** + *

+ * getHostCard. + *

+ * + * @return a {@link forge.game.card.Card} object. + */ + public final Card getHostCard() { + return this.hostCard; + } + + /** + *

+ * Getter for the field mapParams. + *

+ * + * @return a {@link java.util.HashMap} object. + */ + public final HashMap getMapParams() { + return this.params; + } + + // ******************************************************* + + /** + *

+ * Getter for the field mapParams. + *

+ * + * @param abString + * a {@link java.lang.String} object. + * @param hostCard + * a {@link forge.game.card.Card} object. + * @return a {@link java.util.HashMap} object. + */ + public final HashMap getMapParams(final String abString, final Card hostCard) { + final HashMap mapParameters = new HashMap(); + + if (!(abString.length() > 0)) { + throw new RuntimeException("StaticEffectFactory : getAbility -- abString too short in " + + hostCard.getName() + ": [" + abString + "]"); + } + + final String[] a = abString.split("\\|"); + + for (int aCnt = 0; aCnt < a.length; aCnt++) { + a[aCnt] = a[aCnt].trim(); + } + + if (!(a.length > 0)) { + throw new RuntimeException("StaticEffectFactory : getAbility -- a[] too short in " + hostCard.getName()); + } + + for (final String element : a) { + final String[] aa = element.split("\\$"); + + for (int aaCnt = 0; aaCnt < aa.length; aaCnt++) { + aa[aaCnt] = aa[aaCnt].trim(); + } + + if (aa.length != 2) { + final StringBuilder sb = new StringBuilder(); + sb.append("StaticEffectFactory Parsing Error: Split length of "); + sb.append(element).append(" in ").append(hostCard.getName()).append(" is not 2."); + throw new RuntimeException(sb.toString()); + } + + mapParameters.put(aa[0], aa[1]); + } + + return mapParameters; + } + + // In which layer should the ability be applied (for continuous effects + // only) + /** + * Gets the layer. + * + * @return the layer + */ + public final int generateLayer() { + + if (!this.params.get("Mode").equals("Continuous")) { + return 0; + } + + if (this.params.containsKey("GainControl")) { + return 2; + } + + if (this.params.containsKey("AddType") || this.params.containsKey("RemoveType") + || this.params.containsKey("RemoveCardTypes") || this.params.containsKey("RemoveSubTypes") + || this.params.containsKey("RemoveSuperTypes") || this.params.containsKey("RemoveCreatureTypes")) { + return 4; + } + + if (this.params.containsKey("AddColor") || this.params.containsKey("RemoveColor") + || this.params.containsKey("SetColor")) { + return 5; + } + + if (this.params.containsKey("RemoveAllAbilities") || this.params.containsKey("GainsAbilitiesOf")) { + return 6; // Layer 6 + } + + if (this.params.containsKey("AddKeyword") || this.params.containsKey("AddAbility") + || this.params.containsKey("AddTrigger") || this.params.containsKey("RemoveTriggers") + || this.params.containsKey("RemoveKeyword") || this.params.containsKey("AddReplacementEffects")) { + return 7; // Layer 6 (dependent) + } + + if (this.params.containsKey("CharacteristicDefining")) { + return 8; // Layer 7a + } + + if (this.params.containsKey("AddPower") || this.params.containsKey("AddToughness") + || this.params.containsKey("SetPower") || this.params.containsKey("SetToughness")) { + return 9; // This is the collection of 7b and 7c + } + + return 10; // rules change + } + + /** + *

+ * toString. + *

+ * + * @return a {@link java.lang.String} object. + */ + @Override + public final String toString() { + if (this.params.containsKey("Description") && !this.isSuppressed()) { + return this.params.get("Description").replace("CARDNAME", this.hostCard.getName()); + } else { + return ""; + } + } + + // main constructor + /** + * Instantiates a new static ability. + * + * @param params + * the params + * @param host + * the host + */ + public StaticAbility(final String params, final Card host) { + this.params = this.getMapParams(params, host); + this.hostCard = host; + this.layer = this.generateLayer(); + } + + /** + * Instantiates a new static ability. + * + * @param params + * the params + * @param host + * the host + */ + public StaticAbility(final HashMap params, final Card host) { + this.params = new HashMap(); + for (final Map.Entry entry : params.entrySet()) { + this.params.put(entry.getKey(), entry.getValue()); + } + this.layer = this.generateLayer(); + this.hostCard = host; + } + + // apply the ability if it has the right mode + /** + * Apply ability. + * + * @param mode + * the mode + * @return + */ + public final List applyAbility(final String mode) { + + // don't apply the ability if it hasn't got the right mode + if (!this.params.get("Mode").equals(mode)) { + return null; + } + + if (this.isSuppressed() || !this.checkConditions()) { + return null; + } + + if (mode.equals("Continuous")) { + return StaticAbilityContinuous.applyContinuousAbility(this); + } + + return null; + } + + // apply the ability if it has the right mode + /** + * Apply ability. + * + * @param mode + * the mode + * @param source + * the source + * @param target + * the target + * @param in + * the in + * @param isCombat + * the b + * @return the int + */ + public final int applyAbility(final String mode, final Card source, final GameEntity target, final int in, + final boolean isCombat, final boolean isTest) { + + // don't apply the ability if it hasn't got the right mode + if (!this.params.get("Mode").equals(mode)) { + return in; + } + + if (this.isSuppressed() || !this.checkConditions()) { + return in; + } + + if (mode.equals("PreventDamage")) { + return StaticAbilityPreventDamage.applyPreventDamageAbility(this, source, target, in, isCombat, isTest); + } + + return in; + } + + // apply the ability if it has the right mode + /** + * Apply ability. + * + * @param mode + * the mode + * @param card + * the card + * @param player + * the activator + * @return true, if successful + */ + public final boolean applyAbility(final String mode, final Card card, final Player player) { + + // don't apply the ability if it hasn't got the right mode + if (!this.params.get("Mode").equals(mode)) { + return false; + } + + if (this.isSuppressed() || !this.checkConditions()) { + return false; + } + + if (mode.equals("CantBeCast")) { + return StaticAbilityCantBeCast.applyCantBeCastAbility(this, card, player); + } + + if (mode.equals("CantPlayLand")) { + return StaticAbilityCantBeCast.applyCantPlayLandAbility(this, card, player); + } + + if (mode.equals("MayLookAt")) { + return StaticAbilityMayLookAt.applyMayLookAtAbility(this, card, player); + } + + return false; + } + + /** + * Apply ability. + * + * @param mode + * the mode + * @param card + * the card + * @param spellAbility + * the ability + * @return true, if successful + */ + public final boolean applyAbility(final String mode, final Card card, final SpellAbility spellAbility) { + + // don't apply the ability if it hasn't got the right mode + if (!this.params.get("Mode").equals(mode)) { + return false; + } + + if (this.isSuppressed() || !this.checkConditions()) { + return false; + } + + if (mode.equals("CantBeActivated")) { + return StaticAbilityCantBeCast.applyCantBeActivatedAbility(this, card, spellAbility); + } + + if (mode.equals("CantTarget")) { + return StaticAbilityCantTarget.applyCantTargetAbility(this, card, spellAbility); + } + + return false; + } + + /** + * Apply ability. + * + * @param mode + * the mode + * @param sa + * the SpellAbility + * @param originalCost + * the originalCost + * @return the modified ManaCost + */ + public final void applyAbility(final String mode, final SpellAbility sa, final ManaCostBeingPaid originalCost) { + + // don't apply the ability if it hasn't got the right mode + if (!this.params.get("Mode").equals(mode)) { + return; + } + + if (this.isSuppressed() || !this.checkConditions()) { + return; + } + + if (mode.equals("RaiseCost")) { + StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost); + } + if (mode.equals("ReduceCost")) { + StaticAbilityCostChange.applyReduceCostAbility(this, sa, originalCost); + } + if (mode.equals("SetCost")) { //Set cost is only used by Trinisphere + StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost); + } + } + + /** + * Apply ability. + * + * @param mode + * the mode + * @param card + * the card + * @return true, if successful + */ + public final boolean applyAbility(final String mode, final Card card) { + + // don't apply the ability if it hasn't got the right mode + if (!this.params.get("Mode").equals(mode)) { + return false; + } + + if (this.isSuppressed() || !this.checkConditions()) { + return false; + } + + if (mode.equals("ETBTapped")) { + return StaticAbilityETBTapped.applyETBTappedAbility(this, card); + } + + if (mode.equals("GainAbilitiesOf")) { + + } + + return false; + } + + /** + * Apply ability. + * + * @param mode + * the mode + * @param card + * the card + * @param target + * the target + * @return true, if successful + */ + public final boolean applyAbility(final String mode, final Card card, final GameEntity target) { + + // don't apply the ability if it hasn't got the right mode + if (!this.params.get("Mode").equals(mode)) { + return false; + } + + if (this.isSuppressed() || !this.checkConditions()) { + return false; + } + + if (mode.equals("CantAttack")) { + return StaticAbilityCantAttackBlock.applyCantAttackAbility(this, card, target); + } + + return false; + } + + public final Cost getAttackCost(final Card attacker, final GameEntity target) { + if (this.isSuppressed() || !params.get("Mode").equals("CantAttackUnless") || !this.checkConditions()) { + return null; + } + return StaticAbilityCantAttackBlock.getAttackCost(this, attacker, target); + } + + public final Cost getBlockCost(final Card blocker, final Card attacker) { + if (this.isSuppressed() || !params.get("Mode").equals("CantBlockUnless") || !this.checkConditions()) { + return null; + } + return StaticAbilityCantAttackBlock.getBlockCost(this, blocker, attacker); + } + + /** + * Check conditions. + * + * @return true, if successful + */ + public final boolean checkConditions() { + final Player controller = this.hostCard.getController(); + + if (this.hostCard.isPhasedOut()) { + return false; + } + + if (this.params.containsKey("EffectZone")) { + if (!this.params.get("EffectZone").equals("All") + && !ZoneType.listValueOf(this.params.get("EffectZone")) + .contains(controller.getGame().getZoneOf(this.hostCard).getZoneType())) { + return false; + } + } else { + if (!this.hostCard.isInZone(ZoneType.Battlefield)) { // default + return false; + } + } + + String condition = params.get("Condition"); + if (null != condition) { + if (condition.equals("Threshold") && !controller.hasThreshold()) return false; + if (condition.equals("Hellbent") && !controller.hasHellbent()) return false; + if (condition.equals("Metalcraft") && !controller.hasMetalcraft()) return false; + + if (condition.equals("PlayerTurn")) { + if (!controller.getGame().getPhaseHandler().isPlayerTurn(controller)) { + return false; + } + } else if (condition.equals("NotPlayerTurn")) { + if (controller.getGame().getPhaseHandler().isPlayerTurn(controller)) { + return false; + } + } else if (condition.equals("PermanentOfEachColor")) { + if ((controller.getColoredCardsInPlay(MagicColor.Constant.BLACK).isEmpty() + || controller.getColoredCardsInPlay(MagicColor.Constant.BLUE).isEmpty() + || controller.getColoredCardsInPlay(MagicColor.Constant.GREEN).isEmpty() + || controller.getColoredCardsInPlay(MagicColor.Constant.RED).isEmpty() + || controller.getColoredCardsInPlay(MagicColor.Constant.WHITE).isEmpty())) { + return false; + } + } else if (condition.equals("FatefulHour")) { + if (controller.getLife() > 5) { + return false; + } + } + } + + if (this.params.containsKey("OpponentAttackedWithCreatureThisTurn") + && !controller.getOpponent().getAttackedWithCreatureThisTurn()) { + return false; + } + + if (this.params.containsKey("Phases")) { + List phases = PhaseType.parseRange(this.params.get("Phases")); + if (!phases.contains(controller.getGame().getPhaseHandler().getPhase())) { + return false; + } + } + + if (this.params.containsKey("TopCardOfLibraryIs")) { + if (controller.getCardsIn(ZoneType.Library).isEmpty()) { + return false; + } + final Card topCard = controller.getCardsIn(ZoneType.Library).get(0); + if (!topCard.isValid(this.params.get("TopCardOfLibraryIs").split(","), controller, this.hostCard)) { + return false; + } + } + + if (this.params.containsKey("CheckSVar")) { + final int sVar = AbilityUtils.calculateAmount(this.hostCard, this.params.get("CheckSVar"), null); + String comparator = "GE1"; + if (this.params.containsKey("SVarCompare")) { + comparator = this.params.get("SVarCompare"); + } + final String svarOperator = comparator.substring(0, 2); + final String svarOperand = comparator.substring(2); + final int operandValue = AbilityUtils.calculateAmount(this.hostCard, svarOperand, null); + if (!Expressions.compare(sVar, svarOperator, operandValue)) { + return false; + } + } else { //no need to check the others + return true; + } + + if (this.params.containsKey("CheckSecondSVar")) { + final int sVar = AbilityUtils.calculateAmount(this.hostCard, this.params.get("CheckSecondSVar"), null); + String comparator = "GE1"; + if (this.params.containsKey("SecondSVarCompare")) { + comparator = this.params.get("SecondSVarCompare"); + } + final String svarOperator = comparator.substring(0, 2); + final String svarOperand = comparator.substring(2); + final int operandValue = AbilityUtils.calculateAmount(this.hostCard, svarOperand, null); + if (!Expressions.compare(sVar, svarOperator, operandValue)) { + return false; + } + } else { //no need to check the others + return true; + } + + if (this.params.containsKey("CheckThirdSVar")) { + final int sVar = AbilityUtils.calculateAmount(this.hostCard, this.params.get("CheckThirdSVar"), null); + String comparator = "GE1"; + if (this.params.containsKey("ThirdSVarCompare")) { + comparator = this.params.get("ThirdSVarCompare"); + } + final String svarOperator = comparator.substring(0, 2); + final String svarOperand = comparator.substring(2); + final int operandValue = AbilityUtils.calculateAmount(this.hostCard, svarOperand, null); + if (!Expressions.compare(sVar, svarOperator, operandValue)) { + return false; + } + } else { //no need to check the others + return true; + } + + if (this.params.containsKey("CheckFourthSVar")) { + final int sVar = AbilityUtils.calculateAmount(this.hostCard, this.params.get("CheckFourthSVar"), null); + String comparator = "GE1"; + if (this.params.containsKey("FourthSVarCompare")) { + comparator = this.params.get("FourthSVarCompare"); + } + final String svarOperator = comparator.substring(0, 2); + final String svarOperand = comparator.substring(2); + final int operandValue = AbilityUtils.calculateAmount(this.hostCard, svarOperand, null); + if (!Expressions.compare(sVar, svarOperator, operandValue)) { + return false; + } + } + + return true; + } + + /** + * Sets the temporarily suppressed. + * + * @param supp + * the new temporarily suppressed + */ + public final void setTemporarilySuppressed(final boolean supp) { + this.temporarilySuppressed = supp; + } + + /** + * Checks if is suppressed. + * + * @return true, if is suppressed + */ + public final boolean isSuppressed() { + return (this.suppressed || this.temporarilySuppressed); + } + + /** + * @return the layer + */ + public int getLayer() { + return layer; + } + + /** + * @param layer the layer to set + */ + public void setLayer(int layer) { + this.layer = layer; + } + + public void setTemporarily(boolean b) { + this.temporary = b; + } + public boolean isTemporary() { + return this.temporary; + } + +} // end class StaticEffectFactory diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java b/forge-gui/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java similarity index 100% rename from forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java rename to forge-gui/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java b/forge-gui/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java similarity index 97% rename from forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java rename to forge-gui/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java index edd595764ad..d36ff9fdc72 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java +++ b/forge-gui/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java @@ -1,138 +1,138 @@ -/* - * 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 . - */ -package forge.game.staticability; - -import java.util.HashMap; -import java.util.List; - -import forge.game.card.Card; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; -import forge.game.zone.ZoneType; - -/** - * The Class StaticAbility_CantBeCast. - */ -public class StaticAbilityCantBeCast { - - /** - * TODO Write javadoc for this method. - * - * @param stAb - * a StaticAbility - * @param card - * the card - * @param activator - * the activator - * @return true, if successful - */ - public static boolean applyCantBeCastAbility(final StaticAbility stAb, final Card card, final Player activator) { - final HashMap params = stAb.getMapParams(); - final Card hostCard = stAb.getHostCard(); - - if (params.containsKey("ValidCard") - && !card.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard)) { - return false; - } - - if (params.containsKey("Caster") && (activator != null) - && !activator.isValid(params.get("Caster"), hostCard.getController(), hostCard)) { - return false; - } - - if (params.containsKey("OnlySorcerySpeed") && (activator != null) && activator.canCastSorcery()) { - return false; - } - - if (params.containsKey("Origin")) { - List src = ZoneType.listValueOf(params.get("Origin")); - if (!src.contains(activator.getGame().getZoneOf(card).getZoneType())) { - return false; - } - } - - return true; - } - - /** - * Applies Cant Be Activated ability. - * - * @param staticAbility - * a StaticAbility - * @param card - * the card - * @param spellAbility - * a SpellAbility - * @return true, if successful - */ - public static boolean applyCantBeActivatedAbility(final StaticAbility staticAbility, final Card card, - final SpellAbility spellAbility) { - final HashMap params = staticAbility.getMapParams(); - final Card hostCard = staticAbility.getHostCard(); - final Player activator = spellAbility.getActivatingPlayer(); - - if (params.containsKey("ValidCard") - && !card.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard)) { - return false; - } - - if (params.containsKey("Activator") && (activator != null) - && !activator.isValid(params.get("Activator"), hostCard.getController(), hostCard)) { - return false; - } - - if (params.containsKey("NonMana") && (spellAbility.isManaAbility())) { - return false; - } - - if (params.containsKey("TapAbility") && !(spellAbility.getPayCosts().hasTapCost())) { - return false; - } - - return true; - } - - /** - * TODO Write javadoc for this method. - * - * @param stAb - * a StaticAbility - * @param card - * the card - * @param player - * the player - * @return true, if successful - */ - public static boolean applyCantPlayLandAbility(final StaticAbility stAb, final Card card, final Player player) { - final HashMap params = stAb.getMapParams(); - final Card hostCard = stAb.getHostCard(); - - if (params.containsKey("ValidCard") - && (card == null || !card.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard))) { - return false; - } - - if (params.containsKey("Player") && (player != null) - && !player.isValid(params.get("Player"), hostCard.getController(), hostCard)) { - return false; - } - - 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 . + */ +package forge.game.staticability; + +import java.util.HashMap; +import java.util.List; + +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; + +/** + * The Class StaticAbility_CantBeCast. + */ +public class StaticAbilityCantBeCast { + + /** + * TODO Write javadoc for this method. + * + * @param stAb + * a StaticAbility + * @param card + * the card + * @param activator + * the activator + * @return true, if successful + */ + public static boolean applyCantBeCastAbility(final StaticAbility stAb, final Card card, final Player activator) { + final HashMap params = stAb.getMapParams(); + final Card hostCard = stAb.getHostCard(); + + if (params.containsKey("ValidCard") + && !card.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard)) { + return false; + } + + if (params.containsKey("Caster") && (activator != null) + && !activator.isValid(params.get("Caster"), hostCard.getController(), hostCard)) { + return false; + } + + if (params.containsKey("OnlySorcerySpeed") && (activator != null) && activator.canCastSorcery()) { + return false; + } + + if (params.containsKey("Origin")) { + List src = ZoneType.listValueOf(params.get("Origin")); + if (!src.contains(activator.getGame().getZoneOf(card).getZoneType())) { + return false; + } + } + + return true; + } + + /** + * Applies Cant Be Activated ability. + * + * @param staticAbility + * a StaticAbility + * @param card + * the card + * @param spellAbility + * a SpellAbility + * @return true, if successful + */ + public static boolean applyCantBeActivatedAbility(final StaticAbility staticAbility, final Card card, + final SpellAbility spellAbility) { + final HashMap params = staticAbility.getMapParams(); + final Card hostCard = staticAbility.getHostCard(); + final Player activator = spellAbility.getActivatingPlayer(); + + if (params.containsKey("ValidCard") + && !card.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard)) { + return false; + } + + if (params.containsKey("Activator") && (activator != null) + && !activator.isValid(params.get("Activator"), hostCard.getController(), hostCard)) { + return false; + } + + if (params.containsKey("NonMana") && (spellAbility.isManaAbility())) { + return false; + } + + if (params.containsKey("TapAbility") && !(spellAbility.getPayCosts().hasTapCost())) { + return false; + } + + return true; + } + + /** + * TODO Write javadoc for this method. + * + * @param stAb + * a StaticAbility + * @param card + * the card + * @param player + * the player + * @return true, if successful + */ + public static boolean applyCantPlayLandAbility(final StaticAbility stAb, final Card card, final Player player) { + final HashMap params = stAb.getMapParams(); + final Card hostCard = stAb.getHostCard(); + + if (params.containsKey("ValidCard") + && (card == null || !card.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard))) { + return false; + } + + if (params.containsKey("Player") && (player != null) + && !player.isValid(params.get("Player"), hostCard.getController(), hostCard)) { + return false; + } + + return true; + } + +} diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java b/forge-gui/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java similarity index 100% rename from forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java rename to forge-gui/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-gui/src/main/java/forge/game/staticability/StaticAbilityContinuous.java similarity index 97% rename from forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java rename to forge-gui/src/main/java/forge/game/staticability/StaticAbilityContinuous.java index 661bcfd33f1..0b699a6e6f4 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-gui/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -1,579 +1,579 @@ -/* - * 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 . - */ -package forge.game.staticability; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; - -import com.google.common.collect.Lists; - -import forge.card.CardType; -import forge.card.ColorSet; -import forge.card.mana.ManaCostShard; -import forge.game.Game; -import forge.game.GlobalRuleChange; -import forge.game.StaticEffect; -import forge.game.StaticEffects; -import forge.game.TriggerReplacementBase; -import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityUtils; -import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CardLists; -import forge.game.card.CardUtil; -import forge.game.player.Player; -import forge.game.replacement.ReplacementEffect; -import forge.game.replacement.ReplacementHandler; -import forge.game.spellability.AbilityActivated; -import forge.game.spellability.SpellAbility; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerHandler; -import forge.game.zone.ZoneType; - -/** - * The Class StaticAbility_Continuous. - */ -public class StaticAbilityContinuous { - - /** - * - * TODO Write javadoc for this method. - * - * @param stAb - * a StaticAbility - * @return - * - */ - public static List applyContinuousAbility(final StaticAbility stAb) { - final HashMap params = stAb.getMapParams(); - final Card hostCard = stAb.getHostCard(); - - final StaticEffect se = new StaticEffect(hostCard); - final List affectedCards = StaticAbilityContinuous.getAffectedCards(stAb); - final ArrayList affectedPlayers = StaticAbilityContinuous.getAffectedPlayers(stAb); - final Game game = hostCard.getGame(); - - se.setAffectedCards(affectedCards); - se.setAffectedPlayers(affectedPlayers); - se.setParams(params); - se.setTimestamp(hostCard.getTimestamp()); - game.getStaticEffects().addStaticEffect(se); - - String addP = ""; - int powerBonus = 0; - String addT = ""; - int toughnessBonus = 0; - String setP = ""; - int setPower = -1; - String setT = ""; - int setToughness = -1; - int keywordMultiplier = 1; - - String[] addKeywords = null; - String[] addHiddenKeywords = null; - String[] removeKeywords = null; - String[] addAbilities = null; - String[] addReplacements = null; - String[] addSVars = null; - String[] addTypes = null; - String[] removeTypes = null; - String addColors = null; - String[] addTriggers = null; - String[] addStatics = null; - ArrayList addFullAbs = null; - boolean removeAllAbilities = false; - boolean removeSuperTypes = false; - boolean removeCardTypes = false; - boolean removeSubTypes = false; - boolean removeCreatureTypes = false; - - //Global rules changes - if (params.containsKey("GlobalRule")) { - final StaticEffects effects = game.getStaticEffects(); - effects.setGlobalRuleChange(GlobalRuleChange.fromString(params.get("GlobalRule"))); - } - - if (params.containsKey("SetPower")) { - setP = params.get("SetPower"); - setPower = AbilityUtils.calculateAmount(hostCard, setP, null); - } - - if (params.containsKey("SetToughness")) { - setT = params.get("SetToughness"); - setToughness = AbilityUtils.calculateAmount(hostCard, setT, null); - } - - if (params.containsKey("AddPower")) { - addP = params.get("AddPower"); - powerBonus = AbilityUtils.calculateAmount(hostCard, addP, null); - if (!StringUtils.isNumeric(addP) && !addP.equals("AffectedX")) { - se.setXValue(powerBonus); - } - } - - if (params.containsKey("AddToughness")) { - addT = params.get("AddToughness"); - toughnessBonus = AbilityUtils.calculateAmount(hostCard, addT, null); - if (!StringUtils.isNumeric(addT) && !addT.equals("AffectedX")) { - se.setYValue(toughnessBonus); - } - } - - if (params.containsKey("KeywordMultiplier")) { - String multiplier = params.get("KeywordMultiplier"); - if (multiplier.equals("X")) { - keywordMultiplier = CardFactoryUtil.xCount(hostCard, hostCard.getSVar("X")); - se.setXValue(keywordMultiplier); - } else { - keywordMultiplier = Integer.valueOf(multiplier); - } - } - - if (params.containsKey("AddKeyword")) { - addKeywords = params.get("AddKeyword").split(" & "); - final List chosencolors = hostCard.getChosenColor(); - for (final String color : chosencolors) { - for (int w = 0; w < addKeywords.length; w++) { - addKeywords[w] = addKeywords[w].replaceAll("ChosenColor", color.substring(0, 1).toUpperCase().concat(color.substring(1, color.length()))); - } - } - final String chosenType = hostCard.getChosenType(); - for (int w = 0; w < addKeywords.length; w++) { - addKeywords[w] = addKeywords[w].replaceAll("ChosenType", chosenType); - } - final String chosenName = hostCard.getNamedCard(); - for (int w = 0; w < addKeywords.length; w++) { - if (addKeywords[w].startsWith("Protection:")) { - addKeywords[w] = addKeywords[w].replaceAll("ChosenName", "Card.named" + chosenName); - } - } - if (params.containsKey("SharedKeywordsZone")) { - List zones = ZoneType.listValueOf(params.get("SharedKeywordsZone")); - String[] restrictions = params.containsKey("SharedRestrictions") ? params.get("SharedRestrictions").split(",") : new String[] {"Card"}; - List kw = CardFactoryUtil.sharedKeywords(addKeywords, restrictions, zones, hostCard); - addKeywords = kw.toArray(new String[kw.size()]); - } - } - - if (params.containsKey("AddHiddenKeyword")) { - addHiddenKeywords = params.get("AddHiddenKeyword").split(" & "); - } - - if (params.containsKey("RemoveKeyword")) { - removeKeywords = params.get("RemoveKeyword").split(" & "); - } - - if (params.containsKey("RemoveAllAbilities")) { - removeAllAbilities = true; - } - - if (params.containsKey("AddAbility")) { - final String[] sVars = params.get("AddAbility").split(" & "); - for (int i = 0; i < sVars.length; i++) { - sVars[i] = hostCard.getSVar(sVars[i]); - } - addAbilities = sVars; - } - - if (params.containsKey("AddReplacementEffects")) { - final String[] sVars = params.get("AddReplacementEffects").split(" & "); - for (int i = 0; i < sVars.length; i++) { - sVars[i] = hostCard.getSVar(sVars[i]); - } - addReplacements = sVars; - } - - if (params.containsKey("AddSVar")) { - addSVars = params.get("AddSVar").split(" & "); - } - - if (params.containsKey("AddType")) { - addTypes = params.get("AddType").split(" & "); - if (addTypes[0].equals("ChosenType")) { - final String chosenType = hostCard.getChosenType(); - addTypes[0] = chosenType; - se.setChosenType(chosenType); - } else if (addTypes[0].equals("ImprintedCreatureType")) { - final ArrayList imprint = hostCard.getImprinted().get(0).getType(); - ArrayList imprinted = new ArrayList(); - for (String t : imprint) { - if (CardType.isACreatureType(t) || t.equals("AllCreatureTypes")) { - imprinted.add(t); - } - } - addTypes = imprinted.toArray(new String[imprinted.size()]); - } - } - - if (params.containsKey("RemoveType")) { - removeTypes = params.get("RemoveType").split(" & "); - if (removeTypes[0].equals("ChosenType")) { - final String chosenType = hostCard.getChosenType(); - removeTypes[0] = chosenType; - se.setChosenType(chosenType); - } - } - - if (params.containsKey("RemoveSuperTypes")) { - removeSuperTypes = true; - } - - if (params.containsKey("RemoveCardTypes")) { - removeCardTypes = true; - } - - if (params.containsKey("RemoveSubTypes")) { - removeSubTypes = true; - } - - if (params.containsKey("RemoveCreatureTypes")) { - removeCreatureTypes = true; - } - - if (params.containsKey("AddColor")) { - final String colors = params.get("AddColor"); - if (colors.equals("ChosenColor")) { - addColors = CardUtil.getShortColorsString(hostCard.getChosenColor()); - } else { - addColors = CardUtil.getShortColorsString(new ArrayList(Arrays.asList(colors.split( - " & ")))); - } - } - - if (params.containsKey("SetColor")) { - final String colors = params.get("SetColor"); - if (colors.equals("ChosenColor")) { - addColors = CardUtil.getShortColorsString(hostCard.getChosenColor()); - } else { - addColors = CardUtil.getShortColorsString(new ArrayList(Arrays.asList( - colors.split(" & ")))); - } - se.setOverwriteColors(true); - } - - if (params.containsKey("AddTrigger")) { - final String[] sVars = params.get("AddTrigger").split(" & "); - for (int i = 0; i < sVars.length; i++) { - sVars[i] = hostCard.getSVar(sVars[i]); - } - addTriggers = sVars; - } - - if (params.containsKey("AddStaticAbility")) { - final String[] sVars = params.get("AddStaticAbility").split(" & "); - for (int i = 0; i < sVars.length; i++) { - sVars[i] = hostCard.getSVar(sVars[i]); - } - addStatics = sVars; - } - - if (params.containsKey("GainsAbilitiesOf")) { - final String[] valids = params.get("GainsAbilitiesOf").split(","); - ArrayList validZones = new ArrayList(); - validZones.add(ZoneType.Battlefield); - if (params.containsKey("GainsAbilitiesOfZones")) { - validZones.clear(); - for (String s : params.get("GainsAbilitiesOfZones").split(",")) { - validZones.add(ZoneType.smartValueOf(s)); - } - } - - List cardsIGainedAbilitiesFrom = game.getCardsIn(validZones); - cardsIGainedAbilitiesFrom = CardLists.getValidCards(cardsIGainedAbilitiesFrom, valids, hostCard.getController(), hostCard); - - if (cardsIGainedAbilitiesFrom.size() > 0) { - - addFullAbs = new ArrayList(); - - for (Card c : cardsIGainedAbilitiesFrom) { - for (SpellAbility sa : c.getSpellAbilities()) { - if (sa instanceof AbilityActivated) { - SpellAbility newSA = ((AbilityActivated) sa).getCopy(); - newSA.setTemporary(true); - CardFactoryUtil.correctAbilityChainSourceCard(newSA, hostCard); - addFullAbs.add(newSA); - } - } - } - } - } - - // modify players - for (final Player p : affectedPlayers) { - - // add keywords - if (addKeywords != null) { - for (final String keyword : addKeywords) { - for (int i = 0; i < keywordMultiplier; i++) { - p.addKeyword(keyword); - } - } - } - - if (params.containsKey("SetMaxHandSize")) { - String mhs = params.get("SetMaxHandSize"); - if (mhs.equals("Unlimited")) { - p.setUnlimitedHandSize(true); - } else { - int max = AbilityUtils.calculateAmount(hostCard, mhs, null); - p.setMaxHandSize(max); - } - } - - if (params.containsKey("RaiseMaxHandSize")) { - String rmhs = params.get("RaiseMaxHandSize"); - int rmax = AbilityUtils.calculateAmount(hostCard, rmhs, null); - p.setMaxHandSize(p.getMaxHandSize() + rmax); - } - } - - // start modifying the cards - for (int i = 0; i < affectedCards.size(); i++) { - final Card affectedCard = affectedCards.get(i); - - // Gain control - if (params.containsKey("GainControl")) { - affectedCard.addTempController(hostCard.getController(), hostCard.getTimestamp()); - } - - // set P/T - if (params.containsKey("CharacteristicDefining")) { - if (setPower != -1) { - affectedCard.setBaseAttack(setPower); - } - if (setToughness != -1) { - affectedCard.setBaseDefense(setToughness); - } - } else // non CharacteristicDefining - if ((setPower != -1) || (setToughness != -1)) { - if (setP.startsWith("AffectedX")) { - setPower = CardFactoryUtil.xCount(affectedCard, hostCard.getSVar(setP)); - } - if (setT.startsWith("AffectedX")) { - setToughness = CardFactoryUtil.xCount(affectedCard, hostCard.getSVar(setT)); - } - affectedCard.addNewPT(setPower, setToughness, hostCard.getTimestamp()); - } - - // add P/T bonus - if (addP.startsWith("AffectedX")) { - powerBonus = CardFactoryUtil.xCount(affectedCard, hostCard.getSVar(addP)); - se.addXMapValue(affectedCard, powerBonus); - } - if (addT.startsWith("AffectedX")) { - toughnessBonus = CardFactoryUtil.xCount(affectedCard, hostCard.getSVar(addT)); - se.addXMapValue(affectedCard, toughnessBonus); - } - affectedCard.addSemiPermanentAttackBoost(powerBonus); - affectedCard.addSemiPermanentDefenseBoost(toughnessBonus); - - // add keywords - // TODO regular keywords currently don't try to use keyword multiplier - // (Although nothing uses it at this time) - if ((addKeywords != null) || (removeKeywords != null) || removeAllAbilities) { - affectedCard.addChangedCardKeywords(addKeywords, removeKeywords, removeAllAbilities, - hostCard.getTimestamp()); - } - - // add HIDDEN keywords - if (addHiddenKeywords != null) { - for (final String k : addHiddenKeywords) { - for (int j = 0; j < keywordMultiplier; j++) { - affectedCard.addHiddenExtrinsicKeyword(k); - } - } - } - - // add SVars - if (addSVars != null) { - for (final String sVar : addSVars) { - String actualSVar = hostCard.getSVar(sVar); - String name = sVar; - if (actualSVar.startsWith("SVar:")) { - actualSVar = actualSVar.split("SVar:")[1]; - name = actualSVar.split(":")[0]; - actualSVar = actualSVar.split(":")[1]; - } - affectedCard.setSVar(name, actualSVar); - } - } - - if (addFullAbs != null) { - for (final SpellAbility ab : addFullAbs) { - affectedCard.addSpellAbility(ab); - } - } - - // add abilities - if (addAbilities != null) { - for (String abilty : addAbilities) { - if (abilty.contains("CardManaCost")) { - StringBuilder sb = new StringBuilder(); - int generic = affectedCard.getManaCost().getGenericCost(); - if (generic > 0) { - sb.append(generic); - } - for (ManaCostShard s : affectedCard.getManaCost()) { - ColorSet cs = ColorSet.fromMask(s.getColorMask()); - if(cs.isColorless()) continue; - sb.append(' '); - sb.append(s); - } - abilty = abilty.replace("CardManaCost", sb.toString().trim()); - } else if (abilty.contains("ConvertedManaCost")) { - final String costcmc = Integer.toString(affectedCard.getCMC()); - abilty = abilty.replace("ConvertedManaCost", costcmc); - } - if (abilty.startsWith("AB")) { // grant the ability - final SpellAbility sa = AbilityFactory.getAbility(abilty, affectedCard); - sa.setTemporary(true); - sa.setOriginalHost(hostCard); - affectedCard.addSpellAbility(sa); - } - } - } - - // add Replacement effects - if (addReplacements != null) { - for (String rep : addReplacements) { - final ReplacementEffect actualRep = ReplacementHandler.parseReplacement(rep, affectedCard, false); - affectedCard.addReplacementEffect(actualRep).setTemporary(true);; - } - } - - // add Types - if ((addTypes != null) || (removeTypes != null)) { - affectedCard.addChangedCardTypes(addTypes, removeTypes, removeSuperTypes, removeCardTypes, - removeSubTypes, removeCreatureTypes, hostCard.getTimestamp()); - } - - // add colors - if (addColors != null) { - final long t = affectedCard.addColor(addColors, !se.isOverwriteColors(), true); - se.addTimestamp(affectedCard, t); - } - - // add triggers - if (addTriggers != null) { - for (final String trigger : addTriggers) { - final Trigger actualTrigger = TriggerHandler.parseTrigger(trigger, affectedCard, false); - affectedCard.addTrigger(actualTrigger).setTemporary(true); - } - } - - // add static abilities - if (addStatics != null) { - for (String s : addStatics) { - if (s.contains("ConvertedManaCost")) { - final String costcmc = Integer.toString(affectedCard.getCMC()); - s = s.replace("ConvertedManaCost", costcmc); - } - affectedCard.addStaticAbility(s).setTemporarily(true); - } - } - - // remove triggers - if (params.containsKey("RemoveTriggers") || removeAllAbilities) { - for (final Trigger trigger : affectedCard.getTriggers()) { - trigger.setTemporarilySuppressed(true); - } - } - - // remove activated and static abilities - if (removeAllAbilities) { - for (final SpellAbility ab : affectedCard.getSpellAbilities()) { - ab.setTemporarilySuppressed(true); - } - for (final StaticAbility stA : affectedCard.getStaticAbilities()) { - stA.setTemporarilySuppressed(true); - } - for (final TriggerReplacementBase rE : affectedCard.getReplacementEffects()) { - rE.setTemporarilySuppressed(true); - } - } - } - - return affectedCards; - } - - private static ArrayList getAffectedPlayers(final StaticAbility stAb) { - final HashMap params = stAb.getMapParams(); - final Card hostCard = stAb.getHostCard(); - final Player controller = hostCard.getController(); - - final ArrayList players = new ArrayList(); - - if (!params.containsKey("Affected")) { - return players; - } - - final String[] strngs = params.get("Affected").split(","); - - for (Player p : controller.getGame().getPlayers()) { - if (p.isValid(strngs, controller, hostCard)) { - players.add(p); - } - } - - return players; - } - - private static List getAffectedCards(final StaticAbility stAb) { - final HashMap params = stAb.getMapParams(); - final Card hostCard = stAb.getHostCard(); - final Game game = hostCard.getGame(); - final Player controller = hostCard.getController(); - - if (params.containsKey("CharacteristicDefining")) { - return Lists.newArrayList(hostCard); // will always be the card itself - } - - // non - CharacteristicDefining - List affectedCards = new ArrayList(); - - if (params.containsKey("AffectedZone")) { - affectedCards.addAll(game.getCardsIn(ZoneType.listValueOf(params.get("AffectedZone")))); - } else { - affectedCards = game.getCardsIn(ZoneType.Battlefield); - } - - if (params.containsKey("Affected") && !params.get("Affected").contains(",")) { - if (params.get("Affected").contains("Self")) { - affectedCards = Lists.newArrayList(hostCard); - } else if (params.get("Affected").contains("EnchantedBy")) { - affectedCards = Lists.newArrayList(hostCard.getEnchantingCard()); - } else if (params.get("Affected").contains("EquippedBy")) { - affectedCards = Lists.newArrayList(hostCard.getEquippingCard()); - } else if (params.get("Affected").equals("EffectSource")) { - affectedCards = new ArrayList(AbilityUtils.getDefinedCards(hostCard, params.get("Affected"), null)); - return affectedCards; - } - } - - if (params.containsKey("Affected")) { - affectedCards = CardLists.getValidCards(affectedCards, params.get("Affected").split(","), controller, hostCard); - } - - return affectedCards; - } - -} +/* + * 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 . + */ +package forge.game.staticability; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.Lists; + +import forge.card.CardType; +import forge.card.ColorSet; +import forge.card.mana.ManaCostShard; +import forge.game.Game; +import forge.game.GlobalRuleChange; +import forge.game.StaticEffect; +import forge.game.StaticEffects; +import forge.game.TriggerReplacementBase; +import forge.game.ability.AbilityFactory; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.card.CardFactoryUtil; +import forge.game.card.CardLists; +import forge.game.card.CardUtil; +import forge.game.player.Player; +import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementHandler; +import forge.game.spellability.AbilityActivated; +import forge.game.spellability.SpellAbility; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerHandler; +import forge.game.zone.ZoneType; + +/** + * The Class StaticAbility_Continuous. + */ +public class StaticAbilityContinuous { + + /** + * + * TODO Write javadoc for this method. + * + * @param stAb + * a StaticAbility + * @return + * + */ + public static List applyContinuousAbility(final StaticAbility stAb) { + final HashMap params = stAb.getMapParams(); + final Card hostCard = stAb.getHostCard(); + + final StaticEffect se = new StaticEffect(hostCard); + final List affectedCards = StaticAbilityContinuous.getAffectedCards(stAb); + final ArrayList affectedPlayers = StaticAbilityContinuous.getAffectedPlayers(stAb); + final Game game = hostCard.getGame(); + + se.setAffectedCards(affectedCards); + se.setAffectedPlayers(affectedPlayers); + se.setParams(params); + se.setTimestamp(hostCard.getTimestamp()); + game.getStaticEffects().addStaticEffect(se); + + String addP = ""; + int powerBonus = 0; + String addT = ""; + int toughnessBonus = 0; + String setP = ""; + int setPower = -1; + String setT = ""; + int setToughness = -1; + int keywordMultiplier = 1; + + String[] addKeywords = null; + String[] addHiddenKeywords = null; + String[] removeKeywords = null; + String[] addAbilities = null; + String[] addReplacements = null; + String[] addSVars = null; + String[] addTypes = null; + String[] removeTypes = null; + String addColors = null; + String[] addTriggers = null; + String[] addStatics = null; + ArrayList addFullAbs = null; + boolean removeAllAbilities = false; + boolean removeSuperTypes = false; + boolean removeCardTypes = false; + boolean removeSubTypes = false; + boolean removeCreatureTypes = false; + + //Global rules changes + if (params.containsKey("GlobalRule")) { + final StaticEffects effects = game.getStaticEffects(); + effects.setGlobalRuleChange(GlobalRuleChange.fromString(params.get("GlobalRule"))); + } + + if (params.containsKey("SetPower")) { + setP = params.get("SetPower"); + setPower = AbilityUtils.calculateAmount(hostCard, setP, null); + } + + if (params.containsKey("SetToughness")) { + setT = params.get("SetToughness"); + setToughness = AbilityUtils.calculateAmount(hostCard, setT, null); + } + + if (params.containsKey("AddPower")) { + addP = params.get("AddPower"); + powerBonus = AbilityUtils.calculateAmount(hostCard, addP, null); + if (!StringUtils.isNumeric(addP) && !addP.equals("AffectedX")) { + se.setXValue(powerBonus); + } + } + + if (params.containsKey("AddToughness")) { + addT = params.get("AddToughness"); + toughnessBonus = AbilityUtils.calculateAmount(hostCard, addT, null); + if (!StringUtils.isNumeric(addT) && !addT.equals("AffectedX")) { + se.setYValue(toughnessBonus); + } + } + + if (params.containsKey("KeywordMultiplier")) { + String multiplier = params.get("KeywordMultiplier"); + if (multiplier.equals("X")) { + keywordMultiplier = CardFactoryUtil.xCount(hostCard, hostCard.getSVar("X")); + se.setXValue(keywordMultiplier); + } else { + keywordMultiplier = Integer.valueOf(multiplier); + } + } + + if (params.containsKey("AddKeyword")) { + addKeywords = params.get("AddKeyword").split(" & "); + final List chosencolors = hostCard.getChosenColor(); + for (final String color : chosencolors) { + for (int w = 0; w < addKeywords.length; w++) { + addKeywords[w] = addKeywords[w].replaceAll("ChosenColor", color.substring(0, 1).toUpperCase().concat(color.substring(1, color.length()))); + } + } + final String chosenType = hostCard.getChosenType(); + for (int w = 0; w < addKeywords.length; w++) { + addKeywords[w] = addKeywords[w].replaceAll("ChosenType", chosenType); + } + final String chosenName = hostCard.getNamedCard(); + for (int w = 0; w < addKeywords.length; w++) { + if (addKeywords[w].startsWith("Protection:")) { + addKeywords[w] = addKeywords[w].replaceAll("ChosenName", "Card.named" + chosenName); + } + } + if (params.containsKey("SharedKeywordsZone")) { + List zones = ZoneType.listValueOf(params.get("SharedKeywordsZone")); + String[] restrictions = params.containsKey("SharedRestrictions") ? params.get("SharedRestrictions").split(",") : new String[] {"Card"}; + List kw = CardFactoryUtil.sharedKeywords(addKeywords, restrictions, zones, hostCard); + addKeywords = kw.toArray(new String[kw.size()]); + } + } + + if (params.containsKey("AddHiddenKeyword")) { + addHiddenKeywords = params.get("AddHiddenKeyword").split(" & "); + } + + if (params.containsKey("RemoveKeyword")) { + removeKeywords = params.get("RemoveKeyword").split(" & "); + } + + if (params.containsKey("RemoveAllAbilities")) { + removeAllAbilities = true; + } + + if (params.containsKey("AddAbility")) { + final String[] sVars = params.get("AddAbility").split(" & "); + for (int i = 0; i < sVars.length; i++) { + sVars[i] = hostCard.getSVar(sVars[i]); + } + addAbilities = sVars; + } + + if (params.containsKey("AddReplacementEffects")) { + final String[] sVars = params.get("AddReplacementEffects").split(" & "); + for (int i = 0; i < sVars.length; i++) { + sVars[i] = hostCard.getSVar(sVars[i]); + } + addReplacements = sVars; + } + + if (params.containsKey("AddSVar")) { + addSVars = params.get("AddSVar").split(" & "); + } + + if (params.containsKey("AddType")) { + addTypes = params.get("AddType").split(" & "); + if (addTypes[0].equals("ChosenType")) { + final String chosenType = hostCard.getChosenType(); + addTypes[0] = chosenType; + se.setChosenType(chosenType); + } else if (addTypes[0].equals("ImprintedCreatureType")) { + final ArrayList imprint = hostCard.getImprinted().get(0).getType(); + ArrayList imprinted = new ArrayList(); + for (String t : imprint) { + if (CardType.isACreatureType(t) || t.equals("AllCreatureTypes")) { + imprinted.add(t); + } + } + addTypes = imprinted.toArray(new String[imprinted.size()]); + } + } + + if (params.containsKey("RemoveType")) { + removeTypes = params.get("RemoveType").split(" & "); + if (removeTypes[0].equals("ChosenType")) { + final String chosenType = hostCard.getChosenType(); + removeTypes[0] = chosenType; + se.setChosenType(chosenType); + } + } + + if (params.containsKey("RemoveSuperTypes")) { + removeSuperTypes = true; + } + + if (params.containsKey("RemoveCardTypes")) { + removeCardTypes = true; + } + + if (params.containsKey("RemoveSubTypes")) { + removeSubTypes = true; + } + + if (params.containsKey("RemoveCreatureTypes")) { + removeCreatureTypes = true; + } + + if (params.containsKey("AddColor")) { + final String colors = params.get("AddColor"); + if (colors.equals("ChosenColor")) { + addColors = CardUtil.getShortColorsString(hostCard.getChosenColor()); + } else { + addColors = CardUtil.getShortColorsString(new ArrayList(Arrays.asList(colors.split( + " & ")))); + } + } + + if (params.containsKey("SetColor")) { + final String colors = params.get("SetColor"); + if (colors.equals("ChosenColor")) { + addColors = CardUtil.getShortColorsString(hostCard.getChosenColor()); + } else { + addColors = CardUtil.getShortColorsString(new ArrayList(Arrays.asList( + colors.split(" & ")))); + } + se.setOverwriteColors(true); + } + + if (params.containsKey("AddTrigger")) { + final String[] sVars = params.get("AddTrigger").split(" & "); + for (int i = 0; i < sVars.length; i++) { + sVars[i] = hostCard.getSVar(sVars[i]); + } + addTriggers = sVars; + } + + if (params.containsKey("AddStaticAbility")) { + final String[] sVars = params.get("AddStaticAbility").split(" & "); + for (int i = 0; i < sVars.length; i++) { + sVars[i] = hostCard.getSVar(sVars[i]); + } + addStatics = sVars; + } + + if (params.containsKey("GainsAbilitiesOf")) { + final String[] valids = params.get("GainsAbilitiesOf").split(","); + ArrayList validZones = new ArrayList(); + validZones.add(ZoneType.Battlefield); + if (params.containsKey("GainsAbilitiesOfZones")) { + validZones.clear(); + for (String s : params.get("GainsAbilitiesOfZones").split(",")) { + validZones.add(ZoneType.smartValueOf(s)); + } + } + + List cardsIGainedAbilitiesFrom = game.getCardsIn(validZones); + cardsIGainedAbilitiesFrom = CardLists.getValidCards(cardsIGainedAbilitiesFrom, valids, hostCard.getController(), hostCard); + + if (cardsIGainedAbilitiesFrom.size() > 0) { + + addFullAbs = new ArrayList(); + + for (Card c : cardsIGainedAbilitiesFrom) { + for (SpellAbility sa : c.getSpellAbilities()) { + if (sa instanceof AbilityActivated) { + SpellAbility newSA = ((AbilityActivated) sa).getCopy(); + newSA.setTemporary(true); + CardFactoryUtil.correctAbilityChainSourceCard(newSA, hostCard); + addFullAbs.add(newSA); + } + } + } + } + } + + // modify players + for (final Player p : affectedPlayers) { + + // add keywords + if (addKeywords != null) { + for (final String keyword : addKeywords) { + for (int i = 0; i < keywordMultiplier; i++) { + p.addKeyword(keyword); + } + } + } + + if (params.containsKey("SetMaxHandSize")) { + String mhs = params.get("SetMaxHandSize"); + if (mhs.equals("Unlimited")) { + p.setUnlimitedHandSize(true); + } else { + int max = AbilityUtils.calculateAmount(hostCard, mhs, null); + p.setMaxHandSize(max); + } + } + + if (params.containsKey("RaiseMaxHandSize")) { + String rmhs = params.get("RaiseMaxHandSize"); + int rmax = AbilityUtils.calculateAmount(hostCard, rmhs, null); + p.setMaxHandSize(p.getMaxHandSize() + rmax); + } + } + + // start modifying the cards + for (int i = 0; i < affectedCards.size(); i++) { + final Card affectedCard = affectedCards.get(i); + + // Gain control + if (params.containsKey("GainControl")) { + affectedCard.addTempController(hostCard.getController(), hostCard.getTimestamp()); + } + + // set P/T + if (params.containsKey("CharacteristicDefining")) { + if (setPower != -1) { + affectedCard.setBaseAttack(setPower); + } + if (setToughness != -1) { + affectedCard.setBaseDefense(setToughness); + } + } else // non CharacteristicDefining + if ((setPower != -1) || (setToughness != -1)) { + if (setP.startsWith("AffectedX")) { + setPower = CardFactoryUtil.xCount(affectedCard, hostCard.getSVar(setP)); + } + if (setT.startsWith("AffectedX")) { + setToughness = CardFactoryUtil.xCount(affectedCard, hostCard.getSVar(setT)); + } + affectedCard.addNewPT(setPower, setToughness, hostCard.getTimestamp()); + } + + // add P/T bonus + if (addP.startsWith("AffectedX")) { + powerBonus = CardFactoryUtil.xCount(affectedCard, hostCard.getSVar(addP)); + se.addXMapValue(affectedCard, powerBonus); + } + if (addT.startsWith("AffectedX")) { + toughnessBonus = CardFactoryUtil.xCount(affectedCard, hostCard.getSVar(addT)); + se.addXMapValue(affectedCard, toughnessBonus); + } + affectedCard.addSemiPermanentAttackBoost(powerBonus); + affectedCard.addSemiPermanentDefenseBoost(toughnessBonus); + + // add keywords + // TODO regular keywords currently don't try to use keyword multiplier + // (Although nothing uses it at this time) + if ((addKeywords != null) || (removeKeywords != null) || removeAllAbilities) { + affectedCard.addChangedCardKeywords(addKeywords, removeKeywords, removeAllAbilities, + hostCard.getTimestamp()); + } + + // add HIDDEN keywords + if (addHiddenKeywords != null) { + for (final String k : addHiddenKeywords) { + for (int j = 0; j < keywordMultiplier; j++) { + affectedCard.addHiddenExtrinsicKeyword(k); + } + } + } + + // add SVars + if (addSVars != null) { + for (final String sVar : addSVars) { + String actualSVar = hostCard.getSVar(sVar); + String name = sVar; + if (actualSVar.startsWith("SVar:")) { + actualSVar = actualSVar.split("SVar:")[1]; + name = actualSVar.split(":")[0]; + actualSVar = actualSVar.split(":")[1]; + } + affectedCard.setSVar(name, actualSVar); + } + } + + if (addFullAbs != null) { + for (final SpellAbility ab : addFullAbs) { + affectedCard.addSpellAbility(ab); + } + } + + // add abilities + if (addAbilities != null) { + for (String abilty : addAbilities) { + if (abilty.contains("CardManaCost")) { + StringBuilder sb = new StringBuilder(); + int generic = affectedCard.getManaCost().getGenericCost(); + if (generic > 0) { + sb.append(generic); + } + for (ManaCostShard s : affectedCard.getManaCost()) { + ColorSet cs = ColorSet.fromMask(s.getColorMask()); + if(cs.isColorless()) continue; + sb.append(' '); + sb.append(s); + } + abilty = abilty.replace("CardManaCost", sb.toString().trim()); + } else if (abilty.contains("ConvertedManaCost")) { + final String costcmc = Integer.toString(affectedCard.getCMC()); + abilty = abilty.replace("ConvertedManaCost", costcmc); + } + if (abilty.startsWith("AB")) { // grant the ability + final SpellAbility sa = AbilityFactory.getAbility(abilty, affectedCard); + sa.setTemporary(true); + sa.setOriginalHost(hostCard); + affectedCard.addSpellAbility(sa); + } + } + } + + // add Replacement effects + if (addReplacements != null) { + for (String rep : addReplacements) { + final ReplacementEffect actualRep = ReplacementHandler.parseReplacement(rep, affectedCard, false); + affectedCard.addReplacementEffect(actualRep).setTemporary(true);; + } + } + + // add Types + if ((addTypes != null) || (removeTypes != null)) { + affectedCard.addChangedCardTypes(addTypes, removeTypes, removeSuperTypes, removeCardTypes, + removeSubTypes, removeCreatureTypes, hostCard.getTimestamp()); + } + + // add colors + if (addColors != null) { + final long t = affectedCard.addColor(addColors, !se.isOverwriteColors(), true); + se.addTimestamp(affectedCard, t); + } + + // add triggers + if (addTriggers != null) { + for (final String trigger : addTriggers) { + final Trigger actualTrigger = TriggerHandler.parseTrigger(trigger, affectedCard, false); + affectedCard.addTrigger(actualTrigger).setTemporary(true); + } + } + + // add static abilities + if (addStatics != null) { + for (String s : addStatics) { + if (s.contains("ConvertedManaCost")) { + final String costcmc = Integer.toString(affectedCard.getCMC()); + s = s.replace("ConvertedManaCost", costcmc); + } + affectedCard.addStaticAbility(s).setTemporarily(true); + } + } + + // remove triggers + if (params.containsKey("RemoveTriggers") || removeAllAbilities) { + for (final Trigger trigger : affectedCard.getTriggers()) { + trigger.setTemporarilySuppressed(true); + } + } + + // remove activated and static abilities + if (removeAllAbilities) { + for (final SpellAbility ab : affectedCard.getSpellAbilities()) { + ab.setTemporarilySuppressed(true); + } + for (final StaticAbility stA : affectedCard.getStaticAbilities()) { + stA.setTemporarilySuppressed(true); + } + for (final TriggerReplacementBase rE : affectedCard.getReplacementEffects()) { + rE.setTemporarilySuppressed(true); + } + } + } + + return affectedCards; + } + + private static ArrayList getAffectedPlayers(final StaticAbility stAb) { + final HashMap params = stAb.getMapParams(); + final Card hostCard = stAb.getHostCard(); + final Player controller = hostCard.getController(); + + final ArrayList players = new ArrayList(); + + if (!params.containsKey("Affected")) { + return players; + } + + final String[] strngs = params.get("Affected").split(","); + + for (Player p : controller.getGame().getPlayers()) { + if (p.isValid(strngs, controller, hostCard)) { + players.add(p); + } + } + + return players; + } + + private static List getAffectedCards(final StaticAbility stAb) { + final HashMap params = stAb.getMapParams(); + final Card hostCard = stAb.getHostCard(); + final Game game = hostCard.getGame(); + final Player controller = hostCard.getController(); + + if (params.containsKey("CharacteristicDefining")) { + return Lists.newArrayList(hostCard); // will always be the card itself + } + + // non - CharacteristicDefining + List affectedCards = new ArrayList(); + + if (params.containsKey("AffectedZone")) { + affectedCards.addAll(game.getCardsIn(ZoneType.listValueOf(params.get("AffectedZone")))); + } else { + affectedCards = game.getCardsIn(ZoneType.Battlefield); + } + + if (params.containsKey("Affected") && !params.get("Affected").contains(",")) { + if (params.get("Affected").contains("Self")) { + affectedCards = Lists.newArrayList(hostCard); + } else if (params.get("Affected").contains("EnchantedBy")) { + affectedCards = Lists.newArrayList(hostCard.getEnchantingCard()); + } else if (params.get("Affected").contains("EquippedBy")) { + affectedCards = Lists.newArrayList(hostCard.getEquippingCard()); + } else if (params.get("Affected").equals("EffectSource")) { + affectedCards = new ArrayList(AbilityUtils.getDefinedCards(hostCard, params.get("Affected"), null)); + return affectedCards; + } + } + + if (params.containsKey("Affected")) { + affectedCards = CardLists.getValidCards(affectedCards, params.get("Affected").split(","), controller, hostCard); + } + + return affectedCards; + } + +} diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCostChange.java b/forge-gui/src/main/java/forge/game/staticability/StaticAbilityCostChange.java similarity index 100% rename from forge-game/src/main/java/forge/game/staticability/StaticAbilityCostChange.java rename to forge-gui/src/main/java/forge/game/staticability/StaticAbilityCostChange.java diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityETBTapped.java b/forge-gui/src/main/java/forge/game/staticability/StaticAbilityETBTapped.java similarity index 100% rename from forge-game/src/main/java/forge/game/staticability/StaticAbilityETBTapped.java rename to forge-gui/src/main/java/forge/game/staticability/StaticAbilityETBTapped.java diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityMayLookAt.java b/forge-gui/src/main/java/forge/game/staticability/StaticAbilityMayLookAt.java similarity index 100% rename from forge-game/src/main/java/forge/game/staticability/StaticAbilityMayLookAt.java rename to forge-gui/src/main/java/forge/game/staticability/StaticAbilityMayLookAt.java diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityPreventDamage.java b/forge-gui/src/main/java/forge/game/staticability/StaticAbilityPreventDamage.java similarity index 97% rename from forge-game/src/main/java/forge/game/staticability/StaticAbilityPreventDamage.java rename to forge-gui/src/main/java/forge/game/staticability/StaticAbilityPreventDamage.java index 464d4e9e87b..6ee428cff60 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityPreventDamage.java +++ b/forge-gui/src/main/java/forge/game/staticability/StaticAbilityPreventDamage.java @@ -1,112 +1,112 @@ -/* - * 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 . - */ -package forge.game.staticability; - -import java.util.HashMap; - -import forge.game.GameEntity; -import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; - -/** - * The Class StaticAbility_PreventDamage. - */ -public class StaticAbilityPreventDamage { - - /** - * TODO Write javadoc for this method. - * - * @param stAb - * a StaticAbility - * @param source - * the source - * @param target - * the target - * @param damage - * the damage - * @param isCombat - * the is combat - * @return the int - */ - public static int applyPreventDamageAbility(final StaticAbility stAb, final Card source, final GameEntity target, - final int damage, final boolean isCombat, final boolean isTest) { - final HashMap params = stAb.getMapParams(); - final Card hostCard = stAb.getHostCard(); - int restDamage = damage; - - if (params.containsKey("Source") - && !source.isValid(params.get("Source").split(","), hostCard.getController(), hostCard)) { - return restDamage; - } - - if (params.containsKey("Target") - && !target.isValid(params.get("Target").split(","), hostCard.getController(), hostCard)) { - return restDamage; - } - - if (params.containsKey("CombatDamage") && params.get("CombatDamage").equals("True") && !isCombat) { - return restDamage; - } - - if (params.containsKey("CombatDamage") && params.get("CombatDamage").equals("False") && isCombat) { - return restDamage; - } - - if (params.containsKey("MaxDamage") && (Integer.parseInt(params.get("MaxDamage")) < damage)) { - return restDamage; - } - - if (params.containsKey("Optional")) { //Assume if param is present it should be optional - if (!isTest) { - final String logic = params.containsKey("AILogic") ? params.get("AILogic") : ""; - final String message = "Apply the effect of " + hostCard + "? (Affected: " + target + ")"; - boolean confirmed = hostCard.getController().getController().confirmStaticApplication(hostCard, target, logic, message); - - if (!confirmed) { - return restDamage; - } - } else { //test - if (!hostCard.getController().equals(target)) { - return restDamage; - } - } - } - - // no amount means all - if (!params.containsKey("Amount") || params.get("Amount").equals("All")) { - return 0; - } - - if (params.get("Amount").matches("[0-9][0-9]?")) { - restDamage = restDamage - Integer.parseInt(params.get("Amount")); - } else if (params.get("Amount").matches("HalfUp")) { - restDamage = restDamage - (int) (Math.ceil(restDamage / 2.0)); - } else if (params.get("Amount").matches("HalfDown")) { - restDamage = restDamage - (int) (Math.floor(restDamage / 2.0)); - } else { - restDamage = restDamage - CardFactoryUtil.xCount(hostCard, hostCard.getSVar(params.get("Amount"))); - } - - if (restDamage < 0) { - return 0; - } - - return restDamage; - } - -} +/* + * 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 . + */ +package forge.game.staticability; + +import java.util.HashMap; + +import forge.game.GameEntity; +import forge.game.card.Card; +import forge.game.card.CardFactoryUtil; + +/** + * The Class StaticAbility_PreventDamage. + */ +public class StaticAbilityPreventDamage { + + /** + * TODO Write javadoc for this method. + * + * @param stAb + * a StaticAbility + * @param source + * the source + * @param target + * the target + * @param damage + * the damage + * @param isCombat + * the is combat + * @return the int + */ + public static int applyPreventDamageAbility(final StaticAbility stAb, final Card source, final GameEntity target, + final int damage, final boolean isCombat, final boolean isTest) { + final HashMap params = stAb.getMapParams(); + final Card hostCard = stAb.getHostCard(); + int restDamage = damage; + + if (params.containsKey("Source") + && !source.isValid(params.get("Source").split(","), hostCard.getController(), hostCard)) { + return restDamage; + } + + if (params.containsKey("Target") + && !target.isValid(params.get("Target").split(","), hostCard.getController(), hostCard)) { + return restDamage; + } + + if (params.containsKey("CombatDamage") && params.get("CombatDamage").equals("True") && !isCombat) { + return restDamage; + } + + if (params.containsKey("CombatDamage") && params.get("CombatDamage").equals("False") && isCombat) { + return restDamage; + } + + if (params.containsKey("MaxDamage") && (Integer.parseInt(params.get("MaxDamage")) < damage)) { + return restDamage; + } + + if (params.containsKey("Optional")) { //Assume if param is present it should be optional + if (!isTest) { + final String logic = params.containsKey("AILogic") ? params.get("AILogic") : ""; + final String message = "Apply the effect of " + hostCard + "? (Affected: " + target + ")"; + boolean confirmed = hostCard.getController().getController().confirmStaticApplication(hostCard, target, logic, message); + + if (!confirmed) { + return restDamage; + } + } else { //test + if (!hostCard.getController().equals(target)) { + return restDamage; + } + } + } + + // no amount means all + if (!params.containsKey("Amount") || params.get("Amount").equals("All")) { + return 0; + } + + if (params.get("Amount").matches("[0-9][0-9]?")) { + restDamage = restDamage - Integer.parseInt(params.get("Amount")); + } else if (params.get("Amount").matches("HalfUp")) { + restDamage = restDamage - (int) (Math.ceil(restDamage / 2.0)); + } else if (params.get("Amount").matches("HalfDown")) { + restDamage = restDamage - (int) (Math.floor(restDamage / 2.0)); + } else { + restDamage = restDamage - CardFactoryUtil.xCount(hostCard, hostCard.getSVar(params.get("Amount"))); + } + + if (restDamage < 0) { + return 0; + } + + return restDamage; + } + +} diff --git a/forge-game/src/main/java/forge/game/staticability/package-info.java b/forge-gui/src/main/java/forge/game/staticability/package-info.java similarity index 95% rename from forge-game/src/main/java/forge/game/staticability/package-info.java rename to forge-gui/src/main/java/forge/game/staticability/package-info.java index 59b2e79ed77..d32bb9c6e75 100644 --- a/forge-game/src/main/java/forge/game/staticability/package-info.java +++ b/forge-gui/src/main/java/forge/game/staticability/package-info.java @@ -1,3 +1,3 @@ -/** Forge Card Game. */ -package forge.game.staticability; - +/** Forge Card Game. */ +package forge.game.staticability; + diff --git a/forge-game/src/main/java/forge/game/trigger/Trigger.java b/forge-gui/src/main/java/forge/game/trigger/Trigger.java similarity index 95% rename from forge-game/src/main/java/forge/game/trigger/Trigger.java rename to forge-gui/src/main/java/forge/game/trigger/Trigger.java index d3471389d92..6180270ee6a 100644 --- a/forge-game/src/main/java/forge/game/trigger/Trigger.java +++ b/forge-gui/src/main/java/forge/game/trigger/Trigger.java @@ -1,480 +1,480 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import forge.game.Game; -import forge.game.TriggerReplacementBase; -import forge.game.card.Card; -import forge.game.phase.PhaseHandler; -import forge.game.phase.PhaseType; -import forge.game.player.Player; -import forge.game.spellability.Ability; -import forge.game.spellability.OptionalCost; -import forge.game.spellability.SpellAbility; -import forge.game.zone.ZoneType; - -/** - *

- * Abstract Trigger class. Constructed by reflection only - *

- * - * @author Forge - * @version $Id: Trigger.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public abstract class Trigger extends TriggerReplacementBase { - - /** Constant nextID=0. */ - private static int nextID = 0; - - /** - *

- * resetIDs. - *

- */ - public static void resetIDs() { - Trigger.nextID = 50000; - } - - /** The ID. */ - private int id = Trigger.nextID++; - - /** - *

- * setID. - *

- * - * @param id - * a int. - */ - public final void setID(final int id) { - this.id = id; - } - - /** The map params. */ - protected final HashMap mapParams = new HashMap(); - - /** - *

- * Getter for the field mapParams. - *

- * - * @return a {@link java.util.HashMap} object. - */ - public final HashMap getMapParams() { - return this.mapParams; - } - - /** The run params. */ - private Map runParams; - - private TriggerType mode; - - private HashMap storedTriggeredObjects = null; - - /** - *

- * Setter for the field storedTriggeredObjects. - *

- * - * @param storedTriggeredObjects - * a {@link java.util.HashMap} object. - * @since 1.0.15 - */ - public final void setStoredTriggeredObjects(final HashMap storedTriggeredObjects) { - this.storedTriggeredObjects = storedTriggeredObjects; - } - - /** - *

- * Getter for the field storedTriggeredObjects. - *

- * - * @return a {@link java.util.HashMap} object. - * @since 1.0.15 - */ - public final HashMap getStoredTriggeredObjects() { - return this.storedTriggeredObjects; - } - - /** The is intrinsic. */ - private final boolean intrinsic; - - private List validPhases; - - /** - *

- * Constructor for Trigger. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public Trigger(final Map params, final Card host, final boolean intrinsic) { - this.setRunParams(new HashMap()); - this.mapParams.putAll(params); - this.setHostCard(host); - - this.intrinsic = intrinsic; - } - - /** - *

- * toString. - *

- * - * @return a {@link java.lang.String} object. - */ - @Override - public final String toString() { - if (this.mapParams.containsKey("TriggerDescription") && !this.isSuppressed()) { - return this.mapParams.get("TriggerDescription").replace("CARDNAME", this.getHostCard().getName()); - } else { - return ""; - } - } - - /** - *

- * phasesCheck. - *

- * - * @return a boolean. - */ - public final boolean phasesCheck(final Game game) { - PhaseHandler phaseHandler = game.getPhaseHandler(); - if (null != validPhases) { - if (!validPhases.contains(phaseHandler.getPhase())) { - return false; - } - } - - if (this.mapParams.containsKey("PlayerTurn")) { - if (!phaseHandler.isPlayerTurn(this.getHostCard().getController())) { - return false; - } - } - - if (this.mapParams.containsKey("OpponentTurn")) { - if (!phaseHandler.getPlayerTurn().isOpponentOf(this.getHostCard().getController())) { - return false; - } - } - - if (this.mapParams.containsKey("FirstUpkeep")) { - if (!phaseHandler.isFirstUpkeep()) { - return false; - } - } - - if (this.mapParams.containsKey("FirstCombat")) { - if (!phaseHandler.isFirstCombat()) { - return false; - } - } - - return true; - } - /** - *

- * requirementsCheck. - *

- * @param game - * - * @return a boolean. - */ - public final boolean requirementsCheck(Game game) { - return this.requirementsCheck(game, this.getRunParams()); - } - - /** - *

- * requirementsCheck. - *

- * @param game - * - * @param runParams2 - * a {@link java.util.HashMap} object. - * @return a boolean. - */ - public final boolean requirementsCheck(Game game, final Map runParams) { - - if (this.mapParams.containsKey("APlayerHasMoreLifeThanEachOther")) { - int highestLife = -50; // Negative base just in case a few Lich's or Platinum Angels are running around - final List healthiest = new ArrayList(); - for (final Player p : game.getPlayers()) { - if (p.getLife() > highestLife) { - healthiest.clear(); - highestLife = p.getLife(); - healthiest.add(p); - } else if (p.getLife() == highestLife) { - highestLife = p.getLife(); - healthiest.add(p); - } - } - - if (healthiest.size() != 1) { - // More than one player tied for most life - return false; - } - } - - if (this.mapParams.containsKey("APlayerHasMostCardsInHand")) { - int largestHand = 0; - final List withLargestHand = new ArrayList(); - for (final Player p : game.getPlayers()) { - if (p.getCardsIn(ZoneType.Hand).size() > largestHand) { - withLargestHand.clear(); - largestHand = p.getCardsIn(ZoneType.Hand).size(); - withLargestHand.add(p); - } else if (p.getCardsIn(ZoneType.Hand).size() == largestHand) { - largestHand = p.getCardsIn(ZoneType.Hand).size(); - withLargestHand.add(p); - } - } - - if (withLargestHand.size() != 1) { - // More than one player tied for most life - return false; - } - } - - if ( !meetsCommonRequirements(this.mapParams)) - return false; - -// if ( !meetsRequirementsOnTriggeredObjects(runParams) ) -// return false; - - if ("True".equals(this.mapParams.get("EvolveCondition"))) { - final Card moved = (Card) runParams.get("Card"); - if (moved == null) { - return false; - // final StringBuilder sb = new StringBuilder(); - // sb.append("Trigger::requirementsCheck() - EvolveCondition condition being checked without a moved card. "); - // sb.append(this.getHostCard().getName()); - // throw new RuntimeException(sb.toString()); - } - if (moved.getNetAttack() <= this.getHostCard().getNetAttack() - && moved.getNetDefense() <= this.getHostCard().getNetDefense()) { - return false; - } - } - - String condition = this.mapParams.get("Condition"); - if( "AltCost".equals(condition) ) { - final Card moved = (Card) runParams.get("Card"); - if( null != moved && !moved.isOptionalCostPaid(OptionalCost.AltCost)) - return false; - } - - - return true; - } - - -// private boolean meetsRequirementsOnTriggeredObjects(Map runParams) { -// -// return true; -// } - - /** - *

- * isSecondary. - *

- * - * @return a boolean. - */ - public final boolean isSecondary() { - if (this.mapParams.containsKey("Secondary")) { - if (this.mapParams.get("Secondary").equals("True")) { - return true; - } - } - return false; - } - - /** {@inheritDoc} */ - @Override - public final boolean equals(final Object o) { - if (!(o instanceof Trigger)) { - return false; - } - - return this.getId() == ((Trigger) o).getId(); - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return 41 * (41 + this.getId()); - } - - /** - *

- * performTest. - *

- * - * @param runParams2 - * a {@link java.util.HashMap} object. - * @return a boolean. - */ - public abstract boolean performTest(java.util.Map runParams2); - - /** - *

- * setTriggeringObjects. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - */ - public abstract void setTriggeringObjects(SpellAbility sa); - - /** The temporary. */ - private boolean temporary = false; - - /** - * Sets the temporary. - * - * @param temp - * the new temporary - */ - public final void setTemporary(final boolean temp) { - this.temporary = temp; - } - - /** - * Checks if is temporary. - * - * @return true, if is temporary - */ - public final boolean isTemporary() { - return this.temporary; - } - - /** - * Checks if is intrinsic. - * - * @return the isIntrinsic - */ - public boolean isIntrinsic() { - return this.intrinsic; - } - - - /** - * Gets the run params. - * - * @return the runParams - */ - public Map getRunParams() { - return this.runParams; - } - - /** - * Sets the run params. - * - * @param runParams0 - * the runParams to set - */ - public void setRunParams(final Map runParams0) { - this.runParams = runParams0; - } - - /** - * Gets the id. - * - * @return the id - */ - public int getId() { - return this.id; - } - - private Ability triggeredSA; - - /** - * Gets the triggered sa. - * - * @return the triggered sa - */ - public final Ability getTriggeredSA() { - System.out.println("TriggeredSA = " + this.triggeredSA); - return this.triggeredSA; - } - - /** - * Sets the triggered sa. - * - * @param sa - * the triggered sa to set - */ - public void setTriggeredSA(final Ability sa) { - this.triggeredSA = sa; - } - - /** - * TODO: Write javadoc for this method. - * @return the mode - */ - public TriggerType getMode() { - return mode; - } - - /** - * - * @param triggerType - * the triggerType to set - * @param triggerType - */ - void setMode(TriggerType triggerType) { - mode = triggerType; - } - - - public final Trigger getCopyForHostCard(Card newHost) { - TriggerType tt = TriggerType.getTypeFor(this); - Trigger copy = tt.createTrigger(mapParams, newHost, intrinsic); - - if (this.getOverridingAbility() != null) { - copy.setOverridingAbility(this.getOverridingAbility()); - } - - copy.setID(this.getId()); - copy.setMode(this.getMode()); - copy.setTriggerPhases(this.validPhases); - copy.setActiveZone(validHostZones); - copy.setTemporary(isTemporary()); - return copy; - } - - public boolean isStatic() { - return this.mapParams.containsKey("Static"); // && params.get("Static").equals("True") [always true if present] - } - - public void setTriggerPhases(List phases) { - validPhases = phases; - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import forge.game.Game; +import forge.game.TriggerReplacementBase; +import forge.game.card.Card; +import forge.game.phase.PhaseHandler; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.spellability.Ability; +import forge.game.spellability.OptionalCost; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; + +/** + *

+ * Abstract Trigger class. Constructed by reflection only + *

+ * + * @author Forge + * @version $Id$ + */ +public abstract class Trigger extends TriggerReplacementBase { + + /** Constant nextID=0. */ + private static int nextID = 0; + + /** + *

+ * resetIDs. + *

+ */ + public static void resetIDs() { + Trigger.nextID = 50000; + } + + /** The ID. */ + private int id = Trigger.nextID++; + + /** + *

+ * setID. + *

+ * + * @param id + * a int. + */ + public final void setID(final int id) { + this.id = id; + } + + /** The map params. */ + protected final HashMap mapParams = new HashMap(); + + /** + *

+ * Getter for the field mapParams. + *

+ * + * @return a {@link java.util.HashMap} object. + */ + public final HashMap getMapParams() { + return this.mapParams; + } + + /** The run params. */ + private Map runParams; + + private TriggerType mode; + + private HashMap storedTriggeredObjects = null; + + /** + *

+ * Setter for the field storedTriggeredObjects. + *

+ * + * @param storedTriggeredObjects + * a {@link java.util.HashMap} object. + * @since 1.0.15 + */ + public final void setStoredTriggeredObjects(final HashMap storedTriggeredObjects) { + this.storedTriggeredObjects = storedTriggeredObjects; + } + + /** + *

+ * Getter for the field storedTriggeredObjects. + *

+ * + * @return a {@link java.util.HashMap} object. + * @since 1.0.15 + */ + public final HashMap getStoredTriggeredObjects() { + return this.storedTriggeredObjects; + } + + /** The is intrinsic. */ + private final boolean intrinsic; + + private List validPhases; + + /** + *

+ * Constructor for Trigger. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public Trigger(final Map params, final Card host, final boolean intrinsic) { + this.setRunParams(new HashMap()); + this.mapParams.putAll(params); + this.setHostCard(host); + + this.intrinsic = intrinsic; + } + + /** + *

+ * toString. + *

+ * + * @return a {@link java.lang.String} object. + */ + @Override + public final String toString() { + if (this.mapParams.containsKey("TriggerDescription") && !this.isSuppressed()) { + return this.mapParams.get("TriggerDescription").replace("CARDNAME", this.getHostCard().getName()); + } else { + return ""; + } + } + + /** + *

+ * phasesCheck. + *

+ * + * @return a boolean. + */ + public final boolean phasesCheck(final Game game) { + PhaseHandler phaseHandler = game.getPhaseHandler(); + if (null != validPhases) { + if (!validPhases.contains(phaseHandler.getPhase())) { + return false; + } + } + + if (this.mapParams.containsKey("PlayerTurn")) { + if (!phaseHandler.isPlayerTurn(this.getHostCard().getController())) { + return false; + } + } + + if (this.mapParams.containsKey("OpponentTurn")) { + if (!phaseHandler.getPlayerTurn().isOpponentOf(this.getHostCard().getController())) { + return false; + } + } + + if (this.mapParams.containsKey("FirstUpkeep")) { + if (!phaseHandler.isFirstUpkeep()) { + return false; + } + } + + if (this.mapParams.containsKey("FirstCombat")) { + if (!phaseHandler.isFirstCombat()) { + return false; + } + } + + return true; + } + /** + *

+ * requirementsCheck. + *

+ * @param game + * + * @return a boolean. + */ + public final boolean requirementsCheck(Game game) { + return this.requirementsCheck(game, this.getRunParams()); + } + + /** + *

+ * requirementsCheck. + *

+ * @param game + * + * @param runParams2 + * a {@link java.util.HashMap} object. + * @return a boolean. + */ + public final boolean requirementsCheck(Game game, final Map runParams) { + + if (this.mapParams.containsKey("APlayerHasMoreLifeThanEachOther")) { + int highestLife = -50; // Negative base just in case a few Lich's or Platinum Angels are running around + final List healthiest = new ArrayList(); + for (final Player p : game.getPlayers()) { + if (p.getLife() > highestLife) { + healthiest.clear(); + highestLife = p.getLife(); + healthiest.add(p); + } else if (p.getLife() == highestLife) { + highestLife = p.getLife(); + healthiest.add(p); + } + } + + if (healthiest.size() != 1) { + // More than one player tied for most life + return false; + } + } + + if (this.mapParams.containsKey("APlayerHasMostCardsInHand")) { + int largestHand = 0; + final List withLargestHand = new ArrayList(); + for (final Player p : game.getPlayers()) { + if (p.getCardsIn(ZoneType.Hand).size() > largestHand) { + withLargestHand.clear(); + largestHand = p.getCardsIn(ZoneType.Hand).size(); + withLargestHand.add(p); + } else if (p.getCardsIn(ZoneType.Hand).size() == largestHand) { + largestHand = p.getCardsIn(ZoneType.Hand).size(); + withLargestHand.add(p); + } + } + + if (withLargestHand.size() != 1) { + // More than one player tied for most life + return false; + } + } + + if ( !meetsCommonRequirements(this.mapParams)) + return false; + +// if ( !meetsRequirementsOnTriggeredObjects(runParams) ) +// return false; + + if ("True".equals(this.mapParams.get("EvolveCondition"))) { + final Card moved = (Card) runParams.get("Card"); + if (moved == null) { + return false; + // final StringBuilder sb = new StringBuilder(); + // sb.append("Trigger::requirementsCheck() - EvolveCondition condition being checked without a moved card. "); + // sb.append(this.getHostCard().getName()); + // throw new RuntimeException(sb.toString()); + } + if (moved.getNetAttack() <= this.getHostCard().getNetAttack() + && moved.getNetDefense() <= this.getHostCard().getNetDefense()) { + return false; + } + } + + String condition = this.mapParams.get("Condition"); + if( "AltCost".equals(condition) ) { + final Card moved = (Card) runParams.get("Card"); + if( null != moved && !moved.isOptionalCostPaid(OptionalCost.AltCost)) + return false; + } + + + return true; + } + + +// private boolean meetsRequirementsOnTriggeredObjects(Map runParams) { +// +// return true; +// } + + /** + *

+ * isSecondary. + *

+ * + * @return a boolean. + */ + public final boolean isSecondary() { + if (this.mapParams.containsKey("Secondary")) { + if (this.mapParams.get("Secondary").equals("True")) { + return true; + } + } + return false; + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(final Object o) { + if (!(o instanceof Trigger)) { + return false; + } + + return this.getId() == ((Trigger) o).getId(); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return 41 * (41 + this.getId()); + } + + /** + *

+ * performTest. + *

+ * + * @param runParams2 + * a {@link java.util.HashMap} object. + * @return a boolean. + */ + public abstract boolean performTest(java.util.Map runParams2); + + /** + *

+ * setTriggeringObjects. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + */ + public abstract void setTriggeringObjects(SpellAbility sa); + + /** The temporary. */ + private boolean temporary = false; + + /** + * Sets the temporary. + * + * @param temp + * the new temporary + */ + public final void setTemporary(final boolean temp) { + this.temporary = temp; + } + + /** + * Checks if is temporary. + * + * @return true, if is temporary + */ + public final boolean isTemporary() { + return this.temporary; + } + + /** + * Checks if is intrinsic. + * + * @return the isIntrinsic + */ + public boolean isIntrinsic() { + return this.intrinsic; + } + + + /** + * Gets the run params. + * + * @return the runParams + */ + public Map getRunParams() { + return this.runParams; + } + + /** + * Sets the run params. + * + * @param runParams0 + * the runParams to set + */ + public void setRunParams(final Map runParams0) { + this.runParams = runParams0; + } + + /** + * Gets the id. + * + * @return the id + */ + public int getId() { + return this.id; + } + + private Ability triggeredSA; + + /** + * Gets the triggered sa. + * + * @return the triggered sa + */ + public final Ability getTriggeredSA() { + System.out.println("TriggeredSA = " + this.triggeredSA); + return this.triggeredSA; + } + + /** + * Sets the triggered sa. + * + * @param sa + * the triggered sa to set + */ + public void setTriggeredSA(final Ability sa) { + this.triggeredSA = sa; + } + + /** + * TODO: Write javadoc for this method. + * @return the mode + */ + public TriggerType getMode() { + return mode; + } + + /** + * + * @param triggerType + * the triggerType to set + * @param triggerType + */ + void setMode(TriggerType triggerType) { + mode = triggerType; + } + + + public final Trigger getCopyForHostCard(Card newHost) { + TriggerType tt = TriggerType.getTypeFor(this); + Trigger copy = tt.createTrigger(mapParams, newHost, intrinsic); + + if (this.getOverridingAbility() != null) { + copy.setOverridingAbility(this.getOverridingAbility()); + } + + copy.setID(this.getId()); + copy.setMode(this.getMode()); + copy.setTriggerPhases(this.validPhases); + copy.setActiveZone(validHostZones); + copy.setTemporary(isTemporary()); + return copy; + } + + public boolean isStatic() { + return this.mapParams.containsKey("Static"); // && params.get("Static").equals("True") [always true if present] + } + + public void setTriggerPhases(List phases) { + validPhases = phases; + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerAlways.java b/forge-gui/src/main/java/forge/game/trigger/TriggerAlways.java similarity index 92% rename from forge-game/src/main/java/forge/game/trigger/TriggerAlways.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerAlways.java index e53ae6df288..abafcdbd36e 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerAlways.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerAlways.java @@ -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 . - */ -package forge.game.trigger; - -import java.util.Map; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Always class. - *

- * - * @author Forge - * @version $Id: TriggerAlways.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerAlways extends Trigger { - - /** - *

- * Constructor for Trigger_Always. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerAlways(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final Map runParams2) { - return true; - } - - /** {@inheritDoc} */ - @Override - public void setTriggeringObjects(final SpellAbility sa) { - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.Map; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Always class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerAlways extends Trigger { + + /** + *

+ * Constructor for Trigger_Always. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerAlways(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final Map runParams2) { + return true; + } + + /** {@inheritDoc} */ + @Override + public void setTriggeringObjects(final SpellAbility sa) { + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerAttached.java b/forge-gui/src/main/java/forge/game/trigger/TriggerAttached.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerAttached.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerAttached.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java b/forge-gui/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java similarity index 94% rename from forge-game/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java index 2c2cefd5893..748874a253d 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java @@ -1,86 +1,86 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.List; -import java.util.Map; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_AttackerBlocked class. - *

- * - * @author Forge - * @version $Id: TriggerAttackerBlocked.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerAttackerBlocked extends Trigger { - - /** - *

- * Constructor for Trigger_AttackerBlocked. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerAttackerBlocked(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final Map runParams2) { - if (this.mapParams.containsKey("ValidCard")) { - if (!matchesValid(runParams2.get("Attacker"), this.mapParams.get("ValidCard").split(","), - this.getHostCard())) { - return false; - } - } - if (this.mapParams.containsKey("ValidBlocker")) { - boolean valid = false; - @SuppressWarnings("unchecked") - List list = (List) runParams2.get("Blockers"); - for (Card b : list) { - if (matchesValid(b, this.mapParams.get("ValidBlocker").split(","), this.getHostCard())) { - valid = true; - break; - } - } - if (!valid) { - return false; - } - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Attacker", this.getRunParams().get("Attacker")); - sa.setTriggeringObject("Blockers", this.getRunParams().get("Blockers")); - sa.setTriggeringObject("NumBlockers", this.getRunParams().get("NumBlockers")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.List; +import java.util.Map; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_AttackerBlocked class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerAttackerBlocked extends Trigger { + + /** + *

+ * Constructor for Trigger_AttackerBlocked. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerAttackerBlocked(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final Map runParams2) { + if (this.mapParams.containsKey("ValidCard")) { + if (!matchesValid(runParams2.get("Attacker"), this.mapParams.get("ValidCard").split(","), + this.getHostCard())) { + return false; + } + } + if (this.mapParams.containsKey("ValidBlocker")) { + boolean valid = false; + @SuppressWarnings("unchecked") + List list = (List) runParams2.get("Blockers"); + for (Card b : list) { + if (matchesValid(b, this.mapParams.get("ValidBlocker").split(","), this.getHostCard())) { + valid = true; + break; + } + } + if (!valid) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Attacker", this.getRunParams().get("Attacker")); + sa.setTriggeringObject("Blockers", this.getRunParams().get("Blockers")); + sa.setTriggeringObject("NumBlockers", this.getRunParams().get("NumBlockers")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerAttackerUnblocked.java b/forge-gui/src/main/java/forge/game/trigger/TriggerAttackerUnblocked.java similarity index 93% rename from forge-game/src/main/java/forge/game/trigger/TriggerAttackerUnblocked.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerAttackerUnblocked.java index d0e95f92e2b..b5f52b5d7c6 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerAttackerUnblocked.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerAttackerUnblocked.java @@ -1,75 +1,75 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.Map; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_AttackerUnblocked class. - *

- * - * @author Forge - * @version $Id: TriggerAttackerUnblocked.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerAttackerUnblocked extends Trigger { - - /** - *

- * Constructor for Trigger_AttackerUnblocked. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerAttackerUnblocked(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final Map runParams2) { - if (this.mapParams.containsKey("ValidCard")) { - if (!matchesValid(runParams2.get("Attacker"), this.mapParams.get("ValidCard").split(","), - this.getHostCard())) { - return false; - } - } - if (this.mapParams.containsKey("ValidDefender")) { - if (!matchesValid(runParams2.get("Defender"), this.mapParams.get("ValidDefender").split(","), - this.getHostCard())) { - return false; - } - } - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Attacker", this.getRunParams().get("Attacker")); - sa.setTriggeringObject("Defender", this.getRunParams().get("Defender")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.Map; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_AttackerUnblocked class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerAttackerUnblocked extends Trigger { + + /** + *

+ * Constructor for Trigger_AttackerUnblocked. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerAttackerUnblocked(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final Map runParams2) { + if (this.mapParams.containsKey("ValidCard")) { + if (!matchesValid(runParams2.get("Attacker"), this.mapParams.get("ValidCard").split(","), + this.getHostCard())) { + return false; + } + } + if (this.mapParams.containsKey("ValidDefender")) { + if (!matchesValid(runParams2.get("Defender"), this.mapParams.get("ValidDefender").split(","), + this.getHostCard())) { + return false; + } + } + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Attacker", this.getRunParams().get("Attacker")); + sa.setTriggeringObject("Defender", this.getRunParams().get("Defender")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerAttackersDeclared.java b/forge-gui/src/main/java/forge/game/trigger/TriggerAttackersDeclared.java similarity index 97% rename from forge-game/src/main/java/forge/game/trigger/TriggerAttackersDeclared.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerAttackersDeclared.java index 89d60fe5ab3..a3988de5e50 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerAttackersDeclared.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerAttackersDeclared.java @@ -1,86 +1,86 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.List; -import java.util.Map; - -import forge.game.GameEntity; -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - * TODO Write javadoc for this type. - * - */ -public class TriggerAttackersDeclared extends Trigger { - - /** - * Instantiates a new trigger_ attackers declared. - * - * @param params - * the params - * @param host - * the host - * @param intrinsic - * the intrinsic - */ - public TriggerAttackersDeclared(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final Map runParams2) { - if (this.mapParams.containsKey("SingleAttacker")) { - @SuppressWarnings("unchecked") - final List attackers = (List) runParams2.get("Attackers"); - if (attackers.size() != 1) { - return false; - } - } - if (this.mapParams.containsKey("AttackingPlayer")) { - if (!matchesValid(runParams2.get("AttackingPlayer"), - this.mapParams.get("AttackingPlayer").split(","), this.getHostCard())) { - return false; - } - } - if (this.mapParams.containsKey("AttackedTarget")) { - boolean valid = false; - @SuppressWarnings("unchecked") - List list = (List) runParams2.get("AttackedTarget"); - for (GameEntity b : list) { - if (matchesValid(b, this.mapParams.get("AttackedTarget").split(","), this.getHostCard())) { - valid = true; - break; - } - } - if (!valid) { - return false; - } - } - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Attackers", this.getRunParams().get("Attackers")); - sa.setTriggeringObject("AttackingPlayer", this.getRunParams().get("AttackingPlayer")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.List; +import java.util.Map; + +import forge.game.GameEntity; +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + * TODO Write javadoc for this type. + * + */ +public class TriggerAttackersDeclared extends Trigger { + + /** + * Instantiates a new trigger_ attackers declared. + * + * @param params + * the params + * @param host + * the host + * @param intrinsic + * the intrinsic + */ + public TriggerAttackersDeclared(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final Map runParams2) { + if (this.mapParams.containsKey("SingleAttacker")) { + @SuppressWarnings("unchecked") + final List attackers = (List) runParams2.get("Attackers"); + if (attackers.size() != 1) { + return false; + } + } + if (this.mapParams.containsKey("AttackingPlayer")) { + if (!matchesValid(runParams2.get("AttackingPlayer"), + this.mapParams.get("AttackingPlayer").split(","), this.getHostCard())) { + return false; + } + } + if (this.mapParams.containsKey("AttackedTarget")) { + boolean valid = false; + @SuppressWarnings("unchecked") + List list = (List) runParams2.get("AttackedTarget"); + for (GameEntity b : list) { + if (matchesValid(b, this.mapParams.get("AttackedTarget").split(","), this.getHostCard())) { + valid = true; + break; + } + } + if (!valid) { + return false; + } + } + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Attackers", this.getRunParams().get("Attackers")); + sa.setTriggeringObject("AttackingPlayer", this.getRunParams().get("AttackingPlayer")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerAttacks.java b/forge-gui/src/main/java/forge/game/trigger/TriggerAttacks.java similarity index 95% rename from forge-game/src/main/java/forge/game/trigger/TriggerAttacks.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerAttacks.java index 7b6aa466d38..f0a7cccb556 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerAttacks.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerAttacks.java @@ -1,107 +1,107 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.List; -import java.util.Map; - -import forge.game.GameEntity; -import forge.game.card.Card; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Attacks class. - *

- * - * @author Forge - * @version $Id: TriggerAttacks.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerAttacks extends Trigger { - - /** - *

- * Constructor for Trigger_Attacks. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerAttacks(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final Map runParams2) { - if (this.mapParams.containsKey("ValidCard")) { - if (!matchesValid(runParams2.get("Attacker"), this.mapParams.get("ValidCard").split(","), - this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("Attacked")) { - GameEntity attacked = (GameEntity) runParams2.get("Attacked"); - if (!attacked.isValid(this.mapParams.get("Attacked").split(",") - , this.getHostCard().getController(), this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("Alone")) { - @SuppressWarnings("unchecked") - final List otherAttackers = (List) runParams2.get("OtherAttackers"); - if (otherAttackers == null) { - return false; - } - if (this.mapParams.get("Alone").equals("True")) { - if (otherAttackers.size() != 0) { - return false; - } - } else { - if (otherAttackers.size() == 0) { - return false; - } - } - } - - if (this.mapParams.containsKey("FirstAttack")) { - Card attacker = (Card) runParams2.get("Attacker"); - if (attacker.getDamageHistory().getCreatureAttacksThisTurn() > 1) { - return false; - } - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Attacker", this.getRunParams().get("Attacker")); - sa.setTriggeringObject("Defender", this.getRunParams().get("Attacked")); - final Player defendingPlayer = ((Card) this.getRunParams().get("Attacker")).getGame().getCombat().getDefenderPlayerByAttacker((Card) this.getRunParams().get("Attacker")); - sa.setTriggeringObject("DefendingPlayer", defendingPlayer); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.List; +import java.util.Map; + +import forge.game.GameEntity; +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Attacks class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerAttacks extends Trigger { + + /** + *

+ * Constructor for Trigger_Attacks. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerAttacks(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final Map runParams2) { + if (this.mapParams.containsKey("ValidCard")) { + if (!matchesValid(runParams2.get("Attacker"), this.mapParams.get("ValidCard").split(","), + this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("Attacked")) { + GameEntity attacked = (GameEntity) runParams2.get("Attacked"); + if (!attacked.isValid(this.mapParams.get("Attacked").split(",") + , this.getHostCard().getController(), this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("Alone")) { + @SuppressWarnings("unchecked") + final List otherAttackers = (List) runParams2.get("OtherAttackers"); + if (otherAttackers == null) { + return false; + } + if (this.mapParams.get("Alone").equals("True")) { + if (otherAttackers.size() != 0) { + return false; + } + } else { + if (otherAttackers.size() == 0) { + return false; + } + } + } + + if (this.mapParams.containsKey("FirstAttack")) { + Card attacker = (Card) runParams2.get("Attacker"); + if (attacker.getDamageHistory().getCreatureAttacksThisTurn() > 1) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Attacker", this.getRunParams().get("Attacker")); + sa.setTriggeringObject("Defender", this.getRunParams().get("Attacked")); + final Player defendingPlayer = ((Card) this.getRunParams().get("Attacker")).getGame().getCombat().getDefenderPlayerByAttacker((Card) this.getRunParams().get("Attacker")); + sa.setTriggeringObject("DefendingPlayer", defendingPlayer); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerBecomeMonstrous.java b/forge-gui/src/main/java/forge/game/trigger/TriggerBecomeMonstrous.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerBecomeMonstrous.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerBecomeMonstrous.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerBecomesTarget.java b/forge-gui/src/main/java/forge/game/trigger/TriggerBecomesTarget.java similarity index 94% rename from forge-game/src/main/java/forge/game/trigger/TriggerBecomesTarget.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerBecomesTarget.java index 2d386917754..193c89f0fb7 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerBecomesTarget.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerBecomesTarget.java @@ -1,95 +1,95 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.Map; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_BecomesTarget class. - *

- * - * @author Forge - * @version $Id: TriggerBecomesTarget.java 23787 2013-11-24 07:09:23Z Max mtg $ - * @since 1.0.15 - */ -public class TriggerBecomesTarget extends Trigger { - - /** - *

- * Constructor for Trigger_BecomesTarget. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerBecomesTarget(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final Map runParams2) { - if (this.mapParams.containsKey("SourceType")) { - final SpellAbility sa = (SpellAbility) runParams2.get("SourceSA"); - if (this.mapParams.get("SourceType").equalsIgnoreCase("spell")) { - if (!sa.isSpell()) { - return false; - } - } else if (this.mapParams.get("SourceType").equalsIgnoreCase("ability")) { - if (!sa.isAbility()) { - return false; - } - } - } - if (this.mapParams.containsKey("ValidSource")) { - if (!matchesValid(((SpellAbility) runParams2.get("SourceSA")).getSourceCard(), this.mapParams - .get("ValidSource").split(","), this.getHostCard())) { - return false; - } - } - if (this.mapParams.containsKey("ValidTarget")) { - if (!matchesValid(runParams2.get("Target"), this.mapParams.get("ValidTarget").split(","), - this.getHostCard())) { - return false; - } - } - if (this.mapParams.containsKey("FirstTime")) { - if (!runParams2.containsKey("FirstTime")) { - return false; - } - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("SourceSA", this.getRunParams().get("SourceSA")); - sa.setTriggeringObject("Source", ((SpellAbility) this.getRunParams().get("SourceSA")).getSourceCard()); - sa.setTriggeringObject("Target", this.getRunParams().get("Target")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.Map; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_BecomesTarget class. + *

+ * + * @author Forge + * @version $Id$ + * @since 1.0.15 + */ +public class TriggerBecomesTarget extends Trigger { + + /** + *

+ * Constructor for Trigger_BecomesTarget. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerBecomesTarget(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final Map runParams2) { + if (this.mapParams.containsKey("SourceType")) { + final SpellAbility sa = (SpellAbility) runParams2.get("SourceSA"); + if (this.mapParams.get("SourceType").equalsIgnoreCase("spell")) { + if (!sa.isSpell()) { + return false; + } + } else if (this.mapParams.get("SourceType").equalsIgnoreCase("ability")) { + if (!sa.isAbility()) { + return false; + } + } + } + if (this.mapParams.containsKey("ValidSource")) { + if (!matchesValid(((SpellAbility) runParams2.get("SourceSA")).getSourceCard(), this.mapParams + .get("ValidSource").split(","), this.getHostCard())) { + return false; + } + } + if (this.mapParams.containsKey("ValidTarget")) { + if (!matchesValid(runParams2.get("Target"), this.mapParams.get("ValidTarget").split(","), + this.getHostCard())) { + return false; + } + } + if (this.mapParams.containsKey("FirstTime")) { + if (!runParams2.containsKey("FirstTime")) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("SourceSA", this.getRunParams().get("SourceSA")); + sa.setTriggeringObject("Source", ((SpellAbility) this.getRunParams().get("SourceSA")).getSourceCard()); + sa.setTriggeringObject("Target", this.getRunParams().get("Target")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerBlockersDeclared.java b/forge-gui/src/main/java/forge/game/trigger/TriggerBlockersDeclared.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerBlockersDeclared.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerBlockersDeclared.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerBlocks.java b/forge-gui/src/main/java/forge/game/trigger/TriggerBlocks.java similarity index 95% rename from forge-game/src/main/java/forge/game/trigger/TriggerBlocks.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerBlocks.java index be8da046de1..8adc657f6fd 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerBlocks.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerBlocks.java @@ -1,94 +1,94 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.Map; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Blocks class. - *

- * - * @author Forge - * @version $Id: TriggerBlocks.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerBlocks extends Trigger { - - /** - *

- * Constructor for Trigger_Blocks. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerBlocks(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final Map runParams2) { - Card blocker = (Card) runParams2.get("Blocker"); - Card attacker = (Card) runParams2.get("Attacker"); - if (this.mapParams.containsKey("ValidCard")) { - String validBlocker = this.mapParams.get("ValidCard"); - if (validBlocker.contains(".withLesserPower")) { - // Have to check this here as triggering objects aren't set yet for AI combat trigger checks - // so ValidCard$Creature.powerLTX where X:TriggeredAttacker$CardPower crashes with NPE - validBlocker = validBlocker.replace(".withLesserPower", ""); - if (blocker.getCurrentPower() >= attacker.getCurrentPower()) { - return false; - } - } - if (!matchesValid(runParams2.get("Blocker"), validBlocker.split(","), this.getHostCard())) { - return false; - } - } - if (this.mapParams.containsKey("ValidBlocked")) { - String validBlocked = this.mapParams.get("ValidBlocked"); - if (validBlocked.contains(".withLesserPower")) { - // Have to check this here as triggering objects aren't set yet for AI combat trigger checks - // so ValidBlocked$Creature.powerLTX where X:TriggeredBlocker$CardPower crashes with NPE - validBlocked = validBlocked.replace(".withLesserPower", ""); - if (blocker.getCurrentPower() <= attacker.getCurrentPower()) { - return false; - } - } - if (!matchesValid(runParams2.get("Attacker"), validBlocked.split(","), this.getHostCard())) { - return false; - } - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Blocker", this.getRunParams().get("Blocker")); - sa.setTriggeringObject("Attacker", this.getRunParams().get("Attacker")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.Map; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Blocks class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerBlocks extends Trigger { + + /** + *

+ * Constructor for Trigger_Blocks. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerBlocks(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final Map runParams2) { + Card blocker = (Card) runParams2.get("Blocker"); + Card attacker = (Card) runParams2.get("Attacker"); + if (this.mapParams.containsKey("ValidCard")) { + String validBlocker = this.mapParams.get("ValidCard"); + if (validBlocker.contains(".withLesserPower")) { + // Have to check this here as triggering objects aren't set yet for AI combat trigger checks + // so ValidCard$Creature.powerLTX where X:TriggeredAttacker$CardPower crashes with NPE + validBlocker = validBlocker.replace(".withLesserPower", ""); + if (blocker.getCurrentPower() >= attacker.getCurrentPower()) { + return false; + } + } + if (!matchesValid(runParams2.get("Blocker"), validBlocker.split(","), this.getHostCard())) { + return false; + } + } + if (this.mapParams.containsKey("ValidBlocked")) { + String validBlocked = this.mapParams.get("ValidBlocked"); + if (validBlocked.contains(".withLesserPower")) { + // Have to check this here as triggering objects aren't set yet for AI combat trigger checks + // so ValidBlocked$Creature.powerLTX where X:TriggeredBlocker$CardPower crashes with NPE + validBlocked = validBlocked.replace(".withLesserPower", ""); + if (blocker.getCurrentPower() <= attacker.getCurrentPower()) { + return false; + } + } + if (!matchesValid(runParams2.get("Attacker"), validBlocked.split(","), this.getHostCard())) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Blocker", this.getRunParams().get("Blocker")); + sa.setTriggeringObject("Attacker", this.getRunParams().get("Attacker")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerChampioned.java b/forge-gui/src/main/java/forge/game/trigger/TriggerChampioned.java similarity index 93% rename from forge-game/src/main/java/forge/game/trigger/TriggerChampioned.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerChampioned.java index 4d4127b34a3..b6b90940c39 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerChampioned.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerChampioned.java @@ -1,71 +1,71 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Championed class. - *

- * - * @author Forge - * @version $Id: TriggerChampioned.java 23787 2013-11-24 07:09:23Z Max mtg $ - * @since 1.0.15 - */ -public class TriggerChampioned extends Trigger { - - /** - *

- * Constructor for Trigger_Championed. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerChampioned(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - final Card championed = (Card) runParams2.get("Championed"); - - if (this.mapParams.containsKey("ValidCard")) { - if (!championed.isValid(this.mapParams.get("ValidCard").split(","), - this.getHostCard().getController(), this.getHostCard())) { - return false; - } - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Championed", this.getRunParams().get("Championed")); - sa.setTriggeringObject("Card", this.getRunParams().get("Card")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Championed class. + *

+ * + * @author Forge + * @version $Id$ + * @since 1.0.15 + */ +public class TriggerChampioned extends Trigger { + + /** + *

+ * Constructor for Trigger_Championed. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerChampioned(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + final Card championed = (Card) runParams2.get("Championed"); + + if (this.mapParams.containsKey("ValidCard")) { + if (!championed.isValid(this.mapParams.get("ValidCard").split(","), + this.getHostCard().getController(), this.getHostCard())) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Championed", this.getRunParams().get("Championed")); + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerChangesController.java b/forge-gui/src/main/java/forge/game/trigger/TriggerChangesController.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerChangesController.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerChangesController.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java b/forge-gui/src/main/java/forge/game/trigger/TriggerChangesZone.java similarity index 95% rename from forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerChangesZone.java index cc3ca9fae7f..f108767f865 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerChangesZone.java @@ -1,114 +1,114 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.Map; - -import forge.game.ability.AbilityUtils; -import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; -import forge.game.spellability.SpellAbility; -import forge.util.Expressions; - -/** - *

- * Trigger_ChangesZone class. - *

- * - * @author Forge - * @version $Id: TriggerChangesZone.java 23788 2013-11-24 07:17:43Z Max mtg $ - */ -public class TriggerChangesZone extends Trigger { - - /** - *

- * Constructor for Trigger_ChangesZone. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerChangesZone(final Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - if (this.mapParams.containsKey("Origin")) { - if (!this.mapParams.get("Origin").equals("Any")) { - if (this.mapParams.get("Origin") == null) { - return false; - } - if (!this.mapParams.get("Origin").equals(runParams2.get("Origin"))) { - return false; - } - } - } - - if (this.mapParams.containsKey("Destination")) { - if (!this.mapParams.get("Destination").equals("Any")) { - if (!this.mapParams.get("Destination").equals(runParams2.get("Destination"))) { - return false; - } - } - } - - if (this.mapParams.containsKey("ExcludedDestinations")) { - for (final String notTo : this.mapParams.get("ExcludedDestinations").split(",")) { - if (notTo.equals(runParams2.get("Destination"))) { - return false; - } - } - } - - if (this.mapParams.containsKey("ValidCard")) { - final Card moved = (Card) runParams2.get("Card"); - if (!moved.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), - this.getHostCard())) { - return false; - } - } - - // Check number of lands ETB this turn on triggered card's controller - if (mapParams.containsKey("CheckOnTriggeredCard")) { - final String[] condition = mapParams.get("CheckOnTriggeredCard").split(" ", 2); - - final Card host = hostCard.getGame().getCardState(hostCard); - final String comparator = condition.length < 2 ? "GE1" : condition[1]; - final int referenceValue = AbilityUtils.calculateAmount(host, comparator.substring(2), null); - final Card triggered = (Card)runParams2.get("Card"); - final int actualValue = CardFactoryUtil.xCount((Card)triggered, host.getSVar(condition[0])); - if (!Expressions.compare(actualValue, comparator.substring(0, 2), referenceValue)) { - return false; - } - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Card", this.getRunParams().get("Card")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.Map; + +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.card.CardFactoryUtil; +import forge.game.spellability.SpellAbility; +import forge.util.Expressions; + +/** + *

+ * Trigger_ChangesZone class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerChangesZone extends Trigger { + + /** + *

+ * Constructor for Trigger_ChangesZone. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerChangesZone(final Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + if (this.mapParams.containsKey("Origin")) { + if (!this.mapParams.get("Origin").equals("Any")) { + if (this.mapParams.get("Origin") == null) { + return false; + } + if (!this.mapParams.get("Origin").equals(runParams2.get("Origin"))) { + return false; + } + } + } + + if (this.mapParams.containsKey("Destination")) { + if (!this.mapParams.get("Destination").equals("Any")) { + if (!this.mapParams.get("Destination").equals(runParams2.get("Destination"))) { + return false; + } + } + } + + if (this.mapParams.containsKey("ExcludedDestinations")) { + for (final String notTo : this.mapParams.get("ExcludedDestinations").split(",")) { + if (notTo.equals(runParams2.get("Destination"))) { + return false; + } + } + } + + if (this.mapParams.containsKey("ValidCard")) { + final Card moved = (Card) runParams2.get("Card"); + if (!moved.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), + this.getHostCard())) { + return false; + } + } + + // Check number of lands ETB this turn on triggered card's controller + if (mapParams.containsKey("CheckOnTriggeredCard")) { + final String[] condition = mapParams.get("CheckOnTriggeredCard").split(" ", 2); + + final Card host = hostCard.getGame().getCardState(hostCard); + final String comparator = condition.length < 2 ? "GE1" : condition[1]; + final int referenceValue = AbilityUtils.calculateAmount(host, comparator.substring(2), null); + final Card triggered = (Card)runParams2.get("Card"); + final int actualValue = CardFactoryUtil.xCount((Card)triggered, host.getSVar(condition[0])); + if (!Expressions.compare(actualValue, comparator.substring(0, 2), referenceValue)) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerClashed.java b/forge-gui/src/main/java/forge/game/trigger/TriggerClashed.java similarity index 93% rename from forge-game/src/main/java/forge/game/trigger/TriggerClashed.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerClashed.java index 42f2d832c2e..d92ba147a68 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerClashed.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerClashed.java @@ -1,75 +1,75 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.Map; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Clashed class. - *

- * - * @author Forge - * @version $Id: TriggerClashed.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerClashed extends Trigger { - - /** - *

- * Constructor for Trigger_Clashed. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerClashed(final Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - if (this.mapParams.containsKey("ValidPlayer")) { - if (!matchesValid(runParams2.get("Player"), this.mapParams.get("ValidPlayer").split(","), - this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("Won")) { - if (!this.mapParams.get("Won").equals(runParams2.get("Won"))) { - return false; - } - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public void setTriggeringObjects(final SpellAbility sa) { - // No triggered-variables for you :( - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.Map; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Clashed class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerClashed extends Trigger { + + /** + *

+ * Constructor for Trigger_Clashed. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerClashed(final Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + if (this.mapParams.containsKey("ValidPlayer")) { + if (!matchesValid(runParams2.get("Player"), this.mapParams.get("ValidPlayer").split(","), + this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("Won")) { + if (!this.mapParams.get("Won").equals(runParams2.get("Won"))) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public void setTriggeringObjects(final SpellAbility sa) { + // No triggered-variables for you :( + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerCombatDamageDoneOnce.java b/forge-gui/src/main/java/forge/game/trigger/TriggerCombatDamageDoneOnce.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerCombatDamageDoneOnce.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerCombatDamageDoneOnce.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerCounterAdded.java b/forge-gui/src/main/java/forge/game/trigger/TriggerCounterAdded.java similarity index 93% rename from forge-game/src/main/java/forge/game/trigger/TriggerCounterAdded.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerCounterAdded.java index 5c2699f871c..d87e07c0613 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerCounterAdded.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerCounterAdded.java @@ -1,78 +1,78 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.card.Card; -import forge.game.card.CounterType; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_CounterAdded class. - *

- * - * @author Forge - * @version $Id: TriggerCounterAdded.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerCounterAdded extends Trigger { - - /** - *

- * Constructor for Trigger_CounterAdded. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerCounterAdded(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - final Card addedTo = (Card) runParams2.get("Card"); - final CounterType addedType = (CounterType) runParams2.get("CounterType"); - - if (this.mapParams.containsKey("ValidCard")) { - if (!addedTo.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), - this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("CounterType")) { - final String type = this.mapParams.get("CounterType"); - if (!type.equals(addedType.toString())) { - return false; - } - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Card", this.getRunParams().get("Card")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import forge.game.card.Card; +import forge.game.card.CounterType; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_CounterAdded class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerCounterAdded extends Trigger { + + /** + *

+ * Constructor for Trigger_CounterAdded. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerCounterAdded(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + final Card addedTo = (Card) runParams2.get("Card"); + final CounterType addedType = (CounterType) runParams2.get("CounterType"); + + if (this.mapParams.containsKey("ValidCard")) { + if (!addedTo.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), + this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("CounterType")) { + final String type = this.mapParams.get("CounterType"); + if (!type.equals(addedType.toString())) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerCounterRemoved.java b/forge-gui/src/main/java/forge/game/trigger/TriggerCounterRemoved.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerCounterRemoved.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerCounterRemoved.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerCountered.java b/forge-gui/src/main/java/forge/game/trigger/TriggerCountered.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerCountered.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerCountered.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerCycled.java b/forge-gui/src/main/java/forge/game/trigger/TriggerCycled.java similarity index 93% rename from forge-game/src/main/java/forge/game/trigger/TriggerCycled.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerCycled.java index 369688377ba..ec58bfc6d82 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerCycled.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerCycled.java @@ -1,67 +1,67 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Cycled class. - *

- * - * @author Forge - * @version $Id: TriggerCycled.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerCycled extends Trigger { - - /** - *

- * Constructor for Trigger_Cycled. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * a boolean - */ - public TriggerCycled(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Card", this.getRunParams().get("Card")); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - if (this.mapParams.containsKey("ValidCard")) { - if (!matchesValid(runParams2.get("Card"), this.mapParams.get("ValidCard").split(","), - this.getHostCard())) { - return false; - } - } - 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 . + */ +package forge.game.trigger; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Cycled class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerCycled extends Trigger { + + /** + *

+ * Constructor for Trigger_Cycled. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * a boolean + */ + public TriggerCycled(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + if (this.mapParams.containsKey("ValidCard")) { + if (!matchesValid(runParams2.get("Card"), this.mapParams.get("ValidCard").split(","), + this.getHostCard())) { + return false; + } + } + return true; + } + +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDamageDone.java b/forge-gui/src/main/java/forge/game/trigger/TriggerDamageDone.java similarity index 95% rename from forge-game/src/main/java/forge/game/trigger/TriggerDamageDone.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerDamageDone.java index 83f30c31828..53977ff5503 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerDamageDone.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerDamageDone.java @@ -1,108 +1,108 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; -import forge.util.Expressions; - -/** - *

- * Trigger_DamageDone class. - *

- * - * @author Forge - * @version $Id: TriggerDamageDone.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerDamageDone extends Trigger { - - /** - *

- * Constructor for Trigger_DamageDone. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerDamageDone(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - final Card src = (Card) runParams2.get("DamageSource"); - final Object tgt = runParams2.get("DamageTarget"); - - if (this.mapParams.containsKey("ValidSource")) { - if (!src.isValid(this.mapParams.get("ValidSource").split(","), this.getHostCard().getController(), - this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("ValidTarget")) { - if (!matchesValid(tgt, this.mapParams.get("ValidTarget").split(","), this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("CombatDamage")) { - if (this.mapParams.get("CombatDamage").equals("True")) { - if (!((Boolean) runParams2.get("IsCombatDamage"))) { - return false; - } - } else if (this.mapParams.get("CombatDamage").equals("False")) { - if (((Boolean) runParams2.get("IsCombatDamage"))) { - return false; - } - } - } - - if (this.mapParams.containsKey("DamageAmount")) { - final String fullParam = this.mapParams.get("DamageAmount"); - - final String operator = fullParam.substring(0, 2); - final int operand = Integer.parseInt(fullParam.substring(2)); - final int actualAmount = (Integer) runParams2.get("DamageAmount"); - - if (!Expressions.compare(actualAmount, operator, operand)) { - return false; - } - - System.out.print("DamageDone Amount Operator: "); - System.out.println(operator); - System.out.print("DamageDone Amount Operand: "); - System.out.println(operand); - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Source", this.getRunParams().get("DamageSource")); - sa.setTriggeringObject("Target", this.getRunParams().get("DamageTarget")); - sa.setTriggeringObject("DamageAmount", this.getRunParams().get("DamageAmount")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; +import forge.util.Expressions; + +/** + *

+ * Trigger_DamageDone class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerDamageDone extends Trigger { + + /** + *

+ * Constructor for Trigger_DamageDone. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerDamageDone(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + final Card src = (Card) runParams2.get("DamageSource"); + final Object tgt = runParams2.get("DamageTarget"); + + if (this.mapParams.containsKey("ValidSource")) { + if (!src.isValid(this.mapParams.get("ValidSource").split(","), this.getHostCard().getController(), + this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("ValidTarget")) { + if (!matchesValid(tgt, this.mapParams.get("ValidTarget").split(","), this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("CombatDamage")) { + if (this.mapParams.get("CombatDamage").equals("True")) { + if (!((Boolean) runParams2.get("IsCombatDamage"))) { + return false; + } + } else if (this.mapParams.get("CombatDamage").equals("False")) { + if (((Boolean) runParams2.get("IsCombatDamage"))) { + return false; + } + } + } + + if (this.mapParams.containsKey("DamageAmount")) { + final String fullParam = this.mapParams.get("DamageAmount"); + + final String operator = fullParam.substring(0, 2); + final int operand = Integer.parseInt(fullParam.substring(2)); + final int actualAmount = (Integer) runParams2.get("DamageAmount"); + + if (!Expressions.compare(actualAmount, operator, operand)) { + return false; + } + + System.out.print("DamageDone Amount Operator: "); + System.out.println(operator); + System.out.print("DamageDone Amount Operand: "); + System.out.println(operand); + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Source", this.getRunParams().get("DamageSource")); + sa.setTriggeringObject("Target", this.getRunParams().get("DamageTarget")); + sa.setTriggeringObject("DamageAmount", this.getRunParams().get("DamageAmount")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDestroyed.java b/forge-gui/src/main/java/forge/game/trigger/TriggerDestroyed.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerDestroyed.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerDestroyed.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDevoured.java b/forge-gui/src/main/java/forge/game/trigger/TriggerDevoured.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerDevoured.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerDevoured.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDiscarded.java b/forge-gui/src/main/java/forge/game/trigger/TriggerDiscarded.java similarity index 94% rename from forge-game/src/main/java/forge/game/trigger/TriggerDiscarded.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerDiscarded.java index 3804502b1b3..49a6bf1dde0 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerDiscarded.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerDiscarded.java @@ -1,91 +1,91 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Discarded class. - *

- * - * @author Forge - * @version $Id: TriggerDiscarded.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerDiscarded extends Trigger { - - /** - *

- * Constructor for Trigger_Discarded. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerDiscarded(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - if (this.mapParams.containsKey("ValidCard")) { - if (!matchesValid(runParams2.get("Card"), this.mapParams.get("ValidCard").split(","), - this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("ValidPlayer")) { - if (!matchesValid(runParams2.get("Player"), this.mapParams.get("ValidPlayer").split(","), - this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("ValidCause")) { - if (runParams2.get("Cause") == null) { - return false; - } - if (!matchesValid(runParams2.get("Cause"), this.mapParams.get("ValidCause").split(","), - this.getHostCard())) { - return false; - } - } - if (this.mapParams.containsKey("IsMadness")) { - Boolean madness = (Boolean) runParams2.get("IsMadness"); - if (this.mapParams.get("IsMadness").equals("True") ^ madness) { - return false; - } - } - return true; - } - - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Card", this.getRunParams().get("Card")); - sa.setTriggeringObject("Cause", this.getRunParams().get("Cause")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Discarded class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerDiscarded extends Trigger { + + /** + *

+ * Constructor for Trigger_Discarded. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerDiscarded(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + if (this.mapParams.containsKey("ValidCard")) { + if (!matchesValid(runParams2.get("Card"), this.mapParams.get("ValidCard").split(","), + this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("ValidPlayer")) { + if (!matchesValid(runParams2.get("Player"), this.mapParams.get("ValidPlayer").split(","), + this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("ValidCause")) { + if (runParams2.get("Cause") == null) { + return false; + } + if (!matchesValid(runParams2.get("Cause"), this.mapParams.get("ValidCause").split(","), + this.getHostCard())) { + return false; + } + } + if (this.mapParams.containsKey("IsMadness")) { + Boolean madness = (Boolean) runParams2.get("IsMadness"); + if (this.mapParams.get("IsMadness").equals("True") ^ madness) { + return false; + } + } + return true; + } + + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + sa.setTriggeringObject("Cause", this.getRunParams().get("Cause")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java b/forge-gui/src/main/java/forge/game/trigger/TriggerDrawn.java similarity index 94% rename from forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerDrawn.java index 33faaea9854..f6d07ec2a21 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerDrawn.java @@ -1,76 +1,76 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Drawn class. - *

- * - * @author Forge - * @version $Id: TriggerDrawn.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerDrawn extends Trigger { - - /** - *

- * Constructor for Trigger_Drawn. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerDrawn(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - final Card draw = ((Card) runParams2.get("Card")); - final int number = ((Integer) runParams2.get("Number")); - - if (this.mapParams.containsKey("ValidCard")) { - if (!draw.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), - this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("Number")) { - if (number != Integer.parseInt(this.mapParams.get("Number"))) { - return false; - } - } - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Card", this.getRunParams().get("Card")); - sa.setTriggeringObject("Player", this.getRunParams().get("Player")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Drawn class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerDrawn extends Trigger { + + /** + *

+ * Constructor for Trigger_Drawn. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerDrawn(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + final Card draw = ((Card) runParams2.get("Card")); + final int number = ((Integer) runParams2.get("Number")); + + if (this.mapParams.containsKey("ValidCard")) { + if (!draw.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), + this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("Number")) { + if (number != Integer.parseInt(this.mapParams.get("Number"))) { + return false; + } + } + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + sa.setTriggeringObject("Player", this.getRunParams().get("Player")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerEvolved.java b/forge-gui/src/main/java/forge/game/trigger/TriggerEvolved.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerEvolved.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerEvolved.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerFlippedCoin.java b/forge-gui/src/main/java/forge/game/trigger/TriggerFlippedCoin.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerFlippedCoin.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerFlippedCoin.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-gui/src/main/java/forge/game/trigger/TriggerHandler.java similarity index 97% rename from forge-game/src/main/java/forge/game/trigger/TriggerHandler.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerHandler.java index 6038fa49b03..7f9988193e8 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerHandler.java @@ -1,429 +1,429 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import forge.card.mana.ManaCost; -import forge.game.Game; -import forge.game.GlobalRuleChange; -import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; -import forge.game.ability.effects.CharmEffect; -import forge.game.card.Card; -import forge.game.phase.PhaseType; -import forge.game.player.Player; -import forge.game.spellability.Ability; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; -import forge.game.zone.ZoneType; - -public class TriggerHandler { - private final ArrayList suppressedModes = new ArrayList(); - - private final ArrayList delayedTriggers = new ArrayList(); - private final List waitingTriggers = new ArrayList(); - private final Game game; - - /** - * TODO: Write javadoc for Constructor. - * @param gameState - */ - public TriggerHandler(Game gameState) { - game = gameState; - } - - public final void cleanUpTemporaryTriggers() { - final List absolutelyAllCards = game.getCardsInGame(); - for (final Card c : absolutelyAllCards) { - for (int i = 0; i < c.getTriggers().size(); i++) { - if (c.getTriggers().get(i).isTemporary()) { - c.getTriggers().remove(i); - i--; - } - } - } - for (final Card c : absolutelyAllCards) { - for (int i = 0; i < c.getTriggers().size(); i++) { - c.getTriggers().get(i).setTemporarilySuppressed(false); - } - } - } - - public final void registerDelayedTrigger(final Trigger trig) { - this.delayedTriggers.add(trig); - } - - public final void clearDelayedTrigger() { - this.delayedTriggers.clear(); - } - - public final void clearDelayedTrigger(Card card) { - ArrayList deltrigs = new ArrayList(this.delayedTriggers); - - for (Trigger trigger : deltrigs) { - if (trigger.getHostCard().equals(card)) { - this.delayedTriggers.remove(trigger); - } - } - } - - - public final void suppressMode(final TriggerType mode) { - this.suppressedModes.add(mode); - } - - public final void clearSuppression(final TriggerType mode) { - this.suppressedModes.remove(mode); - } - - public static Trigger parseTrigger(final String trigParse, final Card host, final boolean intrinsic) { - final HashMap mapParams = TriggerHandler.parseParams(trigParse); - return TriggerHandler.parseTrigger(mapParams, host, intrinsic); - } - - public static Trigger parseTrigger(final Map mapParams, final Card host, final boolean intrinsic) { - Trigger ret = null; - - final TriggerType type = TriggerType.smartValueOf(mapParams.get("Mode")); - ret = type.createTrigger(mapParams, host, intrinsic); - - String triggerZones = mapParams.get("TriggerZones"); - if (null != triggerZones) { - ret.setActiveZone(EnumSet.copyOf(ZoneType.listValueOf(triggerZones))); - } - - String triggerPhases = mapParams.get("Phase"); - if (null != triggerPhases) { - ret.setTriggerPhases(PhaseType.parseRange(triggerPhases)); - } - - return ret; - } - - private static HashMap parseParams(final String trigParse) { - final HashMap mapParams = new HashMap(); - - if (trigParse.length() == 0) { - throw new RuntimeException("TriggerFactory : registerTrigger -- trigParse too short"); - } - - final String[] params = trigParse.split("\\|"); - - for (int i = 0; i < params.length; i++) { - params[i] = params[i].trim(); - } - - for (final String param : params) { - final String[] splitParam = param.split("\\$"); - for (int i = 0; i < splitParam.length; i++) { - splitParam[i] = splitParam[i].trim(); - } - - if (splitParam.length != 2) { - final StringBuilder sb = new StringBuilder(); - sb.append("TriggerFactory Parsing Error in registerTrigger() : Split length of "); - sb.append(param).append(" is not 2."); - throw new RuntimeException(sb.toString()); - } - - mapParams.put(splitParam[0], splitParam[1]); - } - - return mapParams; - } - - public final void runTrigger(final TriggerType mode, final Map runParams, boolean holdTrigger) { - if (this.suppressedModes.contains(mode)) { - return; - } - - //runWaitingTrigger(new TriggerWaiting(mode, runParams)); - - if (game.getStack().isFrozen() || holdTrigger) { - waitingTriggers.add(new TriggerWaiting(mode, runParams)); - } else { - runWaitingTrigger(new TriggerWaiting(mode, runParams)); - } - // Tell auto stop to stop - } - - public final boolean runWaitingTriggers() { - ArrayList waiting = new ArrayList(waitingTriggers); - waitingTriggers.clear(); - if (waiting.isEmpty()) { - return false; - } - - boolean haveWaiting = false; - for (TriggerWaiting wt : waiting) { - haveWaiting |= runWaitingTrigger(wt); - } - - return haveWaiting; - } - - public final boolean runWaitingTrigger(TriggerWaiting wt) { - final TriggerType mode = wt.getMode(); - final Map runParams = wt.getParams(); - - final Player playerAP = game.getPhaseHandler().getPlayerTurn(); - if (playerAP == null) { - // This should only happen outside of games, so it's safe to just - // abort. - return false; - } - - // This is done to allow the list of triggers to be modified while - // triggers are running. - final ArrayList delayedTriggersWorkingCopy = new ArrayList(this.delayedTriggers); - List allCards = new ArrayList(); - for(Player p : game.getPlayers()) - { - allCards.addAll(p.getAllCards()); - } - //allCards.addAll(game.getCardsIn(ZoneType.Stack)); - boolean checkStatics = false; - - // Static triggers - for (final Card c : allCards) { - for (final Trigger t : c.getTriggers()) { - if (t.isStatic() && canRunTrigger(t, mode, runParams)) { - this.runSingleTrigger(t, runParams); - checkStatics = true; - } - } - } - - if (runParams.containsKey("Destination")) { - // Check static abilities when a card enters the battlefield - String type = (String) runParams.get("Destination"); - checkStatics |= type.equals("Battlefield"); - } - - // AP - checkStatics |= runNonStaticTriggersForPlayer(playerAP, mode, runParams, delayedTriggersWorkingCopy); - - // NAPs - for (Player nap : game.getPlayers()) { - if (!nap.equals(playerAP)) - checkStatics |= runNonStaticTriggersForPlayer(nap, mode, runParams, delayedTriggersWorkingCopy); - } - - return checkStatics; - } - - private boolean runNonStaticTriggersForPlayer(final Player player, final TriggerType mode, - final Map runParams, final ArrayList delayedTriggersWorkingCopy ) { - - boolean checkStatics = false; - List playerCards = player.getAllCards(); - - // check LKI copies for triggers - if (runParams.containsKey("Destination") && !ZoneType.Battlefield.name().equals(runParams.get("Destination")) - && runParams.containsKey("Card")) { - Card card = (Card) runParams.get("Card"); - if (card.getController() == player) { - for (final Trigger t : card.getTriggers()) { - if (!t.isStatic() && (card.isCloned() || !t.isIntrinsic()) - && canRunTrigger(t, mode, runParams)) { - this.runSingleTrigger(t, runParams); - checkStatics = true; - } - } - } - } - - for (final Card c : playerCards) { - for (final Trigger t : c.getTriggers()) { - if (!t.isStatic() && canRunTrigger(t, mode, runParams)) { - this.runSingleTrigger(t, runParams); - checkStatics = true; - } - } - } - - for (Trigger deltrig : delayedTriggersWorkingCopy) { - if (deltrig.getHostCard().getController().equals(player)) { - if (this.canRunTrigger(deltrig, mode, runParams)) { - this.runSingleTrigger(deltrig, runParams); - this.delayedTriggers.remove(deltrig); - } - } - } - return checkStatics; - } - - private boolean canRunTrigger(final Trigger regtrig, final TriggerType mode, final Map runParams) { - if (regtrig.getMode() != mode) { - return false; // Not the right mode. - } - - if (!regtrig.phasesCheck(game)) { - return false; // It's not the right phase to go off. - } - - if (!regtrig.requirementsCheck(game, runParams)) { - return false; // Conditions aren't right. - } - if (regtrig.getHostCard().isFaceDown() && regtrig.isIntrinsic()) { - return false; // Morphed cards only have pumped triggers go off. - } - if (regtrig instanceof TriggerAlways) { - if (game.getStack().hasStateTrigger(regtrig.getId())) { - return false; // State triggers that are already on the stack - // don't trigger again. - } - } - - if (!regtrig.performTest(runParams)) { - return false; // Test failed. - } - if (regtrig.isSuppressed()) { - return false; // Trigger removed by effect - } - if (!regtrig.zonesCheck(game.getZoneOf(regtrig.getHostCard()))) { - return false; // Host card isn't where it needs to be. - } - - // Torpor Orb check - if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCreatureETBTriggers) - && !regtrig.isStatic() && mode.equals(TriggerType.ChangesZone)) { - if (runParams.get("Destination") instanceof String) { - String dest = (String) runParams.get("Destination"); - if (dest.equals("Battlefield") && runParams.get("Card") instanceof Card) { - Card card = (Card) runParams.get("Card"); - if (card.isCreature()) { - return false; - } - } - } - } // Torpor Orb check - return true; - } - - - // Checks if the conditions are right for a single trigger to go off, and - // runs it if so. - // Return true if the trigger went off, false otherwise. - private void runSingleTrigger(final Trigger regtrig, final Map runParams) { - final Map triggerParams = regtrig.mapParams; - - // Any trigger should cause the phase not to skip - for (Player p : game.getPlayers()) { - p.getController().autoPassCancel(); - } - - regtrig.setRunParams(runParams); - - // All tests passed, execute ability. - if (regtrig instanceof TriggerTapsForMana) { - final SpellAbility abMana = (SpellAbility) runParams.get("AbilityMana"); - if (null != abMana && null != abMana.getManaPart()) { - abMana.setUndoable(false); - } - } - - SpellAbility sa = null; - Card host = regtrig.getHostCard(); - - sa = regtrig.getOverridingAbility(); - if (sa == null) { - if (!triggerParams.containsKey("Execute")) { - sa = new Ability(regtrig.getHostCard(), ManaCost.ZERO) { - @Override - public void resolve() { - } - }; - } else { - sa = AbilityFactory.getAbility(host.getSVar(triggerParams.get("Execute")), host); - } - } - host = game.getCardState(regtrig.getHostCard()); - sa.setSourceCard(host); - sa.setTrigger(true); - sa.setSourceTrigger(regtrig.getId()); - regtrig.setTriggeringObjects(sa); - if (regtrig.getStoredTriggeredObjects() != null) { - sa.setAllTriggeringObjects(regtrig.getStoredTriggeredObjects()); - } - - sa.setActivatingPlayer(host.getController()); - if (triggerParams.containsKey("TriggerController")) { - Player p = AbilityUtils.getDefinedPlayers(regtrig.getHostCard(), triggerParams.get("TriggerController"), sa).get(0); - sa.setActivatingPlayer(p); - } - - if (triggerParams.containsKey("RememberController")) { - host.addRemembered(sa.getActivatingPlayer()); - } - - sa.setStackDescription(sa.toString()); - if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { - CharmEffect.makeChoices(sa); - } - - Player decider = null; - boolean mand = false; - if (triggerParams.containsKey("OptionalDecider")) { - sa.setOptionalTrigger(true); - mand = false; - decider = AbilityUtils.getDefinedPlayers(host, triggerParams.get("OptionalDecider"), sa).get(0); - } else { - mand = true; - - SpellAbility ability = sa; - while (ability != null) { - final TargetRestrictions tgt = ability.getTargetRestrictions(); - - if (tgt != null) { - tgt.setMandatory(true); - } - ability = ability.getSubAbility(); - } - } - final boolean isMandatory = mand; - - WrappedAbility wrapperAbility = new WrappedAbility(regtrig, sa, decider); - wrapperAbility.setTrigger(true); - wrapperAbility.setMandatory(isMandatory); - wrapperAbility.setDescription(wrapperAbility.getStackDescription()); - - if (regtrig.isStatic()) { - wrapperAbility.getActivatingPlayer().getController().playTrigger(host, wrapperAbility, isMandatory); - } else { - game.getStack().addSimultaneousStackEntry(wrapperAbility); - } - regtrig.setTriggeredSA(wrapperAbility); - - if (triggerParams.containsKey("OneOff")) { - if (regtrig.getHostCard().isImmutable()) { - Player p = regtrig.getHostCard().getController(); - p.getZone(ZoneType.Command).remove(regtrig.getHostCard()); - } else { - regtrig.getHostCard().getTriggers().remove(regtrig); - } - } - - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import forge.card.mana.ManaCost; +import forge.game.Game; +import forge.game.GlobalRuleChange; +import forge.game.ability.AbilityFactory; +import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; +import forge.game.ability.effects.CharmEffect; +import forge.game.card.Card; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.spellability.Ability; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.TargetRestrictions; +import forge.game.zone.ZoneType; + +public class TriggerHandler { + private final ArrayList suppressedModes = new ArrayList(); + + private final ArrayList delayedTriggers = new ArrayList(); + private final List waitingTriggers = new ArrayList(); + private final Game game; + + /** + * TODO: Write javadoc for Constructor. + * @param gameState + */ + public TriggerHandler(Game gameState) { + game = gameState; + } + + public final void cleanUpTemporaryTriggers() { + final List absolutelyAllCards = game.getCardsInGame(); + for (final Card c : absolutelyAllCards) { + for (int i = 0; i < c.getTriggers().size(); i++) { + if (c.getTriggers().get(i).isTemporary()) { + c.getTriggers().remove(i); + i--; + } + } + } + for (final Card c : absolutelyAllCards) { + for (int i = 0; i < c.getTriggers().size(); i++) { + c.getTriggers().get(i).setTemporarilySuppressed(false); + } + } + } + + public final void registerDelayedTrigger(final Trigger trig) { + this.delayedTriggers.add(trig); + } + + public final void clearDelayedTrigger() { + this.delayedTriggers.clear(); + } + + public final void clearDelayedTrigger(Card card) { + ArrayList deltrigs = new ArrayList(this.delayedTriggers); + + for (Trigger trigger : deltrigs) { + if (trigger.getHostCard().equals(card)) { + this.delayedTriggers.remove(trigger); + } + } + } + + + public final void suppressMode(final TriggerType mode) { + this.suppressedModes.add(mode); + } + + public final void clearSuppression(final TriggerType mode) { + this.suppressedModes.remove(mode); + } + + public static Trigger parseTrigger(final String trigParse, final Card host, final boolean intrinsic) { + final HashMap mapParams = TriggerHandler.parseParams(trigParse); + return TriggerHandler.parseTrigger(mapParams, host, intrinsic); + } + + public static Trigger parseTrigger(final Map mapParams, final Card host, final boolean intrinsic) { + Trigger ret = null; + + final TriggerType type = TriggerType.smartValueOf(mapParams.get("Mode")); + ret = type.createTrigger(mapParams, host, intrinsic); + + String triggerZones = mapParams.get("TriggerZones"); + if (null != triggerZones) { + ret.setActiveZone(EnumSet.copyOf(ZoneType.listValueOf(triggerZones))); + } + + String triggerPhases = mapParams.get("Phase"); + if (null != triggerPhases) { + ret.setTriggerPhases(PhaseType.parseRange(triggerPhases)); + } + + return ret; + } + + private static HashMap parseParams(final String trigParse) { + final HashMap mapParams = new HashMap(); + + if (trigParse.length() == 0) { + throw new RuntimeException("TriggerFactory : registerTrigger -- trigParse too short"); + } + + final String[] params = trigParse.split("\\|"); + + for (int i = 0; i < params.length; i++) { + params[i] = params[i].trim(); + } + + for (final String param : params) { + final String[] splitParam = param.split("\\$"); + for (int i = 0; i < splitParam.length; i++) { + splitParam[i] = splitParam[i].trim(); + } + + if (splitParam.length != 2) { + final StringBuilder sb = new StringBuilder(); + sb.append("TriggerFactory Parsing Error in registerTrigger() : Split length of "); + sb.append(param).append(" is not 2."); + throw new RuntimeException(sb.toString()); + } + + mapParams.put(splitParam[0], splitParam[1]); + } + + return mapParams; + } + + public final void runTrigger(final TriggerType mode, final Map runParams, boolean holdTrigger) { + if (this.suppressedModes.contains(mode)) { + return; + } + + //runWaitingTrigger(new TriggerWaiting(mode, runParams)); + + if (game.getStack().isFrozen() || holdTrigger) { + waitingTriggers.add(new TriggerWaiting(mode, runParams)); + } else { + runWaitingTrigger(new TriggerWaiting(mode, runParams)); + } + // Tell auto stop to stop + } + + public final boolean runWaitingTriggers() { + ArrayList waiting = new ArrayList(waitingTriggers); + waitingTriggers.clear(); + if (waiting.isEmpty()) { + return false; + } + + boolean haveWaiting = false; + for (TriggerWaiting wt : waiting) { + haveWaiting |= runWaitingTrigger(wt); + } + + return haveWaiting; + } + + public final boolean runWaitingTrigger(TriggerWaiting wt) { + final TriggerType mode = wt.getMode(); + final Map runParams = wt.getParams(); + + final Player playerAP = game.getPhaseHandler().getPlayerTurn(); + if (playerAP == null) { + // This should only happen outside of games, so it's safe to just + // abort. + return false; + } + + // This is done to allow the list of triggers to be modified while + // triggers are running. + final ArrayList delayedTriggersWorkingCopy = new ArrayList(this.delayedTriggers); + List allCards = new ArrayList(); + for(Player p : game.getPlayers()) + { + allCards.addAll(p.getAllCards()); + } + //allCards.addAll(game.getCardsIn(ZoneType.Stack)); + boolean checkStatics = false; + + // Static triggers + for (final Card c : allCards) { + for (final Trigger t : c.getTriggers()) { + if (t.isStatic() && canRunTrigger(t, mode, runParams)) { + this.runSingleTrigger(t, runParams); + checkStatics = true; + } + } + } + + if (runParams.containsKey("Destination")) { + // Check static abilities when a card enters the battlefield + String type = (String) runParams.get("Destination"); + checkStatics |= type.equals("Battlefield"); + } + + // AP + checkStatics |= runNonStaticTriggersForPlayer(playerAP, mode, runParams, delayedTriggersWorkingCopy); + + // NAPs + for (Player nap : game.getPlayers()) { + if (!nap.equals(playerAP)) + checkStatics |= runNonStaticTriggersForPlayer(nap, mode, runParams, delayedTriggersWorkingCopy); + } + + return checkStatics; + } + + private boolean runNonStaticTriggersForPlayer(final Player player, final TriggerType mode, + final Map runParams, final ArrayList delayedTriggersWorkingCopy ) { + + boolean checkStatics = false; + List playerCards = player.getAllCards(); + + // check LKI copies for triggers + if (runParams.containsKey("Destination") && !ZoneType.Battlefield.name().equals(runParams.get("Destination")) + && runParams.containsKey("Card")) { + Card card = (Card) runParams.get("Card"); + if (card.getController() == player) { + for (final Trigger t : card.getTriggers()) { + if (!t.isStatic() && (card.isCloned() || !t.isIntrinsic()) + && canRunTrigger(t, mode, runParams)) { + this.runSingleTrigger(t, runParams); + checkStatics = true; + } + } + } + } + + for (final Card c : playerCards) { + for (final Trigger t : c.getTriggers()) { + if (!t.isStatic() && canRunTrigger(t, mode, runParams)) { + this.runSingleTrigger(t, runParams); + checkStatics = true; + } + } + } + + for (Trigger deltrig : delayedTriggersWorkingCopy) { + if (deltrig.getHostCard().getController().equals(player)) { + if (this.canRunTrigger(deltrig, mode, runParams)) { + this.runSingleTrigger(deltrig, runParams); + this.delayedTriggers.remove(deltrig); + } + } + } + return checkStatics; + } + + private boolean canRunTrigger(final Trigger regtrig, final TriggerType mode, final Map runParams) { + if (regtrig.getMode() != mode) { + return false; // Not the right mode. + } + + if (!regtrig.phasesCheck(game)) { + return false; // It's not the right phase to go off. + } + + if (!regtrig.requirementsCheck(game, runParams)) { + return false; // Conditions aren't right. + } + if (regtrig.getHostCard().isFaceDown() && regtrig.isIntrinsic()) { + return false; // Morphed cards only have pumped triggers go off. + } + if (regtrig instanceof TriggerAlways) { + if (game.getStack().hasStateTrigger(regtrig.getId())) { + return false; // State triggers that are already on the stack + // don't trigger again. + } + } + + if (!regtrig.performTest(runParams)) { + return false; // Test failed. + } + if (regtrig.isSuppressed()) { + return false; // Trigger removed by effect + } + if (!regtrig.zonesCheck(game.getZoneOf(regtrig.getHostCard()))) { + return false; // Host card isn't where it needs to be. + } + + // Torpor Orb check + if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCreatureETBTriggers) + && !regtrig.isStatic() && mode.equals(TriggerType.ChangesZone)) { + if (runParams.get("Destination") instanceof String) { + String dest = (String) runParams.get("Destination"); + if (dest.equals("Battlefield") && runParams.get("Card") instanceof Card) { + Card card = (Card) runParams.get("Card"); + if (card.isCreature()) { + return false; + } + } + } + } // Torpor Orb check + return true; + } + + + // Checks if the conditions are right for a single trigger to go off, and + // runs it if so. + // Return true if the trigger went off, false otherwise. + private void runSingleTrigger(final Trigger regtrig, final Map runParams) { + final Map triggerParams = regtrig.mapParams; + + // Any trigger should cause the phase not to skip + for (Player p : game.getPlayers()) { + p.getController().autoPassCancel(); + } + + regtrig.setRunParams(runParams); + + // All tests passed, execute ability. + if (regtrig instanceof TriggerTapsForMana) { + final SpellAbility abMana = (SpellAbility) runParams.get("AbilityMana"); + if (null != abMana && null != abMana.getManaPart()) { + abMana.setUndoable(false); + } + } + + SpellAbility sa = null; + Card host = regtrig.getHostCard(); + + sa = regtrig.getOverridingAbility(); + if (sa == null) { + if (!triggerParams.containsKey("Execute")) { + sa = new Ability(regtrig.getHostCard(), ManaCost.ZERO) { + @Override + public void resolve() { + } + }; + } else { + sa = AbilityFactory.getAbility(host.getSVar(triggerParams.get("Execute")), host); + } + } + host = game.getCardState(regtrig.getHostCard()); + sa.setSourceCard(host); + sa.setTrigger(true); + sa.setSourceTrigger(regtrig.getId()); + regtrig.setTriggeringObjects(sa); + if (regtrig.getStoredTriggeredObjects() != null) { + sa.setAllTriggeringObjects(regtrig.getStoredTriggeredObjects()); + } + + sa.setActivatingPlayer(host.getController()); + if (triggerParams.containsKey("TriggerController")) { + Player p = AbilityUtils.getDefinedPlayers(regtrig.getHostCard(), triggerParams.get("TriggerController"), sa).get(0); + sa.setActivatingPlayer(p); + } + + if (triggerParams.containsKey("RememberController")) { + host.addRemembered(sa.getActivatingPlayer()); + } + + sa.setStackDescription(sa.toString()); + if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { + CharmEffect.makeChoices(sa); + } + + Player decider = null; + boolean mand = false; + if (triggerParams.containsKey("OptionalDecider")) { + sa.setOptionalTrigger(true); + mand = false; + decider = AbilityUtils.getDefinedPlayers(host, triggerParams.get("OptionalDecider"), sa).get(0); + } else { + mand = true; + + SpellAbility ability = sa; + while (ability != null) { + final TargetRestrictions tgt = ability.getTargetRestrictions(); + + if (tgt != null) { + tgt.setMandatory(true); + } + ability = ability.getSubAbility(); + } + } + final boolean isMandatory = mand; + + WrappedAbility wrapperAbility = new WrappedAbility(regtrig, sa, decider); + wrapperAbility.setTrigger(true); + wrapperAbility.setMandatory(isMandatory); + wrapperAbility.setDescription(wrapperAbility.getStackDescription()); + + if (regtrig.isStatic()) { + wrapperAbility.getActivatingPlayer().getController().playTrigger(host, wrapperAbility, isMandatory); + } else { + game.getStack().addSimultaneousStackEntry(wrapperAbility); + } + regtrig.setTriggeredSA(wrapperAbility); + + if (triggerParams.containsKey("OneOff")) { + if (regtrig.getHostCard().isImmutable()) { + Player p = regtrig.getHostCard().getController(); + p.getZone(ZoneType.Command).remove(regtrig.getHostCard()); + } else { + regtrig.getHostCard().getTriggers().remove(regtrig); + } + } + + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerLandPlayed.java b/forge-gui/src/main/java/forge/game/trigger/TriggerLandPlayed.java similarity index 93% rename from forge-game/src/main/java/forge/game/trigger/TriggerLandPlayed.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerLandPlayed.java index 8413ec4678b..236a93c7cb2 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerLandPlayed.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerLandPlayed.java @@ -1,75 +1,75 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.Map; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_LandPlayed class. - *

- * - * @author Forge - * @version $Id: TriggerLandPlayed.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerLandPlayed extends Trigger { - - /** - *

- * Constructor for Trigger_LandPlayed. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerLandPlayed(final Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Card", this.getRunParams().get("Card")); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - Card land = (Card) runParams2.get("Card"); - if (this.mapParams.containsKey("ValidCard")) { - if (!matchesValid(land, this.mapParams.get("ValidCard").split(","), this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("NotFirstLand")) { - if (land.getController().getNumLandsPlayed() < 1) { - return false; - } - } - 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 . + */ +package forge.game.trigger; + +import java.util.Map; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_LandPlayed class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerLandPlayed extends Trigger { + + /** + *

+ * Constructor for Trigger_LandPlayed. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerLandPlayed(final Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + Card land = (Card) runParams2.get("Card"); + if (this.mapParams.containsKey("ValidCard")) { + if (!matchesValid(land, this.mapParams.get("ValidCard").split(","), this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("NotFirstLand")) { + if (land.getController().getNumLandsPlayed() < 1) { + return false; + } + } + return true; + } + +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerLifeGained.java b/forge-gui/src/main/java/forge/game/trigger/TriggerLifeGained.java similarity index 93% rename from forge-game/src/main/java/forge/game/trigger/TriggerLifeGained.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerLifeGained.java index f9c18688311..f45364ed992 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerLifeGained.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerLifeGained.java @@ -1,69 +1,69 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_LifeGained class. - *

- * - * @author Forge - * @version $Id: TriggerLifeGained.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerLifeGained extends Trigger { - - /** - *

- * Constructor for Trigger_LifeGained. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerLifeGained(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - if (this.mapParams.containsKey("ValidPlayer")) { - if (!matchesValid(runParams2.get("Player"), this.mapParams.get("ValidPlayer").split(","), - this.getHostCard())) { - return false; - } - } - - return true; - } - - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("LifeAmount", this.getRunParams().get("LifeAmount")); - sa.setTriggeringObject("Player", this.getRunParams().get("Player")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_LifeGained class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerLifeGained extends Trigger { + + /** + *

+ * Constructor for Trigger_LifeGained. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerLifeGained(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + if (this.mapParams.containsKey("ValidPlayer")) { + if (!matchesValid(runParams2.get("Player"), this.mapParams.get("ValidPlayer").split(","), + this.getHostCard())) { + return false; + } + } + + return true; + } + + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("LifeAmount", this.getRunParams().get("LifeAmount")); + sa.setTriggeringObject("Player", this.getRunParams().get("Player")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerLifeLost.java b/forge-gui/src/main/java/forge/game/trigger/TriggerLifeLost.java similarity index 93% rename from forge-game/src/main/java/forge/game/trigger/TriggerLifeLost.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerLifeLost.java index 6bb44ee27d7..fc1ccbfcb17 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerLifeLost.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerLifeLost.java @@ -1,68 +1,68 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_LifeLost class. - *

- * - * @author Forge - * @version $Id: TriggerLifeLost.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerLifeLost extends Trigger { - - /** - *

- * Constructor for Trigger_LifeLost. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerLifeLost(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - if (this.mapParams.containsKey("ValidPlayer")) { - if (!matchesValid(runParams2.get("Player"), this.mapParams.get("ValidPlayer").split(","), - this.getHostCard())) { - return false; - } - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("LifeAmount", this.getRunParams().get("LifeAmount")); - sa.setTriggeringObject("Player", this.getRunParams().get("Player")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_LifeLost class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerLifeLost extends Trigger { + + /** + *

+ * Constructor for Trigger_LifeLost. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerLifeLost(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + if (this.mapParams.containsKey("ValidPlayer")) { + if (!matchesValid(runParams2.get("Player"), this.mapParams.get("ValidPlayer").split(","), + this.getHostCard())) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("LifeAmount", this.getRunParams().get("LifeAmount")); + sa.setTriggeringObject("Player", this.getRunParams().get("Player")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerLosesGame.java b/forge-gui/src/main/java/forge/game/trigger/TriggerLosesGame.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerLosesGame.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerLosesGame.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerNewGame.java b/forge-gui/src/main/java/forge/game/trigger/TriggerNewGame.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerNewGame.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerNewGame.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerPayCumulativeUpkeep.java b/forge-gui/src/main/java/forge/game/trigger/TriggerPayCumulativeUpkeep.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerPayCumulativeUpkeep.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerPayCumulativeUpkeep.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerPhase.java b/forge-gui/src/main/java/forge/game/trigger/TriggerPhase.java similarity index 93% rename from forge-game/src/main/java/forge/game/trigger/TriggerPhase.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerPhase.java index dbba0341577..d8e85aa79c6 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerPhase.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerPhase.java @@ -1,66 +1,66 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Phase class. - *

- * - * @author Forge - * @version $Id: TriggerPhase.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerPhase extends Trigger { - - /** - *

- * Constructor for Trigger_Phase. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerPhase(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - if (this.mapParams.containsKey("ValidPlayer")) { - if (!matchesValid(runParams2.get("Player"), this.mapParams.get("ValidPlayer").split(","), - this.getHostCard())) { - return false; - } - } - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Player", this.getRunParams().get("Player")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Phase class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerPhase extends Trigger { + + /** + *

+ * Constructor for Trigger_Phase. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerPhase(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + if (this.mapParams.containsKey("ValidPlayer")) { + if (!matchesValid(runParams2.get("Player"), this.mapParams.get("ValidPlayer").split(","), + this.getHostCard())) { + return false; + } + } + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Player", this.getRunParams().get("Player")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerPlanarDice.java b/forge-gui/src/main/java/forge/game/trigger/TriggerPlanarDice.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerPlanarDice.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerPlanarDice.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerPlaneswalkedFrom.java b/forge-gui/src/main/java/forge/game/trigger/TriggerPlaneswalkedFrom.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerPlaneswalkedFrom.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerPlaneswalkedFrom.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerPlaneswalkedTo.java b/forge-gui/src/main/java/forge/game/trigger/TriggerPlaneswalkedTo.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerPlaneswalkedTo.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerPlaneswalkedTo.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerSacrificed.java b/forge-gui/src/main/java/forge/game/trigger/TriggerSacrificed.java similarity index 94% rename from forge-game/src/main/java/forge/game/trigger/TriggerSacrificed.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerSacrificed.java index 8f42f7f251e..573e3e6af14 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerSacrificed.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerSacrificed.java @@ -1,81 +1,81 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Sacrificed class. - *

- * - * @author Forge - * @version $Id: TriggerSacrificed.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerSacrificed extends Trigger { - - /** - *

- * Constructor for Trigger_Sacrificed. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerSacrificed(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - final Card sac = (Card) runParams2.get("Card"); - final SpellAbility sourceSA = (SpellAbility) runParams2.get("Cause"); - if (this.mapParams.containsKey("ValidPlayer")) { - if (!matchesValid(sac.getController(), this.mapParams.get("ValidPlayer").split(","), - this.getHostCard())) { - return false; - } - } - if (this.mapParams.containsKey("ValidCard")) { - if (!sac.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), - this.getHostCard())) { - return false; - } - } - if (this.mapParams.containsKey("ValidSourceController")) { - if (sourceSA == null || !sourceSA.getActivatingPlayer().isValid(this.mapParams.get("ValidSourceController"), - this.getHostCard().getController(), this.getHostCard())) { - return false; - } - } - return true; - } - - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Card", this.getRunParams().get("Card")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Sacrificed class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerSacrificed extends Trigger { + + /** + *

+ * Constructor for Trigger_Sacrificed. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerSacrificed(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + final Card sac = (Card) runParams2.get("Card"); + final SpellAbility sourceSA = (SpellAbility) runParams2.get("Cause"); + if (this.mapParams.containsKey("ValidPlayer")) { + if (!matchesValid(sac.getController(), this.mapParams.get("ValidPlayer").split(","), + this.getHostCard())) { + return false; + } + } + if (this.mapParams.containsKey("ValidCard")) { + if (!sac.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), + this.getHostCard())) { + return false; + } + } + if (this.mapParams.containsKey("ValidSourceController")) { + if (sourceSA == null || !sourceSA.getActivatingPlayer().isValid(this.mapParams.get("ValidSourceController"), + this.getHostCard().getController(), this.getHostCard())) { + return false; + } + } + return true; + } + + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerScry.java b/forge-gui/src/main/java/forge/game/trigger/TriggerScry.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerScry.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerScry.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerSetInMotion.java b/forge-gui/src/main/java/forge/game/trigger/TriggerSetInMotion.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerSetInMotion.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerSetInMotion.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerShuffled.java b/forge-gui/src/main/java/forge/game/trigger/TriggerShuffled.java similarity index 94% rename from forge-game/src/main/java/forge/game/trigger/TriggerShuffled.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerShuffled.java index a435751d7ac..b491aefa6f6 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerShuffled.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerShuffled.java @@ -1,78 +1,78 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Shuffled class. - *

- * - * @author Forge - * @version $Id: TriggerShuffled.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerShuffled extends Trigger { - - /** - *

- * Constructor for Trigger_Shuffled. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerShuffled(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - if (this.mapParams.containsKey("ValidPlayer")) { - if (!matchesValid(runParams2.get("Player"), this.mapParams.get("ValidPlayer").split(","), - this.getHostCard())) { - return false; - } - } - if (this.mapParams.containsKey("ShuffleFromEffect")) { - if (null == runParams2.get("Source")) { - return false; - } - } - if (this.mapParams.containsKey("ShuffleBySelfControlled")) { - SpellAbility source = (SpellAbility) runParams2.get("Source"); - if (!source.getActivatingPlayer().equals(runParams2.get("Player"))) { - return false; - } - } - return true; - } - - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Player", this.getRunParams().get("Player")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Shuffled class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerShuffled extends Trigger { + + /** + *

+ * Constructor for Trigger_Shuffled. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerShuffled(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + if (this.mapParams.containsKey("ValidPlayer")) { + if (!matchesValid(runParams2.get("Player"), this.mapParams.get("ValidPlayer").split(","), + this.getHostCard())) { + return false; + } + } + if (this.mapParams.containsKey("ShuffleFromEffect")) { + if (null == runParams2.get("Source")) { + return false; + } + } + if (this.mapParams.containsKey("ShuffleBySelfControlled")) { + SpellAbility source = (SpellAbility) runParams2.get("Source"); + if (!source.getActivatingPlayer().equals(runParams2.get("Player"))) { + return false; + } + } + return true; + } + + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Player", this.getRunParams().get("Player")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCast.java b/forge-gui/src/main/java/forge/game/trigger/TriggerSpellAbilityCast.java similarity index 96% rename from forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCast.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerSpellAbilityCast.java index 43c22ae8474..ac00e43a190 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCast.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerSpellAbilityCast.java @@ -1,193 +1,193 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.Game; -import forge.game.card.Card; -import forge.game.cost.Cost; -import forge.game.player.Player; -import forge.game.spellability.OptionalCost; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.SpellAbilityStackInstance; -import forge.game.spellability.TargetChoices; - -/** - *

- * Trigger_SpellAbilityCast class. - *

- * - * @author Forge - * @version $Id: TriggerSpellAbilityCast.java 24258 2014-01-14 12:15:24Z swordshine $ - */ -public class TriggerSpellAbilityCast extends Trigger { - - /** - *

- * Constructor for Trigger_SpellAbilityCast. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerSpellAbilityCast(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - final SpellAbility spellAbility = (SpellAbility) runParams2.get("CastSA"); - if (spellAbility == null) { - System.out.println("TriggerSpellAbilityCast performTest encountered spellAbility == null. runParams2 = " + runParams2); - return false; - } - final Card cast = spellAbility.getSourceCard(); - final Game game = cast.getGame(); - final SpellAbilityStackInstance si = game.getStack().getInstanceFromSpellAbility(spellAbility); - - if (this.getMode() == TriggerType.SpellCast) { - if (!spellAbility.isSpell()) { - return false; - } - } else if (this.getMode() == TriggerType.AbilityCast) { - if (!spellAbility.isAbility()) { - return false; - } - } else if (this.getMode() == TriggerType.SpellAbilityCast) { - // Empty block for readability. - } - - if (this.mapParams.containsKey("ActivatedOnly")) { - if (spellAbility.isTrigger()) { - return false; - } - } - - if (this.mapParams.containsKey("ValidControllingPlayer")) { - if (!matchesValid(cast.getController(), this.mapParams.get("ValidControllingPlayer").split(","), - this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("ValidActivatingPlayer")) { - if (si == null || !matchesValid(si.getSpellAbility().getActivatingPlayer(), this.mapParams.get("ValidActivatingPlayer") - .split(","), this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("ValidCard")) { - if (!matchesValid(cast, this.mapParams.get("ValidCard").split(","), this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("TargetsValid")) { - SpellAbility sa = spellAbility; - if (si != null) { - sa = si.getSpellAbility(); - } - - boolean validTgtFound = false; - while (sa != null && !validTgtFound) { - for (final Card tgt : sa.getTargets().getTargetCards()) { - if (tgt.isValid(this.mapParams.get("TargetsValid").split(","), this.getHostCard() - .getController(), this.getHostCard())) { - validTgtFound = true; - break; - } - } - - for (final Player p : sa.getTargets().getTargetPlayers()) { - if (matchesValid(p, this.mapParams.get("TargetsValid").split(","), this.getHostCard())) { - validTgtFound = true; - break; - } - } - sa = sa.getSubAbility(); - } - if (!validTgtFound) { - return false; - } - } - - if (this.mapParams.containsKey("NonTapCost")) { - final Cost cost = (Cost) (runParams2.get("Cost")); - if (cost.hasTapCost()) { - return false; - } - } - - if (this.mapParams.containsKey("Conspire")) { - if (!spellAbility.isOptionalCostPaid(OptionalCost.Conspire)) { - return false; - } - if (spellAbility.getConspireInstances() == 0) { - return false; - } else { - spellAbility.subtractConspireInstance(); - //System.out.println("Conspire instances left = " + spellAbility.getConspireInstances()); - } - } - - if (this.mapParams.containsKey("IsSingleTarget")) { - int numTargeted = 0; - for (TargetChoices tc : spellAbility.getAllTargetChoices()) { - numTargeted += tc.getNumTargeted(); - } - if (numTargeted != 1) { - return false; - } - } - - if (this.mapParams.containsKey("SpellSpeed")) { - if (this.mapParams.get("SpellSpeed").equals("NotSorcerySpeed")) { - if (this.getHostCard().getController().couldCastSorcery(spellAbility)) { - return false; - } - if (this.getHostCard().hasKeyword("You may cast CARDNAME as though it had flash. If you cast it any time a " - + "sorcery couldn't have been cast, the controller of the permanent it becomes sacrifices it at the beginning" - + " of the next cleanup step.")) { - // for these cards the trigger must only fire if using their own ability to cast at instant speed - if (this.getHostCard().hasKeyword("Flash") - || this.getHostCard().getController().hasKeyword("You may cast nonland cards as though they had flash.")) { - return false; - } - } - return true; - } - } - - return true; - } - - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Card", ((SpellAbility) this.getRunParams().get("CastSA")).getSourceCard()); - sa.setTriggeringObject("SpellAbility", this.getRunParams().get("CastSA")); - sa.setTriggeringObject("Player", this.getRunParams().get("Player")); - sa.setTriggeringObject("Activator", this.getRunParams().get("Activator")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import forge.game.Game; +import forge.game.card.Card; +import forge.game.cost.Cost; +import forge.game.player.Player; +import forge.game.spellability.OptionalCost; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityStackInstance; +import forge.game.spellability.TargetChoices; + +/** + *

+ * Trigger_SpellAbilityCast class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerSpellAbilityCast extends Trigger { + + /** + *

+ * Constructor for Trigger_SpellAbilityCast. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerSpellAbilityCast(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + final SpellAbility spellAbility = (SpellAbility) runParams2.get("CastSA"); + if (spellAbility == null) { + System.out.println("TriggerSpellAbilityCast performTest encountered spellAbility == null. runParams2 = " + runParams2); + return false; + } + final Card cast = spellAbility.getSourceCard(); + final Game game = cast.getGame(); + final SpellAbilityStackInstance si = game.getStack().getInstanceFromSpellAbility(spellAbility); + + if (this.getMode() == TriggerType.SpellCast) { + if (!spellAbility.isSpell()) { + return false; + } + } else if (this.getMode() == TriggerType.AbilityCast) { + if (!spellAbility.isAbility()) { + return false; + } + } else if (this.getMode() == TriggerType.SpellAbilityCast) { + // Empty block for readability. + } + + if (this.mapParams.containsKey("ActivatedOnly")) { + if (spellAbility.isTrigger()) { + return false; + } + } + + if (this.mapParams.containsKey("ValidControllingPlayer")) { + if (!matchesValid(cast.getController(), this.mapParams.get("ValidControllingPlayer").split(","), + this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("ValidActivatingPlayer")) { + if (si == null || !matchesValid(si.getSpellAbility().getActivatingPlayer(), this.mapParams.get("ValidActivatingPlayer") + .split(","), this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("ValidCard")) { + if (!matchesValid(cast, this.mapParams.get("ValidCard").split(","), this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("TargetsValid")) { + SpellAbility sa = spellAbility; + if (si != null) { + sa = si.getSpellAbility(); + } + + boolean validTgtFound = false; + while (sa != null && !validTgtFound) { + for (final Card tgt : sa.getTargets().getTargetCards()) { + if (tgt.isValid(this.mapParams.get("TargetsValid").split(","), this.getHostCard() + .getController(), this.getHostCard())) { + validTgtFound = true; + break; + } + } + + for (final Player p : sa.getTargets().getTargetPlayers()) { + if (matchesValid(p, this.mapParams.get("TargetsValid").split(","), this.getHostCard())) { + validTgtFound = true; + break; + } + } + sa = sa.getSubAbility(); + } + if (!validTgtFound) { + return false; + } + } + + if (this.mapParams.containsKey("NonTapCost")) { + final Cost cost = (Cost) (runParams2.get("Cost")); + if (cost.hasTapCost()) { + return false; + } + } + + if (this.mapParams.containsKey("Conspire")) { + if (!spellAbility.isOptionalCostPaid(OptionalCost.Conspire)) { + return false; + } + if (spellAbility.getConspireInstances() == 0) { + return false; + } else { + spellAbility.subtractConspireInstance(); + //System.out.println("Conspire instances left = " + spellAbility.getConspireInstances()); + } + } + + if (this.mapParams.containsKey("IsSingleTarget")) { + int numTargeted = 0; + for (TargetChoices tc : spellAbility.getAllTargetChoices()) { + numTargeted += tc.getNumTargeted(); + } + if (numTargeted != 1) { + return false; + } + } + + if (this.mapParams.containsKey("SpellSpeed")) { + if (this.mapParams.get("SpellSpeed").equals("NotSorcerySpeed")) { + if (this.getHostCard().getController().couldCastSorcery(spellAbility)) { + return false; + } + if (this.getHostCard().hasKeyword("You may cast CARDNAME as though it had flash. If you cast it any time a " + + "sorcery couldn't have been cast, the controller of the permanent it becomes sacrifices it at the beginning" + + " of the next cleanup step.")) { + // for these cards the trigger must only fire if using their own ability to cast at instant speed + if (this.getHostCard().hasKeyword("Flash") + || this.getHostCard().getController().hasKeyword("You may cast nonland cards as though they had flash.")) { + return false; + } + } + return true; + } + } + + return true; + } + + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", ((SpellAbility) this.getRunParams().get("CastSA")).getSourceCard()); + sa.setTriggeringObject("SpellAbility", this.getRunParams().get("CastSA")); + sa.setTriggeringObject("Player", this.getRunParams().get("Player")); + sa.setTriggeringObject("Activator", this.getRunParams().get("Activator")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerTaps.java b/forge-gui/src/main/java/forge/game/trigger/TriggerTaps.java similarity index 93% rename from forge-game/src/main/java/forge/game/trigger/TriggerTaps.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerTaps.java index eeee938f273..e19b2dff6bf 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerTaps.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerTaps.java @@ -1,72 +1,72 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.Map; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Taps class. - *

- * - * @author Forge - * @version $Id: TriggerTaps.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerTaps extends Trigger { - - /** - *

- * Constructor for Trigger_Taps. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * a boolean - */ - public TriggerTaps(final Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - final Card tapper = (Card) runParams2.get("Card"); - - if (this.mapParams.containsKey("ValidCard")) { - if (!tapper.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), - this.getHostCard())) { - return false; - } - } - - return true; - } - - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Card", this.getRunParams().get("Card")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.Map; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Taps class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerTaps extends Trigger { + + /** + *

+ * Constructor for Trigger_Taps. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * a boolean + */ + public TriggerTaps(final Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + final Card tapper = (Card) runParams2.get("Card"); + + if (this.mapParams.containsKey("ValidCard")) { + if (!tapper.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), + this.getHostCard())) { + return false; + } + } + + return true; + } + + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java b/forge-gui/src/main/java/forge/game/trigger/TriggerTapsForMana.java similarity index 95% rename from forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerTapsForMana.java index c447ef84fe7..a1760dc6965 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerTapsForMana.java @@ -1,105 +1,105 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.List; - -import forge.card.MagicColor; -import forge.game.card.Card; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_TapsForMana class. - *

- * - * @author Forge - * @version $Id: TriggerTapsForMana.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerTapsForMana extends Trigger { - - /** - *

- * Constructor for Trigger_TapsForMana. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerTapsForMana(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - - //Check for tapping - if (!mapParams.containsKey("NoTapCheck")) { - final SpellAbility manaAbility = (SpellAbility) runParams2.get("AbilityMana"); - if (manaAbility == null || manaAbility.getPayCosts() == null || !manaAbility.getPayCosts().hasTapCost()) { - return false; - } - } - - if (this.mapParams.containsKey("ValidCard")) { - final Card tapper = (Card) runParams2.get("Card"); - if (!tapper.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), - this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("Player")) { - final Player player = (Player) runParams2.get("Player"); - if (!player.isValid(this.mapParams.get("Player").split(","), this.getHostCard().getController(), this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("Produced")) { - Object prod = runParams2.get("Produced"); - if (prod == null || !(prod instanceof String)) { - return false; - } - String produced = (String) prod; - if ("ChosenColor".equals(mapParams.get("Produced"))) { - List colors = this.getHostCard().getChosenColor(); - if (colors.isEmpty() || !produced.contains(MagicColor.toShortString(colors.get(0)))) { - return false; - } - } - } - - return true; - } - - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Card", this.getRunParams().get("Card")); - sa.setTriggeringObject("Player", this.getRunParams().get("Player")); - sa.setTriggeringObject("Produced", this.getRunParams().get("Produced")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.List; + +import forge.card.MagicColor; +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_TapsForMana class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerTapsForMana extends Trigger { + + /** + *

+ * Constructor for Trigger_TapsForMana. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerTapsForMana(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + + //Check for tapping + if (!mapParams.containsKey("NoTapCheck")) { + final SpellAbility manaAbility = (SpellAbility) runParams2.get("AbilityMana"); + if (manaAbility == null || manaAbility.getPayCosts() == null || !manaAbility.getPayCosts().hasTapCost()) { + return false; + } + } + + if (this.mapParams.containsKey("ValidCard")) { + final Card tapper = (Card) runParams2.get("Card"); + if (!tapper.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), + this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("Player")) { + final Player player = (Player) runParams2.get("Player"); + if (!player.isValid(this.mapParams.get("Player").split(","), this.getHostCard().getController(), this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("Produced")) { + Object prod = runParams2.get("Produced"); + if (prod == null || !(prod instanceof String)) { + return false; + } + String produced = (String) prod; + if ("ChosenColor".equals(mapParams.get("Produced"))) { + List colors = this.getHostCard().getChosenColor(); + if (colors.isEmpty() || !produced.contains(MagicColor.toShortString(colors.get(0)))) { + return false; + } + } + } + + return true; + } + + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + sa.setTriggeringObject("Player", this.getRunParams().get("Player")); + sa.setTriggeringObject("Produced", this.getRunParams().get("Produced")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerTransformed.java b/forge-gui/src/main/java/forge/game/trigger/TriggerTransformed.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerTransformed.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerTransformed.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerTurnFaceUp.java b/forge-gui/src/main/java/forge/game/trigger/TriggerTurnFaceUp.java similarity index 93% rename from forge-game/src/main/java/forge/game/trigger/TriggerTurnFaceUp.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerTurnFaceUp.java index a3b58035653..4dfcae26468 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerTurnFaceUp.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerTurnFaceUp.java @@ -1,67 +1,67 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_TurnFaceUp class. - *

- * - * @author Forge - * @version $Id: TriggerTurnFaceUp.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerTurnFaceUp extends Trigger { - - /** - *

- * Constructor for Trigger_TurnFaceUp. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerTurnFaceUp(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - if (this.mapParams.containsKey("ValidCard")) { - if (!matchesValid(runParams2.get("Card"), this.mapParams.get("ValidCard").split(","), - this.getHostCard())) { - return false; - } - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Card", this.getRunParams().get("Card")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_TurnFaceUp class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerTurnFaceUp extends Trigger { + + /** + *

+ * Constructor for Trigger_TurnFaceUp. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerTurnFaceUp(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + if (this.mapParams.containsKey("ValidCard")) { + if (!matchesValid(runParams2.get("Card"), this.mapParams.get("ValidCard").split(","), + this.getHostCard())) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerType.java b/forge-gui/src/main/java/forge/game/trigger/TriggerType.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerType.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerType.java diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerUnequip.java b/forge-gui/src/main/java/forge/game/trigger/TriggerUnequip.java similarity index 94% rename from forge-game/src/main/java/forge/game/trigger/TriggerUnequip.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerUnequip.java index 0a835cfdf34..2a560683232 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerUnequip.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerUnequip.java @@ -1,78 +1,78 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Unequip class. - *

- * - * @author Forge - * @version $Id: TriggerUnequip.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerUnequip extends Trigger { - - /** - *

- * Constructor for Trigger_Unequip. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerUnequip(final java.util.Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - final Card equipped = (Card) runParams2.get("Card"); - final Card equipment = (Card) runParams2.get("Equipment"); - - if (this.mapParams.containsKey("ValidCard")) { - if (!equipped.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), - this.getHostCard())) { - return false; - } - } - - if (this.mapParams.containsKey("ValidEquipment")) { - if (!equipment.isValid(this.mapParams.get("ValidEquipment").split(","), this.getHostCard() - .getController(), this.getHostCard())) { - return false; - } - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Card", this.getRunParams().get("Card")); - sa.setTriggeringObject("Equipment", this.getRunParams().get("Equipment")); - } -} +/* + * 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 . + */ +package forge.game.trigger; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Unequip class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerUnequip extends Trigger { + + /** + *

+ * Constructor for Trigger_Unequip. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerUnequip(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + final Card equipped = (Card) runParams2.get("Card"); + final Card equipment = (Card) runParams2.get("Equipment"); + + if (this.mapParams.containsKey("ValidCard")) { + if (!equipped.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), + this.getHostCard())) { + return false; + } + } + + if (this.mapParams.containsKey("ValidEquipment")) { + if (!equipment.isValid(this.mapParams.get("ValidEquipment").split(","), this.getHostCard() + .getController(), this.getHostCard())) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + sa.setTriggeringObject("Equipment", this.getRunParams().get("Equipment")); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerUntaps.java b/forge-gui/src/main/java/forge/game/trigger/TriggerUntaps.java similarity index 93% rename from forge-game/src/main/java/forge/game/trigger/TriggerUntaps.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerUntaps.java index 8c3e0849532..adce6994c58 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerUntaps.java +++ b/forge-gui/src/main/java/forge/game/trigger/TriggerUntaps.java @@ -1,72 +1,72 @@ -/* - * 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 . - */ -package forge.game.trigger; - -import java.util.Map; - -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -/** - *

- * Trigger_Untaps class. - *

- * - * @author Forge - * @version $Id: TriggerUntaps.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class TriggerUntaps extends Trigger { - - /** - *

- * Constructor for Trigger_Untaps. - *

- * - * @param params - * a {@link java.util.HashMap} object. - * @param host - * a {@link forge.game.card.Card} object. - * @param intrinsic - * the intrinsic - */ - public TriggerUntaps(final Map params, final Card host, final boolean intrinsic) { - super(params, host, intrinsic); - } - - /** {@inheritDoc} */ - @Override - public final boolean performTest(final java.util.Map runParams2) { - final Card untapper = (Card) runParams2.get("Card"); - - if (this.mapParams.containsKey("ValidCard")) { - if (!untapper.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), - this.getHostCard())) { - return false; - } - } - - return true; - } - - /** {@inheritDoc} */ - @Override - public final void setTriggeringObjects(final SpellAbility sa) { - sa.setTriggeringObject("Card", this.getRunParams().get("Card")); - } - -} +/* + * 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 . + */ +package forge.game.trigger; + +import java.util.Map; + +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +/** + *

+ * Trigger_Untaps class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class TriggerUntaps extends Trigger { + + /** + *

+ * Constructor for Trigger_Untaps. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerUntaps(final Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + final Card untapper = (Card) runParams2.get("Card"); + + if (this.mapParams.containsKey("ValidCard")) { + if (!untapper.isValid(this.mapParams.get("ValidCard").split(","), this.getHostCard().getController(), + this.getHostCard())) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + } + +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerWaiting.java b/forge-gui/src/main/java/forge/game/trigger/TriggerWaiting.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/TriggerWaiting.java rename to forge-gui/src/main/java/forge/game/trigger/TriggerWaiting.java diff --git a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java b/forge-gui/src/main/java/forge/game/trigger/WrappedAbility.java similarity index 100% rename from forge-game/src/main/java/forge/game/trigger/WrappedAbility.java rename to forge-gui/src/main/java/forge/game/trigger/WrappedAbility.java diff --git a/forge-game/src/main/java/forge/game/trigger/ZCTrigger.java b/forge-gui/src/main/java/forge/game/trigger/ZCTrigger.java similarity index 94% rename from forge-game/src/main/java/forge/game/trigger/ZCTrigger.java rename to forge-gui/src/main/java/forge/game/trigger/ZCTrigger.java index 33697ea6448..301982e1827 100644 --- a/forge-game/src/main/java/forge/game/trigger/ZCTrigger.java +++ b/forge-gui/src/main/java/forge/game/trigger/ZCTrigger.java @@ -1,103 +1,103 @@ -/* - * 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 . - */ -package forge.game.trigger; - -/** - *

- * ZCTrigger class. - *

- * - * @author Forge - * @version $Id: ZCTrigger.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public enum ZCTrigger { - - /** The ENTERFIELD. */ - ENTERFIELD("comes into play", "any > field"), // explanation: zone before - // last trigger check ">" zone - // card currently in - /** The LEAVEFIELD. */ - LEAVEFIELD("leaves play", "field > any"), - - /** The DESTROY. */ - DESTROY("is put into a graveyard from play", "field > grave"), - - /** The ENTERGRAVE. */ - ENTERGRAVE("is put into a graveyard from anywhere", "any > grave"); - - /** The rule text. */ - private String ruleText; - - /** The trigger zones. */ - private String[] triggerZones; - - /** - *

- * Constructor for ZCTrigger. - *

- * - * @param text - * a {@link java.lang.String} object. - * @param tofrom - * a {@link java.lang.String} object. - */ - ZCTrigger(final String text, final String tofrom) { - this.ruleText = text; - this.triggerZones = tofrom.split(" > "); - } - - /** - *

- * triggerOn. - *

- * - * @param sourceZone - * a {@link java.lang.String} object. - * @param destintationZone - * a {@link java.lang.String} object. - * @return a boolean. - */ - public boolean triggerOn(final String sourceZone, final String destintationZone) { - return ((this.triggerZones[0].equals("any") || this.triggerZones[0].equals(sourceZone)) && (this.triggerZones[1] - .equals("any") || this.triggerZones[0].equals(sourceZone))); - } - - /** - *

- * getTrigger. - *

- * - * @param description - * a {@link java.lang.String} object. - * @return a {@link forge.game.trigger.ZCTrigger} object. - */ - public static ZCTrigger getTrigger(final String description) { - for (final ZCTrigger t : ZCTrigger.values()) { - if (t.ruleText.equals(description)) { - return t; - } - } - return null; - } - - /** {@inheritDoc} */ - @Override - public String toString() { - return this.ruleText; - } -} +/* + * 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 . + */ +package forge.game.trigger; + +/** + *

+ * ZCTrigger class. + *

+ * + * @author Forge + * @version $Id$ + */ +public enum ZCTrigger { + + /** The ENTERFIELD. */ + ENTERFIELD("comes into play", "any > field"), // explanation: zone before + // last trigger check ">" zone + // card currently in + /** The LEAVEFIELD. */ + LEAVEFIELD("leaves play", "field > any"), + + /** The DESTROY. */ + DESTROY("is put into a graveyard from play", "field > grave"), + + /** The ENTERGRAVE. */ + ENTERGRAVE("is put into a graveyard from anywhere", "any > grave"); + + /** The rule text. */ + private String ruleText; + + /** The trigger zones. */ + private String[] triggerZones; + + /** + *

+ * Constructor for ZCTrigger. + *

+ * + * @param text + * a {@link java.lang.String} object. + * @param tofrom + * a {@link java.lang.String} object. + */ + ZCTrigger(final String text, final String tofrom) { + this.ruleText = text; + this.triggerZones = tofrom.split(" > "); + } + + /** + *

+ * triggerOn. + *

+ * + * @param sourceZone + * a {@link java.lang.String} object. + * @param destintationZone + * a {@link java.lang.String} object. + * @return a boolean. + */ + public boolean triggerOn(final String sourceZone, final String destintationZone) { + return ((this.triggerZones[0].equals("any") || this.triggerZones[0].equals(sourceZone)) && (this.triggerZones[1] + .equals("any") || this.triggerZones[0].equals(sourceZone))); + } + + /** + *

+ * getTrigger. + *

+ * + * @param description + * a {@link java.lang.String} object. + * @return a {@link forge.game.trigger.ZCTrigger} object. + */ + public static ZCTrigger getTrigger(final String description) { + for (final ZCTrigger t : ZCTrigger.values()) { + if (t.ruleText.equals(description)) { + return t; + } + } + return null; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return this.ruleText; + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/package-info.java b/forge-gui/src/main/java/forge/game/trigger/package-info.java similarity index 94% rename from forge-game/src/main/java/forge/game/trigger/package-info.java rename to forge-gui/src/main/java/forge/game/trigger/package-info.java index 6166cb5f021..2df7af6f310 100644 --- a/forge-game/src/main/java/forge/game/trigger/package-info.java +++ b/forge-gui/src/main/java/forge/game/trigger/package-info.java @@ -1,3 +1,3 @@ -/** Forge Card Game. */ -package forge.game.trigger; - +/** Forge Card Game. */ +package forge.game.trigger; + diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-gui/src/main/java/forge/game/zone/MagicStack.java similarity index 97% rename from forge-game/src/main/java/forge/game/zone/MagicStack.java rename to forge-gui/src/main/java/forge/game/zone/MagicStack.java index 88eed8f7d44..559a10f61ef 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-gui/src/main/java/forge/game/zone/MagicStack.java @@ -1,936 +1,940 @@ -/* - * 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 . - */ -package forge.game.zone; - -import java.util.ArrayList; -import java.util.Deque; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Stack; -import java.util.concurrent.LinkedBlockingDeque; - -import com.esotericsoftware.minlog.Log; -import com.google.common.collect.Iterables; -import forge.card.mana.ManaCost; -import forge.game.Game; -import forge.game.GameLogEntryType; -import forge.game.GameObject; -import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; -import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; -import forge.game.card.CardPredicates.Presets; -import forge.game.cost.Cost; -import forge.game.event.GameEventCardStatsChanged; -import forge.game.event.GameEventSpellAbilityCast; -import forge.game.event.GameEventSpellRemovedFromStack; -import forge.game.event.GameEventSpellResolved; -import forge.game.player.Player; -import forge.game.player.PlayerController.ManaPaymentPurpose; -import forge.game.replacement.ReplacementEffect; -import forge.game.replacement.ReplacementHandler; -import forge.game.replacement.ReplacementLayer; -import forge.game.spellability.Ability; -import forge.game.spellability.AbilityStatic; -import forge.game.spellability.AbilityTriggered; -import forge.game.spellability.OptionalCost; -import forge.game.spellability.Spell; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.SpellAbilityStackInstance; -import forge.game.spellability.TargetChoices; -import forge.game.spellability.TargetRestrictions; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerType; - - -/** - *

- * MagicStack class. - *

- * - * @author Forge - * @version $Id: MagicStack.java 24265 2014-01-15 01:33:07Z drdev $ - */ -public class MagicStack /* extends MyObservable */ implements Iterable { - private final List simultaneousStackEntryList = new ArrayList(); - - // They don't provide a LIFO queue, so had to use a deque - private final Deque stack = new LinkedBlockingDeque(); - private final Stack frozenStack = new Stack(); - private final Stack undoStack = new Stack(); - - private boolean frozen = false; - private boolean bResolving = false; - - private final List thisTurnCast = new ArrayList(); - private List lastTurnCast = new ArrayList(); - private Card curResolvingCard = null; - - private final Game game; - - /** - * TODO: Write javadoc for Constructor. - * @param gameState - */ - public MagicStack(Game gameState) { - game = gameState; - } - - /** - *

- * isFrozen. - *

- * - * @return a boolean. - */ - public final boolean isFrozen() { - return this.frozen; - } - - /** - *

- * Setter for the field frozen. - *

- * - * @param frozen0 - * a boolean. - */ - public final void setFrozen(final boolean frozen0) { - this.frozen = frozen0; - } - - /** - *

- * reset. - *

- */ - public final void reset() { - this.clear(); - this.simultaneousStackEntryList.clear(); - this.frozen = false; - this.lastTurnCast.clear(); - this.thisTurnCast.clear(); - this.curResolvingCard = null; - this.frozenStack.clear(); - this.undoStack.clear(); - } - - /** - *

- * isSplitSecondOnStack. - *

- * - * @return a boolean. - */ - public final boolean isSplitSecondOnStack() { - for(SpellAbilityStackInstance si : this.stack) { - if (si.isSpell() && si.getSourceCard().hasKeyword("Split second")) { - return true; - } - } - return false; - } - - /** - *

- * freezeStack. - *

- */ - public final void freezeStack() { - this.frozen = true; - } - - /** - *

- * addAndUnfreeze. - *

- * - * @param ability - * a {@link forge.game.spellability.SpellAbility} object. - */ - public final void addAndUnfreeze(final SpellAbility ability) { - ability.getRestrictions().abilityActivated(); - if ((ability.getRestrictions().getActivationNumberSacrifice() != -1) - && (ability.getRestrictions().getNumberTurnActivations() >= ability.getRestrictions() - .getActivationNumberSacrifice())) { - ability.getSourceCard().addHiddenExtrinsicKeyword("At the beginning of the end step, sacrifice CARDNAME."); - } - - // if the ability is a spell, but not a copied spell and its not already - // on the stack zone, move there - if (ability.isSpell()) { - final Card source = ability.getSourceCard(); - if (!source.isCopiedSpell() && !source.isInZone(ZoneType.Stack)) { - ability.setSourceCard(game.getAction().moveToStack(source)); - } - } - - // Always add the ability here and always unfreeze the stack - this.add(ability); - this.unfreezeStack(); - } - - /** - *

- * unfreezeStack. - *

- */ - public final void unfreezeStack() { - this.frozen = false; - - // Add all Frozen Abilities onto the stack - while (!this.frozenStack.isEmpty()) { - final SpellAbility sa = this.frozenStack.pop().getSpellAbility(); - this.add(sa); - } - // Add all waiting triggers onto the stack - game.getTriggerHandler().runWaitingTriggers(); - - if (!simultaneousStackEntryList.isEmpty()) { - this.chooseOrderOfSimultaneousStackEntryAll(); - //game.getAction().checkStaticAbilities(); - } - } - - /** - *

- * clearFrozen. - *

- */ - public final void clearFrozen() { - // TODO: frozen triggered abilities and undoable costs have nasty - // consequences - this.frozen = false; - this.frozenStack.clear(); - } - - /** - *

- * removeFromFrozenStack. - *

- * @param sa - * a SpellAbility. - */ - public final void removeFromFrozenStack(SpellAbility sa) { - SpellAbilityStackInstance si = this.getInstanceFromSpellAbility(sa); - this.frozenStack.remove(si); - if (this.frozenStack.isEmpty()) { - clearFrozen(); - } - } - - /** - *

- * setResolving. - *

- * - * @param b - * a boolean. - */ - public final void setResolving(final boolean b) { - this.bResolving = b; - if (!this.bResolving) { - this.chooseOrderOfSimultaneousStackEntryAll(); - } - } - - /** - *

- * getResolving. - *

- * - * @return a boolean. - */ - public final boolean isResolving() { - return this.bResolving; - } - - /** - *

- * undo. - *

- * - * @return a boolean. - */ - public final boolean undo() { - if (undoStack.isEmpty()) { return false; } - - SpellAbility sa = undoStack.pop(); - sa.undo(); - sa.getActivatingPlayer().getManaPool().refundManaPaid(sa); - return true; - } - - /** - *

- * add. - *

- * - * @param sp - * a {@link forge.game.spellability.SpellAbility} object. - */ - public final void add(final SpellAbility sp) { - SpellAbilityStackInstance si = null; - final Card source = sp.getSourceCard(); - Player activator = sp.getActivatingPlayer(); - - // if activating player slips through the cracks, assign activating - // Player to the controller here - if (null == activator) { - sp.setActivatingPlayer(source.getController()); - activator = sp.getActivatingPlayer(); - System.out.println(source.getName() + " - activatingPlayer not set before adding to stack."); - } - - if (sp.isUndoable()) { //either push onto or clear undo stack based on where spell/ability is undoable - undoStack.push(sp); - } - else { - undoStack.clear(); - } - - if (sp.isManaAbility()) { // Mana Abilities go straight through - AbilityUtils.resolve(sp); - game.getGameLog().add(GameLogEntryType.MANA, source + " - " + sp.getDescription()); - sp.resetOnceResolved(); - return; - } - - if (sp.isSpell()) { - source.setController(activator, 0); - Spell spell = (Spell) sp; - if (spell.isCastFaceDown()) { - source.turnFaceDown(); - } - } - - if (this.frozen) { - si = new SpellAbilityStackInstance(sp); - this.frozenStack.push(si); - return; - } - - if ((sp instanceof AbilityTriggered) || (sp instanceof AbilityStatic)) { - // TODO: make working triggered ability - AbilityUtils.resolve(sp); - } else { - for (OptionalCost s : sp.getOptionalCosts()) { - source.addOptionalCostPaid(s); - } - if (sp.isCopied()) { - si = this.push(sp); - } else { - if (sp.isMultiKicker()) { - final Cost costMultikicker = new Cost(sp.getMultiKickerManaCost(), false); - - boolean hasPaid = false; - do { - int mkMagnitude = source.getKickerMagnitude(); - String prompt = String.format("Multikicker for %s\r\nTimes Kicked: %d\r\n", source, mkMagnitude ); - hasPaid = activator.getController().payManaOptional(source, costMultikicker, sp, prompt, ManaPaymentPurpose.Multikicker); - if( hasPaid ) - source.addMultiKickerMagnitude(1); - } while( hasPaid ); - } - if (source.isCreature() && Iterables.any(activator.getCardsIn(ZoneType.Battlefield), - CardPredicates.hasKeyword("As an additional cost to cast creature spells," + - " you may pay any amount of mana. If you do, that creature enters " + - "the battlefield with that many additional +1/+1 counters on it."))) { - final Cost costPseudoKicker = new Cost(ManaCost.ONE, false); - boolean hasPaid = false; - do { - int mkMagnitude = source.getPseudoKickerMagnitude(); - String prompt = String.format("Additional Cost for %s\r\nTimes Kicked: %d\r\n", source, mkMagnitude ); - hasPaid = activator.getController().payManaOptional(source, costPseudoKicker, sp, prompt, ManaPaymentPurpose.Multikicker); - if (hasPaid) { - source.addPseudoMultiKickerMagnitude(1); - } - } while (hasPaid); - if (source.getPseudoKickerMagnitude() > 0) { - String abStr = "AB$ ChangeZone | Cost$ 0 | Hidden$ True | Origin$ All | Destination$ Battlefield" - + "| Defined$ ReplacedCard | SubAbility$ ChorusDBETBCounters"; - String dbStr = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ " + source.getPseudoKickerMagnitude(); - - source.setSVar("ChorusETBCounters", abStr); - source.setSVar("ChorusDBETBCounters", dbStr); - - String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield " - + "| ReplaceWith$ ChorusETBCounters | Secondary$ True | Description$ CARDNAME" - + " enters the battlefield with " + source.getPseudoKickerMagnitude() + " +1/+1 counters."; - - ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, source, false); - re.setLayer(ReplacementLayer.Other); - - source.addReplacementEffect(re); - } - } - - // The ability is added to stack HERE - si = this.push(sp); - - if (sp.isSpell() && (source.hasStartOfKeyword("Replicate") - || ((source.isInstant() || source.isSorcery()) && Iterables.any(activator.getCardsIn(ZoneType.Battlefield), - CardPredicates.hasKeyword("Each instant and sorcery spell you cast has replicate. The replicate cost is equal to its mana cost."))))) { - int magnitude = 0; - // TODO: convert multikicker/replicate support in abCost so this - // doesn't happen here - - final Cost costReplicate = new Cost(sp.getPayCosts().getTotalMana(), false); - boolean hasPaid = false; - - do { - String prompt = String.format("Replicate for %s\r\nTimes Replicated: %d\r\n", source, magnitude); - hasPaid = activator.getController().payManaOptional(source, costReplicate, sp, prompt, ManaPaymentPurpose.Replicate); - if( hasPaid ) - magnitude++; - } while( hasPaid ); - // Replicate Trigger - String effect = String.format("AB$ CopySpellAbility | Cost$ 0 | Defined$ SourceFirstSpell | Amount$ %d", magnitude); - SpellAbility sa = AbilityFactory.getAbility(effect, source); - sa.setDescription("Replicate - " + source); - sa.setTrigger(true); - addSimultaneousStackEntry(sa); - } - - } - } - - // Copied spells aren't cast per se so triggers shouldn't run for them. - HashMap runParams = new HashMap(); - if (!(sp instanceof AbilityStatic) && !sp.isCopied()) { - // Run SpellAbilityCast triggers - runParams.put("Cost", sp.getPayCosts()); - runParams.put("Player", sp.getSourceCard().getController()); - runParams.put("Activator", sp.getActivatingPlayer()); - runParams.put("CastSA", si.getSpellAbility()); - game.getTriggerHandler().runTrigger(TriggerType.SpellAbilityCast, runParams, true); - - // Run SpellCast triggers - if (sp.isSpell()) { - game.getTriggerHandler().runTrigger(TriggerType.SpellCast, runParams, true); - } - - // Run AbilityCast triggers - if (sp.isAbility() && !sp.isTrigger()) { - game.getTriggerHandler().runTrigger(TriggerType.AbilityCast, runParams, true); - } - - // Run Cycled triggers - if (sp.isCycling()) { - runParams.clear(); - runParams.put("Card", sp.getSourceCard()); - game.getTriggerHandler().runTrigger(TriggerType.Cycled, runParams, false); - } - } - - // Run BecomesTarget triggers - // Create a new object, since the triggers aren't happening right away - final List chosenTargets = sp.getAllTargetChoices(); - if (!chosenTargets.isEmpty()) { - runParams = new HashMap(); - SpellAbility s = sp; - if (si != null) { - s = si.getSpellAbility(); - } - runParams.put("SourceSA", s); - HashSet distinctObjects = new HashSet(); - for (final TargetChoices tc : chosenTargets) { - if (tc != null && tc.getTargetCards() != null) { - for (final Object tgt : tc.getTargets()) { - // Track distinct objects so Becomes targets don't trigger for things like: - // Seeds of Strength - if (distinctObjects.contains(tgt)) { - continue; - } - - distinctObjects.add(tgt); - if (tgt instanceof Card && !((Card) tgt).hasBecomeTargetThisTurn()) { - runParams.put("FirstTime", null); - ((Card) tgt).setBecameTargetThisTurn(true); - } - runParams.put("Target", tgt); - game.getTriggerHandler().runTrigger(TriggerType.BecomesTarget, runParams, false); - } - } - } - } - // Not sure these clauses are necessary. Consider it a precaution - // for backwards compatibility for hardcoded cards. - else if (sp.getTargetCard() != null) { - runParams.put("Target", sp.getTargetCard()); - - game.getTriggerHandler().runTrigger(TriggerType.BecomesTarget, runParams, false); - } - - if (!this.simultaneousStackEntryList.isEmpty()) { - chooseOrderOfSimultaneousStackEntryAll(); - // Why should we pass priority after adding something to a stack? - // game.getPhaseHandler().passPriority(); - } - } - - /** - *

- * size. - *

- * - * @return a int. - */ - public final int size() { - return this.stack.size(); - } - - /** - *

- * isEmpty. - *

- * - * @return a boolean. - */ - public final boolean isEmpty() { - return this.stack.isEmpty(); - } - - // Push should only be used by add. - private SpellAbilityStackInstance push(final SpellAbility sp) { - if (null == sp.getActivatingPlayer()) { - sp.setActivatingPlayer(sp.getSourceCard().getController()); - System.out.println(sp.getSourceCard().getName() + " - activatingPlayer not set before adding to stack."); - } - - final SpellAbilityStackInstance si = new SpellAbilityStackInstance(sp); - - this.stack.addFirst(si); - game.fireEvent(new GameEventSpellAbilityCast(sp, false)); - - // 2012-07-21 the following comparison needs to move below the pushes but somehow screws up priority - // When it's down there. That makes absolutely no sense to me, so i'm putting it back for now - if (!(sp.isTrigger() || (sp instanceof AbilityStatic))) { - // when something is added we need to setPriority - game.getPhaseHandler().setPriority(sp.getActivatingPlayer()); - } - - if (sp.isSpell() && !sp.isCopied()) { - this.thisTurnCast.add(sp.getSourceCard()); - - /*final Command ripple = new RippleExecutor(sp.getActivatingPlayer(), sp.getSourceCard()); - ripple.run();*/ - } - return si; - } - - /** - *

- * resolveStack. - *

- */ - public final void resolveStack() { - // Resolving the Stack - - // freeze the stack while we're in the middle of resolving - this.freezeStack(); - this.setResolving(true); - - // The SpellAbility isn't removed from the Stack until it finishes resolving - // temporarily reverted removing SAs after resolution - final SpellAbility sa = this.peekAbility(); - //final SpellAbility sa = this.pop(); - - // ActivePlayer gains priority first after Resolve - game.getPhaseHandler().resetPriority(); - - final Card source = sa.getSourceCard(); - curResolvingCard = source; - - boolean thisHasFizzled = this.hasFizzled(sa, source, false); - - if (thisHasFizzled) { // Fizzle - if (sa.hasParam("Bestow")) { - // 702.102d: if its target is illegal, - // the effect making it an Aura spell ends. - // It continues resolving as a creature spell. - source.unanimateBestow(); - game.fireEvent(new GameEventCardStatsChanged(source)); - AbilityUtils.resolve(sa.getSourceCard().getFirstSpellAbility()); - } else { - // TODO: Spell fizzles, what's the best way to alert player? - Log.debug(source.getName() + " ability fizzles."); - } - } else if (sa.getApi() != null) { - AbilityUtils.handleRemembering(sa); - AbilityUtils.resolve(sa); - } else { - sa.resolve(); - // do creatures ETB from here? - } - - game.fireEvent(new GameEventSpellResolved(sa, thisHasFizzled)); - this.finishResolving(sa, thisHasFizzled); - - if (source.hasStartOfKeyword("Haunt") && !source.isCreature() && game.getZoneOf(source).is(ZoneType.Graveyard)) { - handleHauntForNonPermanents(sa); - } - } - - private void handleHauntForNonPermanents(final SpellAbility sa) { - final Card source = sa.getSourceCard(); - final List creats = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); - final Ability haunterDiesWork = new Ability(source, ManaCost.ZERO) { - @Override - public void resolve() { - game.getAction().exile(source); - this.getTargetCard().addHauntedBy(source); - } - }; - for (int i = 0; i < creats.size(); i++) { - haunterDiesWork.setActivatingPlayer(sa.getActivatingPlayer()); - if (!creats.get(i).canBeTargetedBy(haunterDiesWork)) { - creats.remove(i); - i--; - } - } - if (!creats.isEmpty()) { - haunterDiesWork.setDescription(""); - haunterDiesWork.setTargetRestrictions(new TargetRestrictions("", "Creature".split(" "), "1", "1")); - final Card targetCard = source.getController().getController().chooseSingleEntityForEffect(creats, new SpellAbility.EmptySa(ApiType.InternalHaunt, source), "Choose target creature to haunt."); - haunterDiesWork.setTargetCard(targetCard); - this.add(haunterDiesWork); - } - } - - - private final void finishResolving(final SpellAbility sa, final boolean fizzle) { - - // remove SA and card from the stack - this.removeCardFromStack(sa, fizzle); - // SpellAbility is removed from the stack here - // temporarily removed removing SA after resolution - final SpellAbilityStackInstance si = this.getInstanceFromSpellAbility(sa); - - if (si != null) { - this.remove(si); - } - - // After SA resolves we have to do a handful of things - this.setResolving(false); - this.unfreezeStack(); - sa.resetOnceResolved(); - - //game.getAction().checkStaticAbilities(); - game.getPhaseHandler().onStackResolved(); - - this.curResolvingCard = null; - - // TODO: this is a huge hack. Why is this necessary? - // hostCard in AF is not the same object that's on the battlefield - // verified by System.identityHashCode(card); - final Card tmp = sa.getSourceCard(); - tmp.setCanCounter(true); // reset mana pumped counter magic flag - if (tmp.getClones().size() > 0) { - for (final Card c : game.getCardsIn(ZoneType.Battlefield)) { - if (c.equals(tmp)) { - c.setClones(tmp.getClones()); - } - } - } - - sa.getSourceCard().setXManaCostPaid(0); - } - - private final void removeCardFromStack(final SpellAbility sa, final boolean fizzle) { - Card source = sa.getSourceCard(); - - // do nothing - if (sa.getSourceCard().isCopiedSpell() || sa.isAbility()) { - } - // Handle cards that need to be moved differently - else if (sa.isBuyBackAbility() && !fizzle) { - game.getAction().moveToHand(source); - } else if (sa.isFlashBackAbility()) { - game.getAction().exile(source); - sa.setFlashBackAbility(false); - } else if (source.hasKeyword("Rebound") - && source.getCastFrom() == ZoneType.Hand - && game.getZoneOf(source).is(ZoneType.Stack) - && source.getOwner().equals(source.getController())) //"If you cast this spell from your hand" - { - - //Move rebounding card to exile - source = game.getAction().exile(source); - - source.setSVar("ReboundAbilityTrigger", "DB$ Play | Defined$ Self " - + "| WithoutManaCost$ True | Optional$ True"); - - //Setup a Rebound-trigger - final Trigger reboundTrigger = forge.game.trigger.TriggerHandler.parseTrigger("Mode$ Phase " - + "| Phase$ Upkeep | ValidPlayer$ You | OptionalDecider$ You | Execute$ ReboundAbilityTrigger " - + "| TriggerDescription$ At the beginning of your next upkeep, you may cast " + source.toString() - + " without paying it's manacost.", source, true); - - game.getTriggerHandler().registerDelayedTrigger(reboundTrigger); - } - - // If Spell and still on the Stack then let it goto the graveyard or - // replace its own movement - else if (!source.isCopiedSpell() && (source.isInstant() || source.isSorcery() || fizzle) - && source.isInZone(ZoneType.Stack)) { - game.getAction().moveToGraveyard(source); - } - } - - - /** - *

- * hasFizzled. - *

- * - * @param sa - * a {@link forge.game.spellability.SpellAbility} object. - * @param source - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - private final boolean hasFizzled(final SpellAbility sa, final Card source, final boolean parentFizzled) { - // Can't fizzle unless there are some targets - boolean fizzle = false; - - TargetRestrictions tgt = sa.getTargetRestrictions(); - if (tgt != null) { - if (tgt.getMinTargets(source, sa) == 0 && sa.getTargets().getNumTargeted() == 0) { - // Nothing targeted, and nothing needs to be targeted. - } - else { - // Some targets were chosen, fizzling for this subability is now possible - fizzle = true; - // With multi-targets, as long as one target is still legal, - // we'll try to go through as much as possible - final TargetChoices choices = sa.getTargets(); - for (final GameObject o : sa.getTargets().getTargets()) { - boolean invalidTarget = false; - if (o instanceof Card) { - final Card card = (Card) o; - Card current = game.getCardState(card); - invalidTarget = current.getTimestamp() != card.getTimestamp(); - invalidTarget |= !(CardFactoryUtil.isTargetStillValid(sa, card)); - } else { - invalidTarget = !o.canBeTargetedBy(sa); - } - // Remove targets - if (invalidTarget) { - choices.remove(o); - } - - fizzle &= invalidTarget; - - if (sa.hasParam("CantFizzle")) { - // Gilded Drake cannot be countered by rules if the - // targeted card is not valid - fizzle = false; - } - } - } - } - else if (sa.getTargetCard() != null) { - fizzle = !CardFactoryUtil.isTargetStillValid(sa, sa.getTargetCard()); - } - else { - // Set fizzle to the same as the parent if there's no target info - fizzle = parentFizzled; - } - - if (sa.getSubAbility() == null) { - return fizzle; - } - - return hasFizzled(sa.getSubAbility(), source, fizzle) && fizzle; - } - - public final SpellAbility peekAbility() { - return this.stack.peekFirst().getSpellAbility(); - } - - public final void remove(final SpellAbilityStackInstance si) { - this.stack.remove(si); - this.frozenStack.remove(si); - game.fireEvent(new GameEventSpellRemovedFromStack(si.getSpellAbility())); - } - - public final SpellAbilityStackInstance getInstanceFromSpellAbility(final SpellAbility sa) { - // TODO: Confirm this works! - for (final SpellAbilityStackInstance si : this.stack) { - if (si.compareToSpellAbility(sa)) { - return si; - } - } - return null; - } - - public final boolean hasSimultaneousStackEntries() { - return !this.simultaneousStackEntryList.isEmpty(); - } - - public final void clearSimultaneousStack() { - this.simultaneousStackEntryList.clear(); - } - - public final void addSimultaneousStackEntry(final SpellAbility sa) { - this.simultaneousStackEntryList.add(sa); - } - - - public final void chooseOrderOfSimultaneousStackEntryAll() { - final Player playerTurn = game.getPhaseHandler().getPlayerTurn(); - - this.chooseOrderOfSimultaneousStackEntry(playerTurn); - - if (playerTurn != null) { - for(final Player other : playerTurn.getGame().getPlayers()) { - if ( other == playerTurn ) continue; - this.chooseOrderOfSimultaneousStackEntry(other); - } - } - } - - - private final void chooseOrderOfSimultaneousStackEntry(final Player activePlayer) { - if (this.simultaneousStackEntryList.isEmpty()) { - return; - } - - final List activePlayerSAs = new ArrayList(); - for (int i = 0; i < this.simultaneousStackEntryList.size(); i++) { - SpellAbility sa = this.simultaneousStackEntryList.get(i); - Player activator = sa.getActivatingPlayer(); - if (activator == null) { - if (sa.getSourceCard().getController().equals(activePlayer)) { - activePlayerSAs.add(sa); - this.simultaneousStackEntryList.remove(i); - i--; - } - } else { - if (activator.equals(activePlayer)) { - activePlayerSAs.add(sa); - this.simultaneousStackEntryList.remove(i); - i--; - } - } - } - if (activePlayerSAs.isEmpty()) { - return; - } - - activePlayer.getController().orderAndPlaySimultaneousSa(activePlayerSAs); - } - - /** - * TODO: Write javadoc for this method. - * - * @param triggerID - * the trigger id - * @return true, if successful - */ - public final boolean hasStateTrigger(final int triggerID) { - for (final SpellAbilityStackInstance sasi : this.stack) { - if (sasi.isStateTrigger(triggerID)) { - return true; - } - } - - for (final SpellAbilityStackInstance sasi : this.frozenStack) { - if (sasi.isStateTrigger(triggerID)) { - return true; - } - } - - for (final SpellAbility sa : this.simultaneousStackEntryList) { - if (sa.getSourceTrigger() == triggerID) { - return true; - } - } - - return false; - } - - /** - * Accessor for the field thisTurnCast. - * - * @return a CardList. - */ - public final List getCardsCastThisTurn() { - return this.thisTurnCast; - } - - /** - * clearCardsCastThisTurn. - */ - public final void onNextTurn() { - this.lastTurnCast = new ArrayList(this.thisTurnCast); - this.thisTurnCast.clear(); - } - - /** - * Accessor for the field lastTurnCast. - * - * @return a CardList. - */ - public final List getCardsCastLastTurn() { - return this.lastTurnCast; - } - - /** - * Checks if is resolving. - * - * @param c the c - * @return true, if is resolving - */ - public final boolean isResolving(Card c) { - if (!this.isResolving() || this.curResolvingCard == null) { - return false; - } - - return c.equals(this.curResolvingCard); - } - - /* (non-Javadoc) - * @see java.lang.Iterable#iterator() - */ - @Override - public Iterator iterator() { - // TODO Auto-generated method stub - return stack.iterator(); - } - - /** - * TODO: Write javadoc for this method. - */ - public void clear() { - stack.clear(); - game.fireEvent(new GameEventSpellRemovedFromStack(null)); - } - - @Override - public String toString() { - return String.format("%s==%s==%s", simultaneousStackEntryList, frozenStack.toString(), stack.toString()); - } - -} +/* + * 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 . + */ +package forge.game.zone; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Stack; +import java.util.concurrent.LinkedBlockingDeque; + +import com.esotericsoftware.minlog.Log; +import com.google.common.collect.Iterables; +import forge.FThreads; +import forge.card.mana.ManaCost; +import forge.game.Game; +import forge.game.GameLogEntryType; +import forge.game.GameObject; +import forge.game.ability.AbilityFactory; +import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; +import forge.game.card.Card; +import forge.game.card.CardFactoryUtil; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +import forge.game.card.CardPredicates.Presets; +import forge.game.cost.Cost; +import forge.game.event.GameEventCardStatsChanged; +import forge.game.event.GameEventSpellAbilityCast; +import forge.game.event.GameEventSpellRemovedFromStack; +import forge.game.event.GameEventSpellResolved; +import forge.game.player.Player; +import forge.game.player.PlayerController.ManaPaymentPurpose; +import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementHandler; +import forge.game.replacement.ReplacementLayer; +import forge.game.spellability.Ability; +import forge.game.spellability.AbilityStatic; +import forge.game.spellability.AbilityTriggered; +import forge.game.spellability.OptionalCost; +import forge.game.spellability.Spell; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityStackInstance; +import forge.game.spellability.TargetChoices; +import forge.game.spellability.TargetRestrictions; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerType; + + +/** + *

+ * MagicStack class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class MagicStack /* extends MyObservable */ implements Iterable { + private final List simultaneousStackEntryList = new ArrayList(); + + // They don't provide a LIFO queue, so had to use a deque + private final Deque stack = new LinkedBlockingDeque(); + private final Stack frozenStack = new Stack(); + private final Stack undoStack = new Stack(); + + private boolean frozen = false; + private boolean bResolving = false; + + private final List thisTurnCast = new ArrayList(); + private List lastTurnCast = new ArrayList(); + private Card curResolvingCard = null; + + private final Game game; + + /** + * TODO: Write javadoc for Constructor. + * @param gameState + */ + public MagicStack(Game gameState) { + game = gameState; + } + + /** + *

+ * isFrozen. + *

+ * + * @return a boolean. + */ + public final boolean isFrozen() { + return this.frozen; + } + + /** + *

+ * Setter for the field frozen. + *

+ * + * @param frozen0 + * a boolean. + */ + public final void setFrozen(final boolean frozen0) { + this.frozen = frozen0; + } + + /** + *

+ * reset. + *

+ */ + public final void reset() { + this.clear(); + this.simultaneousStackEntryList.clear(); + this.frozen = false; + this.lastTurnCast.clear(); + this.thisTurnCast.clear(); + this.curResolvingCard = null; + this.frozenStack.clear(); + this.undoStack.clear(); + } + + /** + *

+ * isSplitSecondOnStack. + *

+ * + * @return a boolean. + */ + public final boolean isSplitSecondOnStack() { + for(SpellAbilityStackInstance si : this.stack) { + if (si.isSpell() && si.getSourceCard().hasKeyword("Split second")) { + return true; + } + } + return false; + } + + /** + *

+ * freezeStack. + *

+ */ + public final void freezeStack() { + this.frozen = true; + } + + /** + *

+ * addAndUnfreeze. + *

+ * + * @param ability + * a {@link forge.game.spellability.SpellAbility} object. + */ + public final void addAndUnfreeze(final SpellAbility ability) { + ability.getRestrictions().abilityActivated(); + if ((ability.getRestrictions().getActivationNumberSacrifice() != -1) + && (ability.getRestrictions().getNumberTurnActivations() >= ability.getRestrictions() + .getActivationNumberSacrifice())) { + ability.getSourceCard().addHiddenExtrinsicKeyword("At the beginning of the end step, sacrifice CARDNAME."); + } + + // if the ability is a spell, but not a copied spell and its not already + // on the stack zone, move there + if (ability.isSpell()) { + final Card source = ability.getSourceCard(); + if (!source.isCopiedSpell() && !source.isInZone(ZoneType.Stack)) { + ability.setSourceCard(game.getAction().moveToStack(source)); + } + } + + // Always add the ability here and always unfreeze the stack + this.add(ability); + this.unfreezeStack(); + } + + /** + *

+ * unfreezeStack. + *

+ */ + public final void unfreezeStack() { + this.frozen = false; + + // Add all Frozen Abilities onto the stack + while (!this.frozenStack.isEmpty()) { + final SpellAbility sa = this.frozenStack.pop().getSpellAbility(); + this.add(sa); + } + // Add all waiting triggers onto the stack + game.getTriggerHandler().runWaitingTriggers(); + + if (!simultaneousStackEntryList.isEmpty()) { + this.chooseOrderOfSimultaneousStackEntryAll(); + //game.getAction().checkStaticAbilities(); + } + } + + /** + *

+ * clearFrozen. + *

+ */ + public final void clearFrozen() { + // TODO: frozen triggered abilities and undoable costs have nasty + // consequences + this.frozen = false; + this.frozenStack.clear(); + } + + /** + *

+ * removeFromFrozenStack. + *

+ * @param sa + * a SpellAbility. + */ + public final void removeFromFrozenStack(SpellAbility sa) { + SpellAbilityStackInstance si = this.getInstanceFromSpellAbility(sa); + this.frozenStack.remove(si); + if (this.frozenStack.isEmpty()) { + clearFrozen(); + } + } + + /** + *

+ * setResolving. + *

+ * + * @param b + * a boolean. + */ + public final void setResolving(final boolean b) { + this.bResolving = b; + if (!this.bResolving) { + this.chooseOrderOfSimultaneousStackEntryAll(); + } + } + + /** + *

+ * getResolving. + *

+ * + * @return a boolean. + */ + public final boolean isResolving() { + return this.bResolving; + } + + /** + *

+ * undo. + *

+ * + * @return a boolean. + */ + public final boolean undo() { + if (undoStack.isEmpty()) { return false; } + + SpellAbility sa = undoStack.pop(); + sa.undo(); + sa.getActivatingPlayer().getManaPool().refundManaPaid(sa); + return true; + } + + /** + *

+ * add. + *

+ * + * @param sp + * a {@link forge.game.spellability.SpellAbility} object. + */ + public final void add(final SpellAbility sp) { + if (!sp.isManaAbility()) { //allow adding mana abilities on EDT + FThreads.assertExecutedByEdt(false); + } + SpellAbilityStackInstance si = null; + final Card source = sp.getSourceCard(); + Player activator = sp.getActivatingPlayer(); + + // if activating player slips through the cracks, assign activating + // Player to the controller here + if (null == activator) { + sp.setActivatingPlayer(source.getController()); + activator = sp.getActivatingPlayer(); + System.out.println(source.getName() + " - activatingPlayer not set before adding to stack."); + } + + if (sp.isUndoable()) { //either push onto or clear undo stack based on where spell/ability is undoable + undoStack.push(sp); + } + else { + undoStack.clear(); + } + + if (sp.isManaAbility()) { // Mana Abilities go straight through + AbilityUtils.resolve(sp); + game.getGameLog().add(GameLogEntryType.MANA, source + " - " + sp.getDescription()); + sp.resetOnceResolved(); + return; + } + + if (sp.isSpell()) { + source.setController(activator, 0); + Spell spell = (Spell) sp; + if (spell.isCastFaceDown()) { + source.turnFaceDown(); + } + } + + if (this.frozen) { + si = new SpellAbilityStackInstance(sp); + this.frozenStack.push(si); + return; + } + + if ((sp instanceof AbilityTriggered) || (sp instanceof AbilityStatic)) { + // TODO: make working triggered ability + AbilityUtils.resolve(sp); + } else { + for (OptionalCost s : sp.getOptionalCosts()) { + source.addOptionalCostPaid(s); + } + if (sp.isCopied()) { + si = this.push(sp); + } else { + if (sp.isMultiKicker()) { + final Cost costMultikicker = new Cost(sp.getMultiKickerManaCost(), false); + + boolean hasPaid = false; + do { + int mkMagnitude = source.getKickerMagnitude(); + String prompt = String.format("Multikicker for %s\r\nTimes Kicked: %d\r\n", source, mkMagnitude ); + hasPaid = activator.getController().payManaOptional(source, costMultikicker, sp, prompt, ManaPaymentPurpose.Multikicker); + if( hasPaid ) + source.addMultiKickerMagnitude(1); + } while( hasPaid ); + } + if (source.isCreature() && Iterables.any(activator.getCardsIn(ZoneType.Battlefield), + CardPredicates.hasKeyword("As an additional cost to cast creature spells," + + " you may pay any amount of mana. If you do, that creature enters " + + "the battlefield with that many additional +1/+1 counters on it."))) { + final Cost costPseudoKicker = new Cost(ManaCost.ONE, false); + boolean hasPaid = false; + do { + int mkMagnitude = source.getPseudoKickerMagnitude(); + String prompt = String.format("Additional Cost for %s\r\nTimes Kicked: %d\r\n", source, mkMagnitude ); + hasPaid = activator.getController().payManaOptional(source, costPseudoKicker, sp, prompt, ManaPaymentPurpose.Multikicker); + if (hasPaid) { + source.addPseudoMultiKickerMagnitude(1); + } + } while (hasPaid); + if (source.getPseudoKickerMagnitude() > 0) { + String abStr = "AB$ ChangeZone | Cost$ 0 | Hidden$ True | Origin$ All | Destination$ Battlefield" + + "| Defined$ ReplacedCard | SubAbility$ ChorusDBETBCounters"; + String dbStr = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ " + source.getPseudoKickerMagnitude(); + + source.setSVar("ChorusETBCounters", abStr); + source.setSVar("ChorusDBETBCounters", dbStr); + + String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield " + + "| ReplaceWith$ ChorusETBCounters | Secondary$ True | Description$ CARDNAME" + + " enters the battlefield with " + source.getPseudoKickerMagnitude() + " +1/+1 counters."; + + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, source, false); + re.setLayer(ReplacementLayer.Other); + + source.addReplacementEffect(re); + } + } + + // The ability is added to stack HERE + si = this.push(sp); + + if (sp.isSpell() && (source.hasStartOfKeyword("Replicate") + || ((source.isInstant() || source.isSorcery()) && Iterables.any(activator.getCardsIn(ZoneType.Battlefield), + CardPredicates.hasKeyword("Each instant and sorcery spell you cast has replicate. The replicate cost is equal to its mana cost."))))) { + int magnitude = 0; + // TODO: convert multikicker/replicate support in abCost so this + // doesn't happen here + + final Cost costReplicate = new Cost(sp.getPayCosts().getTotalMana(), false); + boolean hasPaid = false; + + do { + String prompt = String.format("Replicate for %s\r\nTimes Replicated: %d\r\n", source, magnitude); + hasPaid = activator.getController().payManaOptional(source, costReplicate, sp, prompt, ManaPaymentPurpose.Replicate); + if( hasPaid ) + magnitude++; + } while( hasPaid ); + // Replicate Trigger + String effect = String.format("AB$ CopySpellAbility | Cost$ 0 | Defined$ SourceFirstSpell | Amount$ %d", magnitude); + SpellAbility sa = AbilityFactory.getAbility(effect, source); + sa.setDescription("Replicate - " + source); + sa.setTrigger(true); + addSimultaneousStackEntry(sa); + } + + } + } + + // Copied spells aren't cast per se so triggers shouldn't run for them. + HashMap runParams = new HashMap(); + if (!(sp instanceof AbilityStatic) && !sp.isCopied()) { + // Run SpellAbilityCast triggers + runParams.put("Cost", sp.getPayCosts()); + runParams.put("Player", sp.getSourceCard().getController()); + runParams.put("Activator", sp.getActivatingPlayer()); + runParams.put("CastSA", si.getSpellAbility()); + game.getTriggerHandler().runTrigger(TriggerType.SpellAbilityCast, runParams, true); + + // Run SpellCast triggers + if (sp.isSpell()) { + game.getTriggerHandler().runTrigger(TriggerType.SpellCast, runParams, true); + } + + // Run AbilityCast triggers + if (sp.isAbility() && !sp.isTrigger()) { + game.getTriggerHandler().runTrigger(TriggerType.AbilityCast, runParams, true); + } + + // Run Cycled triggers + if (sp.isCycling()) { + runParams.clear(); + runParams.put("Card", sp.getSourceCard()); + game.getTriggerHandler().runTrigger(TriggerType.Cycled, runParams, false); + } + } + + // Run BecomesTarget triggers + // Create a new object, since the triggers aren't happening right away + final List chosenTargets = sp.getAllTargetChoices(); + if (!chosenTargets.isEmpty()) { + runParams = new HashMap(); + SpellAbility s = sp; + if (si != null) { + s = si.getSpellAbility(); + } + runParams.put("SourceSA", s); + HashSet distinctObjects = new HashSet(); + for (final TargetChoices tc : chosenTargets) { + if (tc != null && tc.getTargetCards() != null) { + for (final Object tgt : tc.getTargets()) { + // Track distinct objects so Becomes targets don't trigger for things like: + // Seeds of Strength + if (distinctObjects.contains(tgt)) { + continue; + } + + distinctObjects.add(tgt); + if (tgt instanceof Card && !((Card) tgt).hasBecomeTargetThisTurn()) { + runParams.put("FirstTime", null); + ((Card) tgt).setBecameTargetThisTurn(true); + } + runParams.put("Target", tgt); + game.getTriggerHandler().runTrigger(TriggerType.BecomesTarget, runParams, false); + } + } + } + } + // Not sure these clauses are necessary. Consider it a precaution + // for backwards compatibility for hardcoded cards. + else if (sp.getTargetCard() != null) { + runParams.put("Target", sp.getTargetCard()); + + game.getTriggerHandler().runTrigger(TriggerType.BecomesTarget, runParams, false); + } + + if (!this.simultaneousStackEntryList.isEmpty()) { + chooseOrderOfSimultaneousStackEntryAll(); + // Why should we pass priority after adding something to a stack? + // game.getPhaseHandler().passPriority(); + } + } + + /** + *

+ * size. + *

+ * + * @return a int. + */ + public final int size() { + return this.stack.size(); + } + + /** + *

+ * isEmpty. + *

+ * + * @return a boolean. + */ + public final boolean isEmpty() { + return this.stack.isEmpty(); + } + + // Push should only be used by add. + private SpellAbilityStackInstance push(final SpellAbility sp) { + if (null == sp.getActivatingPlayer()) { + sp.setActivatingPlayer(sp.getSourceCard().getController()); + System.out.println(sp.getSourceCard().getName() + " - activatingPlayer not set before adding to stack."); + } + + final SpellAbilityStackInstance si = new SpellAbilityStackInstance(sp); + + this.stack.addFirst(si); + game.fireEvent(new GameEventSpellAbilityCast(sp, false)); + + // 2012-07-21 the following comparison needs to move below the pushes but somehow screws up priority + // When it's down there. That makes absolutely no sense to me, so i'm putting it back for now + if (!(sp.isTrigger() || (sp instanceof AbilityStatic))) { + // when something is added we need to setPriority + game.getPhaseHandler().setPriority(sp.getActivatingPlayer()); + } + + if (sp.isSpell() && !sp.isCopied()) { + this.thisTurnCast.add(sp.getSourceCard()); + + /*final Command ripple = new RippleExecutor(sp.getActivatingPlayer(), sp.getSourceCard()); + ripple.run();*/ + } + return si; + } + + /** + *

+ * resolveStack. + *

+ */ + public final void resolveStack() { + // Resolving the Stack + + // freeze the stack while we're in the middle of resolving + this.freezeStack(); + this.setResolving(true); + + // The SpellAbility isn't removed from the Stack until it finishes resolving + // temporarily reverted removing SAs after resolution + final SpellAbility sa = this.peekAbility(); + //final SpellAbility sa = this.pop(); + + // ActivePlayer gains priority first after Resolve + game.getPhaseHandler().resetPriority(); + + final Card source = sa.getSourceCard(); + curResolvingCard = source; + + boolean thisHasFizzled = this.hasFizzled(sa, source, false); + + if (thisHasFizzled) { // Fizzle + if (sa.hasParam("Bestow")) { + // 702.102d: if its target is illegal, + // the effect making it an Aura spell ends. + // It continues resolving as a creature spell. + source.unanimateBestow(); + game.fireEvent(new GameEventCardStatsChanged(source)); + AbilityUtils.resolve(sa.getSourceCard().getFirstSpellAbility()); + } else { + // TODO: Spell fizzles, what's the best way to alert player? + Log.debug(source.getName() + " ability fizzles."); + } + } else if (sa.getApi() != null) { + AbilityUtils.handleRemembering(sa); + AbilityUtils.resolve(sa); + } else { + sa.resolve(); + // do creatures ETB from here? + } + + game.fireEvent(new GameEventSpellResolved(sa, thisHasFizzled)); + this.finishResolving(sa, thisHasFizzled); + + if (source.hasStartOfKeyword("Haunt") && !source.isCreature() && game.getZoneOf(source).is(ZoneType.Graveyard)) { + handleHauntForNonPermanents(sa); + } + } + + private void handleHauntForNonPermanents(final SpellAbility sa) { + final Card source = sa.getSourceCard(); + final List creats = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); + final Ability haunterDiesWork = new Ability(source, ManaCost.ZERO) { + @Override + public void resolve() { + game.getAction().exile(source); + this.getTargetCard().addHauntedBy(source); + } + }; + for (int i = 0; i < creats.size(); i++) { + haunterDiesWork.setActivatingPlayer(sa.getActivatingPlayer()); + if (!creats.get(i).canBeTargetedBy(haunterDiesWork)) { + creats.remove(i); + i--; + } + } + if (!creats.isEmpty()) { + haunterDiesWork.setDescription(""); + haunterDiesWork.setTargetRestrictions(new TargetRestrictions("", "Creature".split(" "), "1", "1")); + final Card targetCard = source.getController().getController().chooseSingleEntityForEffect(creats, new SpellAbility.EmptySa(ApiType.InternalHaunt, source), "Choose target creature to haunt."); + haunterDiesWork.setTargetCard(targetCard); + this.add(haunterDiesWork); + } + } + + + private final void finishResolving(final SpellAbility sa, final boolean fizzle) { + + // remove SA and card from the stack + this.removeCardFromStack(sa, fizzle); + // SpellAbility is removed from the stack here + // temporarily removed removing SA after resolution + final SpellAbilityStackInstance si = this.getInstanceFromSpellAbility(sa); + + if (si != null) { + this.remove(si); + } + + // After SA resolves we have to do a handful of things + this.setResolving(false); + this.unfreezeStack(); + sa.resetOnceResolved(); + + //game.getAction().checkStaticAbilities(); + game.getPhaseHandler().onStackResolved(); + + this.curResolvingCard = null; + + // TODO: this is a huge hack. Why is this necessary? + // hostCard in AF is not the same object that's on the battlefield + // verified by System.identityHashCode(card); + final Card tmp = sa.getSourceCard(); + tmp.setCanCounter(true); // reset mana pumped counter magic flag + if (tmp.getClones().size() > 0) { + for (final Card c : game.getCardsIn(ZoneType.Battlefield)) { + if (c.equals(tmp)) { + c.setClones(tmp.getClones()); + } + } + } + + sa.getSourceCard().setXManaCostPaid(0); + } + + private final void removeCardFromStack(final SpellAbility sa, final boolean fizzle) { + Card source = sa.getSourceCard(); + + // do nothing + if (sa.getSourceCard().isCopiedSpell() || sa.isAbility()) { + } + // Handle cards that need to be moved differently + else if (sa.isBuyBackAbility() && !fizzle) { + game.getAction().moveToHand(source); + } else if (sa.isFlashBackAbility()) { + game.getAction().exile(source); + sa.setFlashBackAbility(false); + } else if (source.hasKeyword("Rebound") + && source.getCastFrom() == ZoneType.Hand + && game.getZoneOf(source).is(ZoneType.Stack) + && source.getOwner().equals(source.getController())) //"If you cast this spell from your hand" + { + + //Move rebounding card to exile + source = game.getAction().exile(source); + + source.setSVar("ReboundAbilityTrigger", "DB$ Play | Defined$ Self " + + "| WithoutManaCost$ True | Optional$ True"); + + //Setup a Rebound-trigger + final Trigger reboundTrigger = forge.game.trigger.TriggerHandler.parseTrigger("Mode$ Phase " + + "| Phase$ Upkeep | ValidPlayer$ You | OptionalDecider$ You | Execute$ ReboundAbilityTrigger " + + "| TriggerDescription$ At the beginning of your next upkeep, you may cast " + source.toString() + + " without paying it's manacost.", source, true); + + game.getTriggerHandler().registerDelayedTrigger(reboundTrigger); + } + + // If Spell and still on the Stack then let it goto the graveyard or + // replace its own movement + else if (!source.isCopiedSpell() && (source.isInstant() || source.isSorcery() || fizzle) + && source.isInZone(ZoneType.Stack)) { + game.getAction().moveToGraveyard(source); + } + } + + + /** + *

+ * hasFizzled. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @param source + * a {@link forge.game.card.Card} object. + * @return a boolean. + */ + private final boolean hasFizzled(final SpellAbility sa, final Card source, final boolean parentFizzled) { + // Can't fizzle unless there are some targets + boolean fizzle = false; + + TargetRestrictions tgt = sa.getTargetRestrictions(); + if (tgt != null) { + if (tgt.getMinTargets(source, sa) == 0 && sa.getTargets().getNumTargeted() == 0) { + // Nothing targeted, and nothing needs to be targeted. + } + else { + // Some targets were chosen, fizzling for this subability is now possible + fizzle = true; + // With multi-targets, as long as one target is still legal, + // we'll try to go through as much as possible + final TargetChoices choices = sa.getTargets(); + for (final GameObject o : sa.getTargets().getTargets()) { + boolean invalidTarget = false; + if (o instanceof Card) { + final Card card = (Card) o; + Card current = game.getCardState(card); + invalidTarget = current.getTimestamp() != card.getTimestamp(); + invalidTarget |= !(CardFactoryUtil.isTargetStillValid(sa, card)); + } else { + invalidTarget = !o.canBeTargetedBy(sa); + } + // Remove targets + if (invalidTarget) { + choices.remove(o); + } + + fizzle &= invalidTarget; + + if (sa.hasParam("CantFizzle")) { + // Gilded Drake cannot be countered by rules if the + // targeted card is not valid + fizzle = false; + } + } + } + } + else if (sa.getTargetCard() != null) { + fizzle = !CardFactoryUtil.isTargetStillValid(sa, sa.getTargetCard()); + } + else { + // Set fizzle to the same as the parent if there's no target info + fizzle = parentFizzled; + } + + if (sa.getSubAbility() == null) { + return fizzle; + } + + return hasFizzled(sa.getSubAbility(), source, fizzle) && fizzle; + } + + public final SpellAbility peekAbility() { + return this.stack.peekFirst().getSpellAbility(); + } + + public final void remove(final SpellAbilityStackInstance si) { + this.stack.remove(si); + this.frozenStack.remove(si); + game.fireEvent(new GameEventSpellRemovedFromStack(si.getSpellAbility())); + } + + public final SpellAbilityStackInstance getInstanceFromSpellAbility(final SpellAbility sa) { + // TODO: Confirm this works! + for (final SpellAbilityStackInstance si : this.stack) { + if (si.compareToSpellAbility(sa)) { + return si; + } + } + return null; + } + + public final boolean hasSimultaneousStackEntries() { + return !this.simultaneousStackEntryList.isEmpty(); + } + + public final void clearSimultaneousStack() { + this.simultaneousStackEntryList.clear(); + } + + public final void addSimultaneousStackEntry(final SpellAbility sa) { + this.simultaneousStackEntryList.add(sa); + } + + + public final void chooseOrderOfSimultaneousStackEntryAll() { + final Player playerTurn = game.getPhaseHandler().getPlayerTurn(); + + this.chooseOrderOfSimultaneousStackEntry(playerTurn); + + if (playerTurn != null) { + for(final Player other : playerTurn.getGame().getPlayers()) { + if ( other == playerTurn ) continue; + this.chooseOrderOfSimultaneousStackEntry(other); + } + } + } + + + private final void chooseOrderOfSimultaneousStackEntry(final Player activePlayer) { + if (this.simultaneousStackEntryList.isEmpty()) { + return; + } + + final List activePlayerSAs = new ArrayList(); + for (int i = 0; i < this.simultaneousStackEntryList.size(); i++) { + SpellAbility sa = this.simultaneousStackEntryList.get(i); + Player activator = sa.getActivatingPlayer(); + if (activator == null) { + if (sa.getSourceCard().getController().equals(activePlayer)) { + activePlayerSAs.add(sa); + this.simultaneousStackEntryList.remove(i); + i--; + } + } else { + if (activator.equals(activePlayer)) { + activePlayerSAs.add(sa); + this.simultaneousStackEntryList.remove(i); + i--; + } + } + } + if (activePlayerSAs.isEmpty()) { + return; + } + + activePlayer.getController().orderAndPlaySimultaneousSa(activePlayerSAs); + } + + /** + * TODO: Write javadoc for this method. + * + * @param triggerID + * the trigger id + * @return true, if successful + */ + public final boolean hasStateTrigger(final int triggerID) { + for (final SpellAbilityStackInstance sasi : this.stack) { + if (sasi.isStateTrigger(triggerID)) { + return true; + } + } + + for (final SpellAbilityStackInstance sasi : this.frozenStack) { + if (sasi.isStateTrigger(triggerID)) { + return true; + } + } + + for (final SpellAbility sa : this.simultaneousStackEntryList) { + if (sa.getSourceTrigger() == triggerID) { + return true; + } + } + + return false; + } + + /** + * Accessor for the field thisTurnCast. + * + * @return a CardList. + */ + public final List getCardsCastThisTurn() { + return this.thisTurnCast; + } + + /** + * clearCardsCastThisTurn. + */ + public final void onNextTurn() { + this.lastTurnCast = new ArrayList(this.thisTurnCast); + this.thisTurnCast.clear(); + } + + /** + * Accessor for the field lastTurnCast. + * + * @return a CardList. + */ + public final List getCardsCastLastTurn() { + return this.lastTurnCast; + } + + /** + * Checks if is resolving. + * + * @param c the c + * @return true, if is resolving + */ + public final boolean isResolving(Card c) { + if (!this.isResolving() || this.curResolvingCard == null) { + return false; + } + + return c.equals(this.curResolvingCard); + } + + /* (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + // TODO Auto-generated method stub + return stack.iterator(); + } + + /** + * TODO: Write javadoc for this method. + */ + public void clear() { + stack.clear(); + game.fireEvent(new GameEventSpellRemovedFromStack(null)); + } + + @Override + public String toString() { + return String.format("%s==%s==%s", simultaneousStackEntryList, frozenStack.toString(), stack.toString()); + } + +} diff --git a/forge-game/src/main/java/forge/game/zone/PlayerZone.java b/forge-gui/src/main/java/forge/game/zone/PlayerZone.java similarity index 95% rename from forge-game/src/main/java/forge/game/zone/PlayerZone.java rename to forge-gui/src/main/java/forge/game/zone/PlayerZone.java index 238e4412f3b..66637130c6d 100644 --- a/forge-game/src/main/java/forge/game/zone/PlayerZone.java +++ b/forge-gui/src/main/java/forge/game/zone/PlayerZone.java @@ -1,137 +1,137 @@ -/* - * 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 . - */ -package forge.game.zone; - -import java.util.List; - -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - -import forge.game.card.Card; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; -import forge.util.Lang; - -/** - *

- * DefaultPlayerZone class. - *

- * - * @author Forge - * @version $Id: PlayerZone.java 23787 2013-11-24 07:09:23Z Max mtg $ - */ -public class PlayerZone extends Zone { - - // the this is not the owner of the card - private final class AlienCardsActivationFilter implements Predicate { - @Override - public boolean apply(final Card c) { - - if (c.hasStartOfKeyword("May be played by your opponent") - || c.hasKeyword("Your opponent may look at this card.")) { - return true; - } - return false; - } - } - - private final class OwnCardsActivationFilter implements Predicate { - @Override - public boolean apply(final Card c) { - if (c.hasKeyword("You may look at this card.")) { - return true; - } - - if (c.isLand() && (c.hasKeyword("May be played") || c.hasKeyword("May be played without paying its mana cost"))) { - return true; - } - - for (final SpellAbility sa : c.getSpellAbilities()) { - final ZoneType restrictZone = sa.getRestrictions().getZone(); - if (PlayerZone.this.is(restrictZone)) { - return true; - } - - if (sa.isSpell() - && (c.hasKeyword("May be played") || c.hasKeyword("May be played without paying its mana cost") - || (c.hasStartOfKeyword("Flashback") && PlayerZone.this.is(ZoneType.Graveyard))) - && restrictZone.equals(ZoneType.Hand)) { - return true; - } - } - return false; - } - } - - /** Constant serialVersionUID=-5687652485777639176L. */ - private static final long serialVersionUID = -5687652485777639176L; - - - private final Player player; - - - - public PlayerZone(final ZoneType zone, final Player inPlayer) { - super(zone, inPlayer.getGame()); - this.player = inPlayer; - } - - /** - *

- * Getter for the field player. - *

- * - * @return a {@link forge.game.player.Player} object. - */ - public final Player getPlayer() { - return this.player; - } - - /** - *

- * toString. - *

- * - * @return a {@link java.lang.String} object. - */ - @Override - public final String toString() { - return String.format("%s %s", Lang.getPossesive(this.player.toString()), this.zoneType); - } - - public List getCardsPlayerCanActivate(Player who) { - - Iterable cl = roCardList; // copy to new AL won't help here - - // Only check the top card of the library - if (is(ZoneType.Library)) { - cl = Iterables.limit(cl, 1); - } - - boolean checkingForOwner = who == this.player; - - if (checkingForOwner && (this.is(ZoneType.Battlefield) || this.is(ZoneType.Hand))) - return roCardList; - - final Predicate filterPredicate = checkingForOwner ? new OwnCardsActivationFilter() : new AlienCardsActivationFilter(); - return Lists.newArrayList(cl = Iterables.filter(cl, filterPredicate)); - } - - -} +/* + * 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 . + */ +package forge.game.zone; + +import java.util.List; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.util.Lang; + +/** + *

+ * DefaultPlayerZone class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class PlayerZone extends Zone { + + // the this is not the owner of the card + private final class AlienCardsActivationFilter implements Predicate { + @Override + public boolean apply(final Card c) { + + if (c.hasStartOfKeyword("May be played by your opponent") + || c.hasKeyword("Your opponent may look at this card.")) { + return true; + } + return false; + } + } + + private final class OwnCardsActivationFilter implements Predicate { + @Override + public boolean apply(final Card c) { + if (c.hasKeyword("You may look at this card.")) { + return true; + } + + if (c.isLand() && (c.hasKeyword("May be played") || c.hasKeyword("May be played without paying its mana cost"))) { + return true; + } + + for (final SpellAbility sa : c.getSpellAbilities()) { + final ZoneType restrictZone = sa.getRestrictions().getZone(); + if (PlayerZone.this.is(restrictZone)) { + return true; + } + + if (sa.isSpell() + && (c.hasKeyword("May be played") || c.hasKeyword("May be played without paying its mana cost") + || (c.hasStartOfKeyword("Flashback") && PlayerZone.this.is(ZoneType.Graveyard))) + && restrictZone.equals(ZoneType.Hand)) { + return true; + } + } + return false; + } + } + + /** Constant serialVersionUID=-5687652485777639176L. */ + private static final long serialVersionUID = -5687652485777639176L; + + + private final Player player; + + + + public PlayerZone(final ZoneType zone, final Player inPlayer) { + super(zone, inPlayer.getGame()); + this.player = inPlayer; + } + + /** + *

+ * Getter for the field player. + *

+ * + * @return a {@link forge.game.player.Player} object. + */ + public final Player getPlayer() { + return this.player; + } + + /** + *

+ * toString. + *

+ * + * @return a {@link java.lang.String} object. + */ + @Override + public final String toString() { + return String.format("%s %s", Lang.getPossesive(this.player.toString()), this.zoneType); + } + + public List getCardsPlayerCanActivate(Player who) { + + Iterable cl = roCardList; // copy to new AL won't help here + + // Only check the top card of the library + if (is(ZoneType.Library)) { + cl = Iterables.limit(cl, 1); + } + + boolean checkingForOwner = who == this.player; + + if (checkingForOwner && (this.is(ZoneType.Battlefield) || this.is(ZoneType.Hand))) + return roCardList; + + final Predicate filterPredicate = checkingForOwner ? new OwnCardsActivationFilter() : new AlienCardsActivationFilter(); + return Lists.newArrayList(cl = Iterables.filter(cl, filterPredicate)); + } + + +} diff --git a/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java b/forge-gui/src/main/java/forge/game/zone/PlayerZoneBattlefield.java similarity index 94% rename from forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java rename to forge-gui/src/main/java/forge/game/zone/PlayerZoneBattlefield.java index 967f0c9f1f7..6f236fa5001 100644 --- a/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java +++ b/forge-gui/src/main/java/forge/game/zone/PlayerZoneBattlefield.java @@ -1,122 +1,122 @@ -/* - * 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 . - */ -package forge.game.zone; - -import java.util.List; - -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - -import forge.game.card.Card; -import forge.game.player.Player; -import forge.game.staticability.StaticAbility; -import forge.game.trigger.ZCTrigger; - -/** - *

- * PlayerZoneComesIntoPlay class. - *

- * - * @author Forge - * @version $Id: PlayerZoneBattlefield.java 24193 2014-01-09 13:46:14Z swordshine $ - */ -public class PlayerZoneBattlefield extends PlayerZone { - /** Constant serialVersionUID=5750837078903423978L. */ - private static final long serialVersionUID = 5750837078903423978L; - - private boolean trigger = true; - private boolean leavesTrigger = true; - - public PlayerZoneBattlefield(final ZoneType zone, final Player player) { - super(zone, player); - } - - /** {@inheritDoc} */ - @Override - public final void add(final Card c, final Integer position) { - if (c == null) { - throw new RuntimeException("PlayerZoneComesInto Play : add() object is null"); - } - - super.add(c, position); - - if (this.trigger) { - if (c.hasKeyword("Hideaway")) { - // it enters the battlefield this way, and should not fire - // triggers - c.setTapped(true); - } else { - // ETBTapped static abilities - for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { - for (final StaticAbility stAb : ca.getStaticAbilities()) { - if (stAb.applyAbility("ETBTapped", c)) { - // it enters the battlefield this way, and should - // not fire triggers - c.setTapped(true); - } - } - } - } - } - - if (this.trigger) { - c.setSickness(true); // summoning sickness - c.executeTrigger(ZCTrigger.ENTERFIELD); - - } - - } // end add() - - /** {@inheritDoc} */ - @Override - public final void remove(final Card c) { - super.remove(c); - - if (this.leavesTrigger) { - c.executeTrigger(ZCTrigger.LEAVEFIELD); - } - - } - - - public final void setTriggers(final boolean b) { - this.trigger = b; - this.leavesTrigger = b; - } - - private static Predicate isNotPhased = new Predicate() { - @Override - public boolean apply(Card crd) { - return !crd.isPhasedOut(); - } - }; - - - @Override - public final List getCards(final boolean filter) { - // Battlefield filters out Phased Out cards by default. Needs to call - // getCards(false) to get Phased Out cards - - if (!filter) { - return super.getCards(false); - } - return Lists.newArrayList(Iterables.filter(roCardList, isNotPhased)); - - } -} +/* + * 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 . + */ +package forge.game.zone; + +import java.util.List; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.staticability.StaticAbility; +import forge.game.trigger.ZCTrigger; + +/** + *

+ * PlayerZoneComesIntoPlay class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class PlayerZoneBattlefield extends PlayerZone { + /** Constant serialVersionUID=5750837078903423978L. */ + private static final long serialVersionUID = 5750837078903423978L; + + private boolean trigger = true; + private boolean leavesTrigger = true; + + public PlayerZoneBattlefield(final ZoneType zone, final Player player) { + super(zone, player); + } + + /** {@inheritDoc} */ + @Override + public final void add(final Card c, final Integer position) { + if (c == null) { + throw new RuntimeException("PlayerZoneComesInto Play : add() object is null"); + } + + super.add(c, position); + + if (this.trigger) { + if (c.hasKeyword("Hideaway")) { + // it enters the battlefield this way, and should not fire + // triggers + c.setTapped(true); + } else { + // ETBTapped static abilities + for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (stAb.applyAbility("ETBTapped", c)) { + // it enters the battlefield this way, and should + // not fire triggers + c.setTapped(true); + } + } + } + } + } + + if (this.trigger) { + c.setSickness(true); // summoning sickness + c.executeTrigger(ZCTrigger.ENTERFIELD); + + } + + } // end add() + + /** {@inheritDoc} */ + @Override + public final void remove(final Card c) { + super.remove(c); + + if (this.leavesTrigger) { + c.executeTrigger(ZCTrigger.LEAVEFIELD); + } + + } + + + public final void setTriggers(final boolean b) { + this.trigger = b; + this.leavesTrigger = b; + } + + private static Predicate isNotPhased = new Predicate() { + @Override + public boolean apply(Card crd) { + return !crd.isPhasedOut(); + } + }; + + + @Override + public final List getCards(final boolean filter) { + // Battlefield filters out Phased Out cards by default. Needs to call + // getCards(false) to get Phased Out cards + + if (!filter) { + return super.getCards(false); + } + return Lists.newArrayList(Iterables.filter(roCardList, isNotPhased)); + + } +} diff --git a/forge-game/src/main/java/forge/game/zone/Zone.java b/forge-gui/src/main/java/forge/game/zone/Zone.java similarity index 100% rename from forge-game/src/main/java/forge/game/zone/Zone.java rename to forge-gui/src/main/java/forge/game/zone/Zone.java diff --git a/forge-game/src/main/java/forge/game/zone/ZoneType.java b/forge-gui/src/main/java/forge/game/zone/ZoneType.java similarity index 100% rename from forge-game/src/main/java/forge/game/zone/ZoneType.java rename to forge-gui/src/main/java/forge/game/zone/ZoneType.java diff --git a/forge-game/src/main/java/forge/game/zone/package-info.java b/forge-gui/src/main/java/forge/game/zone/package-info.java similarity index 94% rename from forge-game/src/main/java/forge/game/zone/package-info.java rename to forge-gui/src/main/java/forge/game/zone/package-info.java index 019ead82538..0d4ba0eb13b 100644 --- a/forge-game/src/main/java/forge/game/zone/package-info.java +++ b/forge-gui/src/main/java/forge/game/zone/package-info.java @@ -1,3 +1,3 @@ -/** Forge Card Game. */ -package forge.game.zone; - +/** Forge Card Game. */ +package forge.game.zone; + diff --git a/forge-gui/src/main/java/forge/gui/match/controllers/CPicture.java b/forge-gui/src/main/java/forge/gui/match/controllers/CPicture.java index 530c0313b39..e41c19aa29d 100644 --- a/forge-gui/src/main/java/forge/gui/match/controllers/CPicture.java +++ b/forge-gui/src/main/java/forge/gui/match/controllers/CPicture.java @@ -24,9 +24,9 @@ import java.awt.event.MouseWheelListener; import javax.swing.JLabel; import forge.Command; -import forge.Singletons; import forge.card.CardCharacteristicName; import forge.game.card.Card; +import forge.game.card.CardUtil; import forge.gui.CardPicturePanel; import forge.gui.framework.ICDoc; import forge.gui.match.views.VPicture; @@ -192,7 +192,7 @@ public enum CPicture implements ICDoc { public void flipCard() { if (isCurrentCardFlippable()) { - displayedState = getAlternateState(currentCard, displayedState); + displayedState = CardUtil.getAlternateState(currentCard, displayedState); picturePanel.setCardImage(displayedState); setCardDetailPanel(); } @@ -220,56 +220,9 @@ public enum CPicture implements ICDoc { private boolean isCurrentCardFlippable() { boolean isFlippableMorph = - currentCard.isFaceDown() && isAuthorizedToViewFaceDownCard(currentCard); + currentCard.isFaceDown() && CardUtil.isAuthorizedToViewFaceDownCard(currentCard); return (currentCard != null) & (currentCard.isDoubleFaced() || currentCard.isFlipCard() || isFlippableMorph); } - - - - /** - * Card characteristic state machine. - *

- * Given a card and a state in terms of {@code CardCharacteristicName} this - * will determine whether there is a valid alternate {@code CardCharacteristicName} - * state for that card. - * - * @param card the {@code Card} - * @param currentState not necessarily {@code card.getCurState()} - * @return the alternate {@code CardCharacteristicName} state or default if not applicable - */ - public static CardCharacteristicName getAlternateState(final Card card, CardCharacteristicName currentState) { - // Default. Most cards will only ever have an "Original" state represented by a single image. - CardCharacteristicName alternateState = CardCharacteristicName.Original; - - if (card.isDoubleFaced()) { - if (currentState == CardCharacteristicName.Original) { - alternateState = CardCharacteristicName.Transformed; - } - - } else if (card.isFlipCard()) { - if (currentState == CardCharacteristicName.Original) { - alternateState = CardCharacteristicName.Flipped; - } - - } else if (card.isFaceDown()) { - if (currentState == CardCharacteristicName.Original) { - alternateState = CardCharacteristicName.FaceDown; - } else if (isAuthorizedToViewFaceDownCard(card)) { - alternateState = CardCharacteristicName.Original; - } else { - alternateState = currentState; - } - } - - return alternateState; - } - - /** - * Prevents player from identifying opponent's face-down card. - */ - public static boolean isAuthorizedToViewFaceDownCard(Card card) { - return Singletons.getControl().mayShowCard(card); - } } diff --git a/forge-gui/src/main/java/forge/gui/toolbox/special/CardZoomer.java b/forge-gui/src/main/java/forge/gui/toolbox/special/CardZoomer.java index 67bdc0376f6..33b5a7c087c 100644 --- a/forge-gui/src/main/java/forge/gui/toolbox/special/CardZoomer.java +++ b/forge-gui/src/main/java/forge/gui/toolbox/special/CardZoomer.java @@ -33,8 +33,8 @@ import javax.swing.Timer; import net.miginfocom.swing.MigLayout; import forge.card.CardCharacteristicName; import forge.game.card.Card; +import forge.game.card.CardUtil; import forge.gui.SOverlayUtils; -import forge.gui.match.controllers.CPicture; import forge.gui.toolbox.FOverlay; import forge.gui.toolbox.FSkin; import forge.gui.toolbox.FSkin.SkinnedLabel; @@ -224,7 +224,7 @@ public enum CardZoomer { * Displays a graphical indicator that shows whether the current card can be flipped or transformed. */ private void setFlipIndicator() { - boolean isFaceDownFlippable = (isFaceDownCard && CPicture.isAuthorizedToViewFaceDownCard(thisCard)); + boolean isFaceDownFlippable = (isFaceDownCard && CardUtil.isAuthorizedToViewFaceDownCard(thisCard)); if (thisCard.isFlipCard() || thisCard.isDoubleFaced() || isFaceDownFlippable ) { imagePanel.setLayout(new MigLayout("insets 0, w 100%!, h 100%!")); imagePanel.add(lblFlipcard, "pos (100% - 100px) 0"); @@ -327,7 +327,7 @@ public enum CardZoomer { * Uses constraint that prevents a player from identifying opponent's face-down cards. */ private void toggleFaceDownCard() { - cardState = CPicture.getAlternateState(thisCard, cardState); + cardState = CardUtil.getAlternateState(thisCard, cardState); setImage(); } @@ -335,7 +335,7 @@ public enum CardZoomer { * Toggles between the front and back image of a double-sided card. */ private void toggleDoubleFacedCard() { - cardState = CPicture.getAlternateState(thisCard, cardState); + cardState = CardUtil.getAlternateState(thisCard, cardState); setImage(); } diff --git a/forge-gui/src/main/java/forge/net/Lobby.java b/forge-gui/src/main/java/forge/net/Lobby.java index 2c65ccb19c5..6da5bba11e2 100644 --- a/forge-gui/src/main/java/forge/net/Lobby.java +++ b/forge-gui/src/main/java/forge/net/Lobby.java @@ -8,6 +8,7 @@ import com.google.common.base.Supplier; import forge.control.ChatArea; import forge.game.player.LobbyPlayer; import forge.game.player.LobbyPlayerAi; +import forge.game.player.LobbyPlayerRemote; import forge.gui.GuiDisplayUtil; import forge.gui.toolbox.FSkin; import forge.net.client.INetClient; diff --git a/forge-core/src/main/java/forge/util/Expressions.java b/forge-gui/src/main/java/forge/util/Expressions.java similarity index 100% rename from forge-core/src/main/java/forge/util/Expressions.java rename to forge-gui/src/main/java/forge/util/Expressions.java diff --git a/forge-core/src/main/java/forge/util/ReflectionUtil.java b/forge-gui/src/main/java/forge/util/ReflectionUtil.java similarity index 100% rename from forge-core/src/main/java/forge/util/ReflectionUtil.java rename to forge-gui/src/main/java/forge/util/ReflectionUtil.java diff --git a/forge-game/src/main/java/forge/util/maps/EnumMapOfLists.java b/forge-gui/src/main/java/forge/util/maps/EnumMapOfLists.java similarity index 100% rename from forge-game/src/main/java/forge/util/maps/EnumMapOfLists.java rename to forge-gui/src/main/java/forge/util/maps/EnumMapOfLists.java diff --git a/forge-game/src/main/java/forge/util/maps/EnumMapToAmount.java b/forge-gui/src/main/java/forge/util/maps/EnumMapToAmount.java similarity index 100% rename from forge-game/src/main/java/forge/util/maps/EnumMapToAmount.java rename to forge-gui/src/main/java/forge/util/maps/EnumMapToAmount.java diff --git a/forge-game/src/main/java/forge/util/maps/HashMapOfLists.java b/forge-gui/src/main/java/forge/util/maps/HashMapOfLists.java similarity index 100% rename from forge-game/src/main/java/forge/util/maps/HashMapOfLists.java rename to forge-gui/src/main/java/forge/util/maps/HashMapOfLists.java diff --git a/forge-game/src/main/java/forge/util/maps/MapOfLists.java b/forge-gui/src/main/java/forge/util/maps/MapOfLists.java similarity index 100% rename from forge-game/src/main/java/forge/util/maps/MapOfLists.java rename to forge-gui/src/main/java/forge/util/maps/MapOfLists.java diff --git a/forge-game/src/main/java/forge/util/maps/MapToAmount.java b/forge-gui/src/main/java/forge/util/maps/MapToAmount.java similarity index 100% rename from forge-game/src/main/java/forge/util/maps/MapToAmount.java rename to forge-gui/src/main/java/forge/util/maps/MapToAmount.java diff --git a/forge-game/src/main/java/forge/util/maps/package-info.java b/forge-gui/src/main/java/forge/util/maps/package-info.java similarity index 100% rename from forge-game/src/main/java/forge/util/maps/package-info.java rename to forge-gui/src/main/java/forge/util/maps/package-info.java diff --git a/forge-gui/src/main/java/forge/view/Main.java b/forge-gui/src/main/java/forge/view/Main.java index 689a60cfd69..fec39c2018a 100644 --- a/forge-gui/src/main/java/forge/view/Main.java +++ b/forge-gui/src/main/java/forge/view/Main.java @@ -18,8 +18,6 @@ package forge.view; -import forge.ImageCacheProvider; -import forge.PreferencesProvider; import forge.Singletons; import forge.net.FServer; @@ -37,9 +35,6 @@ public final class Main { //Turn off the Java 2D system's use of Direct3D to improve rendering speed (particularly when Full Screen) System.setProperty("sun.java2d.d3d", "false"); - forge.PreferencesBridge.Instance = new PreferencesProvider(); - forge.ImageCacheBridge.instance = new ImageCacheProvider(); - // Start splash screen first, then data models, then controller. if (args.length == 0) { Singletons.initializeOnce(true); @@ -49,7 +44,6 @@ public final class Main { return; } - // command line startup here String mode = args[0].toLowerCase();