diff --git a/.gitattributes b/.gitattributes index af2d338cbb5..842b74895ee 100644 --- a/.gitattributes +++ b/.gitattributes @@ -175,6 +175,7 @@ res/cardsfolder/a/akoum_refuge.txt svneol=native#text/plain res/cardsfolder/a/akrasan_squire.txt svneol=native#text/plain res/cardsfolder/a/akroma_angel_of_fury.txt svneol=native#text/plain res/cardsfolder/a/akroma_angel_of_wrath.txt svneol=native#text/plain +res/cardsfolder/a/akroma_angel_of_wrath_avatar.txt -text res/cardsfolder/a/akromas_blessing.txt -text res/cardsfolder/a/akromas_devoted.txt svneol=native#text/plain res/cardsfolder/a/akromas_memorial.txt svneol=native#text/plain @@ -557,6 +558,7 @@ res/cardsfolder/a/assemble_the_legion.txt -text res/cardsfolder/a/assembly_hall.txt -text res/cardsfolder/a/assembly_worker.txt svneol=native#text/plain res/cardsfolder/a/assert_authority.txt svneol=native#text/plain +res/cardsfolder/a/astral_arena.txt -text res/cardsfolder/a/astral_slide.txt svneol=native#text/plain res/cardsfolder/a/astral_steel.txt svneol=native#text/plain res/cardsfolder/a/astrolabe.txt svneol=native#text/plain @@ -901,6 +903,7 @@ res/cardsfolder/b/benevolent_bodyguard.txt svneol=native#text/plain res/cardsfolder/b/benevolent_unicorn.txt svneol=native#text/plain res/cardsfolder/b/benthic_behemoth.txt svneol=native#text/plain res/cardsfolder/b/benthic_djinn.txt svneol=native#text/plain +res/cardsfolder/b/benthic_explorers.txt -text res/cardsfolder/b/benthicore.txt svneol=native#text/plain res/cardsfolder/b/bequeathal.txt svneol=native#text/plain res/cardsfolder/b/bereavement.txt svneol=native#text/plain @@ -976,6 +979,7 @@ res/cardsfolder/b/blaze.txt svneol=native#text/plain res/cardsfolder/b/blazethorn_scarecrow.txt svneol=native#text/plain res/cardsfolder/b/blazing_archon.txt svneol=native#text/plain res/cardsfolder/b/blazing_blade_askari.txt svneol=native#text/plain +res/cardsfolder/b/blazing_effigy.txt -text res/cardsfolder/b/blazing_salvo.txt -text res/cardsfolder/b/blazing_shoal.txt svneol=native#text/plain res/cardsfolder/b/blazing_specter.txt svneol=native#text/plain @@ -1074,6 +1078,7 @@ res/cardsfolder/b/bloodfray_giant.txt -text res/cardsfolder/b/bloodghast.txt svneol=native#text/plain res/cardsfolder/b/bloodgift_demon.txt -text res/cardsfolder/b/bloodhall_ooze.txt svneol=native#text/plain +res/cardsfolder/b/bloodhill_bastion.txt -text res/cardsfolder/b/bloodhunter_bat.txt -text res/cardsfolder/b/bloodhusk_ritualist.txt svneol=native#text/plain res/cardsfolder/b/bloodied_ghost.txt svneol=native#text/plain @@ -1903,6 +1908,7 @@ res/cardsfolder/c/colossus_of_sardia.txt svneol=native#text/plain res/cardsfolder/c/coma_veil.txt svneol=native#text/plain res/cardsfolder/c/combat_medic.txt svneol=native#text/plain res/cardsfolder/c/combust.txt svneol=native#text/plain +res/cardsfolder/c/comet_storm.txt -text res/cardsfolder/c/command_of_unsummoning.txt svneol=native#text/plain res/cardsfolder/c/commander_eesha.txt svneol=native#text/plain res/cardsfolder/c/commander_greven_il_vec.txt svneol=native#text/plain @@ -2238,9 +2244,11 @@ res/cardsfolder/c/cursed_rack.txt svneol=native#text/plain res/cardsfolder/c/cursed_ronin.txt svneol=native#text/plain res/cardsfolder/c/cursed_scroll.txt svneol=native#text/plain res/cardsfolder/c/cursed_totem.txt svneol=native#text/plain +res/cardsfolder/c/curtain_of_light.txt -text res/cardsfolder/c/custody_battle.txt -text svneol=unset#text/plain res/cardsfolder/c/customs_depot.txt svneol=native#text/plain res/cardsfolder/c/cut_the_earthly_bond.txt svneol=native#text/plain +res/cardsfolder/c/cut_the_tethers.txt -text res/cardsfolder/c/cutthroat_il_dal.txt svneol=native#text/plain res/cardsfolder/c/cyclical_evolution.txt svneol=native#text/plain res/cardsfolder/c/cyclonic_rift.txt -text @@ -2378,6 +2386,7 @@ res/cardsfolder/d/day_of_the_dragons.txt svneol=native#text/plain res/cardsfolder/d/daybreak_coronet.txt -text res/cardsfolder/d/daybreak_ranger_nightfall_predator.txt -text res/cardsfolder/d/daze.txt svneol=native#text/plain +res/cardsfolder/d/dazzling_beauty.txt -text res/cardsfolder/d/dead_gone.txt -text res/cardsfolder/d/dead_iron_sledge.txt svneol=native#text/plain res/cardsfolder/d/dead_reckoning.txt -text svneol=unset#text/plain @@ -2502,6 +2511,7 @@ res/cardsfolder/d/deglamer.txt svneol=native#text/plain res/cardsfolder/d/dehydration.txt svneol=native#text/plain res/cardsfolder/d/deity_of_scars.txt svneol=native#text/plain res/cardsfolder/d/deja_vu.txt svneol=native#text/plain +res/cardsfolder/d/delay.txt -text res/cardsfolder/d/delaying_shield.txt -text res/cardsfolder/d/delifs_cone.txt -text res/cardsfolder/d/delifs_cube.txt -text @@ -3036,6 +3046,7 @@ res/cardsfolder/e/ego_erasure.txt -text res/cardsfolder/e/eiganjo_castle.txt svneol=native#text/plain res/cardsfolder/e/eiganjo_free_riders.txt svneol=native#text/plain res/cardsfolder/e/eight_and_a_half_tails.txt -text +res/cardsfolder/e/eight_and_a_half_tails_avatar.txt -text res/cardsfolder/e/eightfold_maze.txt svneol=native#text/plain res/cardsfolder/e/ekundu_griffin.txt svneol=native#text/plain res/cardsfolder/e/el_hajjaj.txt svneol=native#text/plain @@ -3788,6 +3799,7 @@ res/cardsfolder/f/fog_bank.txt svneol=native#text/plain res/cardsfolder/f/fog_elemental.txt svneol=native#text/plain res/cardsfolder/f/fog_of_gnats.txt svneol=native#text/plain res/cardsfolder/f/foil.txt -text +res/cardsfolder/f/fold_into_aether.txt -text res/cardsfolder/f/folk_medicine.txt svneol=native#text/plain res/cardsfolder/f/folk_of_an_havva.txt svneol=native#text/plain res/cardsfolder/f/folk_of_the_pines.txt svneol=native#text/plain @@ -4015,6 +4027,7 @@ res/cardsfolder/g/gather_the_townsfolk.txt -text res/cardsfolder/g/gatherer_of_graces.txt svneol=native#text/plain res/cardsfolder/g/gatstaf_shepherd_gatstaf_howler.txt -text res/cardsfolder/g/gauntlet_of_might.txt svneol=native#text/plain +res/cardsfolder/g/gavony.txt -text res/cardsfolder/g/gavony_ironwright.txt -text res/cardsfolder/g/gavony_township.txt -text res/cardsfolder/g/gaze_of_adamaro.txt svneol=native#text/plain @@ -4716,6 +4729,7 @@ res/cardsfolder/h/harrow.txt svneol=native#text/plain res/cardsfolder/h/harrowing_journey.txt -text res/cardsfolder/h/harsh_deceiver.txt -text res/cardsfolder/h/harsh_justice.txt -text +res/cardsfolder/h/harsh_mercy.txt -text res/cardsfolder/h/haru_onna.txt svneol=native#text/plain res/cardsfolder/h/harvest_gwyllion.txt svneol=native#text/plain res/cardsfolder/h/harvest_pyre.txt -text @@ -4804,6 +4818,7 @@ res/cardsfolder/h/heckling_fiends.txt -text res/cardsfolder/h/hedge_troll.txt svneol=native#text/plain res/cardsfolder/h/hedron_crab.txt svneol=native#text/plain res/cardsfolder/h/hedron_field_purists.txt -text +res/cardsfolder/h/hedron_fields_of_agadeem.txt -text res/cardsfolder/h/hedron_matrix.txt svneol=native#text/plain res/cardsfolder/h/hedron_rover.txt svneol=native#text/plain res/cardsfolder/h/hedron_scrabbler.txt svneol=native#text/plain @@ -4862,6 +4877,7 @@ res/cardsfolder/h/hero_of_oxid_ridge.txt svneol=native#text/plain res/cardsfolder/h/heroes_remembered.txt svneol=native#text/plain res/cardsfolder/h/heroes_reunion.txt svneol=native#text/plain res/cardsfolder/h/heroic_defiance.txt -text +res/cardsfolder/h/heroism.txt -text res/cardsfolder/h/heros_demise.txt svneol=native#text/plain res/cardsfolder/h/heros_resolve.txt svneol=native#text/plain res/cardsfolder/h/hesitation.txt -text svneol=unset#text/plain @@ -4900,6 +4916,7 @@ res/cardsfolder/h/higure_the_still_wind_avatar.txt -text res/cardsfolder/h/hikari_twilight_guardian.txt -text res/cardsfolder/h/hill_giant.txt svneol=native#text/plain res/cardsfolder/h/hillcomber_giant.txt svneol=native#text/plain +res/cardsfolder/h/hinder.txt -text res/cardsfolder/h/hindering_light.txt svneol=native#text/plain res/cardsfolder/h/hindering_touch.txt svneol=native#text/plain res/cardsfolder/h/hindervines.txt -text @@ -5434,6 +5451,7 @@ res/cardsfolder/j/jhessian_infiltrator.txt svneol=native#text/plain res/cardsfolder/j/jhessian_lookout.txt svneol=native#text/plain res/cardsfolder/j/jhessian_zombies.txt svneol=native#text/plain res/cardsfolder/j/jhoira_of_the_ghitu.txt svneol=native#text/plain +res/cardsfolder/j/jhoira_of_the_ghitu_avatar.txt -text res/cardsfolder/j/jhoiras_timebug.txt -text res/cardsfolder/j/jhoiras_toolbox.txt svneol=native#text/plain res/cardsfolder/j/jhovall_queen.txt svneol=native#text/plain @@ -5511,6 +5529,7 @@ res/cardsfolder/k/kaalia_of_the_vast.txt -text res/cardsfolder/k/kabira_crossroads.txt svneol=native#text/plain res/cardsfolder/k/kabira_evangel.txt -text res/cardsfolder/k/kabira_vindicator.txt svneol=native#text/plain +res/cardsfolder/k/kaboom!.txt -text res/cardsfolder/k/kabuto_moth.txt svneol=native#text/plain res/cardsfolder/k/kaervek_the_merciless.txt svneol=native#text/plain res/cardsfolder/k/kaerveks_hex.txt svneol=native#text/plain @@ -5529,6 +5548,7 @@ res/cardsfolder/k/kamahl_fist_of_krosa.txt svneol=native#text/plain res/cardsfolder/k/kamahl_pit_fighter.txt svneol=native#text/plain res/cardsfolder/k/kamahls_desire.txt svneol=native#text/plain res/cardsfolder/k/kamahls_sledge.txt -text +res/cardsfolder/k/kamahls_summons.txt -text res/cardsfolder/k/kami_of_ancient_law.txt svneol=native#text/plain res/cardsfolder/k/kami_of_empty_graves.txt svneol=native#text/plain res/cardsfolder/k/kami_of_false_hope.txt svneol=native#text/plain @@ -5549,11 +5569,13 @@ res/cardsfolder/k/karakas.txt svneol=native#text/plain res/cardsfolder/k/kargan_dragonlord.txt svneol=native#text/plain res/cardsfolder/k/karma.txt svneol=native#text/plain res/cardsfolder/k/karmic_guide.txt svneol=native#text/plain +res/cardsfolder/k/karmic_justice.txt -text res/cardsfolder/k/karn.txt -text res/cardsfolder/k/karn_liberated.txt -text res/cardsfolder/k/karn_silver_golem.txt svneol=native#text/plain res/cardsfolder/k/karns_touch.txt svneol=native#text/plain res/cardsfolder/k/karona_false_god.txt -text +res/cardsfolder/k/karona_false_god_avatar.txt -text res/cardsfolder/k/karonas_zealot.txt -text res/cardsfolder/k/karoo.txt svneol=native#text/plain res/cardsfolder/k/karoo_meerkat.txt svneol=native#text/plain @@ -5633,6 +5655,7 @@ res/cardsfolder/k/kemba_kha_regent.txt svneol=native#text/plain res/cardsfolder/k/kembas_legion.txt -text res/cardsfolder/k/kembas_skyguard.txt svneol=native#text/plain res/cardsfolder/k/kemuri_onna.txt svneol=native#text/plain +res/cardsfolder/k/kessig.txt -text res/cardsfolder/k/kessig_cagebreakers.txt -text res/cardsfolder/k/kessig_malcontents.txt -text res/cardsfolder/k/kessig_recluse.txt -text @@ -5778,6 +5801,7 @@ res/cardsfolder/k/korlash_heir_to_blackblade.txt svneol=native#text/plain res/cardsfolder/k/kormus_bell.txt svneol=native#text/plain res/cardsfolder/k/korozda_guildmage.txt -text res/cardsfolder/k/korozda_monitor.txt -text +res/cardsfolder/k/koskun_falls.txt -text res/cardsfolder/k/koskun_keep.txt svneol=native#text/plain res/cardsfolder/k/koth_of_the_hammer.txt svneol=native#text/plain res/cardsfolder/k/koths_courier.txt svneol=native#text/plain @@ -5796,6 +5820,7 @@ res/cardsfolder/k/krark_clan_stoker.txt svneol=native#text/plain res/cardsfolder/k/krenko_mob_boss.txt -text res/cardsfolder/k/krenkos_command.txt -text res/cardsfolder/k/kresh_the_bloodbraided.txt svneol=native#text/plain +res/cardsfolder/k/kresh_the_bloodbraided_avatar.txt -text res/cardsfolder/k/kris_mage.txt svneol=native#text/plain res/cardsfolder/k/krond_the_dawn_clad.txt -text res/cardsfolder/k/krosa.txt -text @@ -5950,6 +5975,7 @@ res/cardsfolder/l/leap_of_flame.txt svneol=native#text/plain res/cardsfolder/l/leaping_lizard.txt svneol=native#text/plain res/cardsfolder/l/leatherback_baloth.txt svneol=native#text/plain res/cardsfolder/l/leave_no_trace.txt svneol=native#text/plain +res/cardsfolder/l/leech_bonder.txt -text res/cardsfolder/l/leeches.txt svneol=native#text/plain res/cardsfolder/l/leeching_bite.txt -text res/cardsfolder/l/leeching_licid.txt -text @@ -6202,6 +6228,7 @@ res/cardsfolder/l/lu_xun_scholar_general.txt svneol=native#text/plain res/cardsfolder/l/lucent_liminid.txt svneol=native#text/plain res/cardsfolder/l/ludevics_test_subject_ludevics_abomination.txt -text res/cardsfolder/l/lull.txt svneol=native#text/plain +res/cardsfolder/l/lullmage_mentor.txt -text res/cardsfolder/l/lumbering_satyr.txt svneol=native#text/plain res/cardsfolder/l/lumberknot.txt -text res/cardsfolder/l/lumengrid_augur.txt -text @@ -6936,6 +6963,7 @@ res/cardsfolder/m/multani_maro_sorcerer.txt svneol=native#text/plain res/cardsfolder/m/multanis_acolyte.txt svneol=native#text/plain res/cardsfolder/m/multanis_decree.txt svneol=native#text/plain res/cardsfolder/m/multanis_harmony.txt -text svneol=unset#text/plain +res/cardsfolder/m/multanis_presence.txt -text res/cardsfolder/m/mundungu.txt -text res/cardsfolder/m/mungha_wurm.txt svneol=native#text/plain res/cardsfolder/m/muraganda_petroglyphs.txt svneol=native#text/plain @@ -7128,6 +7156,7 @@ res/cardsfolder/n/nemata_grove_guardian.txt svneol=native#text/plain res/cardsfolder/n/nemesis_mask.txt svneol=native#text/plain res/cardsfolder/n/nemesis_of_reason.txt svneol=native#text/plain res/cardsfolder/n/nemesis_trap.txt -text +res/cardsfolder/n/nephalia.txt -text res/cardsfolder/n/nephalia_drownyard.txt -text res/cardsfolder/n/nephalia_seakite.txt -text res/cardsfolder/n/nephalia_smuggler.txt -text @@ -7641,6 +7670,7 @@ res/cardsfolder/p/peace_and_quiet.txt svneol=native#text/plain res/cardsfolder/p/peace_of_mind.txt svneol=native#text/plain res/cardsfolder/p/peace_strider.txt svneol=native#text/plain res/cardsfolder/p/peacekeeper.txt svneol=native#text/plain +res/cardsfolder/p/peacekeeper_avatar.txt -text res/cardsfolder/p/peach_garden_oath.txt svneol=native#text/plain res/cardsfolder/p/pearl_dragon.txt svneol=native#text/plain res/cardsfolder/p/pearl_medallion.txt svneol=native#text/plain @@ -8846,6 +8876,7 @@ res/cardsfolder/r/runeboggle.txt svneol=native#text/plain res/cardsfolder/r/runechanters_pike.txt -text res/cardsfolder/r/runeclaw_bear.txt svneol=native#text/plain res/cardsfolder/r/runed_arch.txt svneol=native#text/plain +res/cardsfolder/r/runed_halo.txt -text res/cardsfolder/r/runed_servitor.txt svneol=native#text/plain res/cardsfolder/r/runed_stalactite.txt svneol=native#text/plain res/cardsfolder/r/runeflare_trap.txt -text @@ -9779,6 +9810,7 @@ res/cardsfolder/s/slithery_stalker.txt svneol=native#text/plain res/cardsfolder/s/sliver_legion.txt svneol=native#text/plain res/cardsfolder/s/sliver_overlord.txt svneol=native#text/plain res/cardsfolder/s/sliver_queen.txt svneol=native#text/plain +res/cardsfolder/s/sliver_queen_avatar.txt -text res/cardsfolder/s/sliver_queen_brood_mother.txt -text res/cardsfolder/s/sliversmith.txt svneol=native#text/plain res/cardsfolder/s/slobad_goblin_tinkerer.txt svneol=native#text/plain @@ -9945,6 +9977,7 @@ res/cardsfolder/s/soul_tithe.txt -text res/cardsfolder/s/soul_warden.txt svneol=native#text/plain res/cardsfolder/s/soulblast.txt -text res/cardsfolder/s/soulbound_guardians.txt svneol=native#text/plain +res/cardsfolder/s/soulbright_flamekin.txt -text res/cardsfolder/s/soulcage_fiend.txt -text res/cardsfolder/s/soulcatcher.txt svneol=native#text/plain res/cardsfolder/s/soulcatchers_aerie.txt svneol=native#text/plain @@ -10330,6 +10363,7 @@ res/cardsfolder/s/stonefare_crocodile.txt -text res/cardsfolder/s/stoneforge_mystic.txt svneol=native#text/plain res/cardsfolder/s/stonehands.txt svneol=native#text/plain res/cardsfolder/s/stonehewer_giant.txt -text +res/cardsfolder/s/stonehewer_giant_avatar.txt -text res/cardsfolder/s/stonehorn_dignitary.txt -text res/cardsfolder/s/stonewood_invocation.txt svneol=native#text/plain res/cardsfolder/s/stonewood_invoker.txt svneol=native#text/plain @@ -10560,6 +10594,7 @@ res/cardsfolder/s/swell_of_courage.txt svneol=native#text/plain res/cardsfolder/s/swelter.txt svneol=native#text/plain res/cardsfolder/s/swift_justice.txt -text res/cardsfolder/s/swift_maneuver.txt svneol=native#text/plain +res/cardsfolder/s/swift_silence.txt -text res/cardsfolder/s/swiftfoot_boots.txt svneol=native#text/plain res/cardsfolder/s/swirling_sandstorm.txt svneol=native#text/plain res/cardsfolder/s/swirling_spriggan.txt -text @@ -10858,6 +10893,7 @@ res/cardsfolder/t/the_tabernacle_at_pendrell_vale.txt svneol=native#text/plain res/cardsfolder/t/the_unspeakable.txt svneol=native#text/plain res/cardsfolder/t/the_very_soil_shall_shake.txt -text res/cardsfolder/t/the_wretched.txt -text svneol=unset#text/plain +res/cardsfolder/t/the_zephyr_maze.txt -text res/cardsfolder/t/theft_of_dreams.txt svneol=native#text/plain res/cardsfolder/t/thelon_of_havenwood.txt -text res/cardsfolder/t/thelonite_druid.txt -text @@ -11644,6 +11680,7 @@ res/cardsfolder/v/vex.txt svneol=native#text/plain res/cardsfolder/v/vexing_arcanix.txt -text res/cardsfolder/v/vexing_beetle.txt svneol=native#text/plain res/cardsfolder/v/vexing_devil.txt -text +res/cardsfolder/v/vexing_shusher.txt -text res/cardsfolder/v/vexing_sphinx.txt -text res/cardsfolder/v/vhati_il_dal.txt -text res/cardsfolder/v/viashino_bladescout.txt svneol=native#text/plain @@ -11960,6 +11997,7 @@ res/cardsfolder/w/warmongers_chariot.txt svneol=native#text/plain res/cardsfolder/w/warmth.txt svneol=native#text/plain res/cardsfolder/w/warning.txt svneol=native#text/plain res/cardsfolder/w/warp_artifact.txt svneol=native#text/plain +res/cardsfolder/w/warp_world.txt -text res/cardsfolder/w/warpath.txt svneol=native#text/plain res/cardsfolder/w/warpath_ghoul.txt svneol=native#text/plain res/cardsfolder/w/warped_devotion.txt svneol=native#text/plain @@ -12031,6 +12069,7 @@ res/cardsfolder/w/wei_infantry.txt svneol=native#text/plain res/cardsfolder/w/wei_night_raiders.txt svneol=native#text/plain res/cardsfolder/w/wei_scout.txt svneol=native#text/plain res/cardsfolder/w/wei_strike_force.txt svneol=native#text/plain +res/cardsfolder/w/weight_of_conscience.txt -text res/cardsfolder/w/weight_of_spires.txt -text res/cardsfolder/w/weird_harvest.txt -text res/cardsfolder/w/weirding_shaman.txt svneol=native#text/plain @@ -12452,6 +12491,7 @@ res/cardsfolder/z/zur_the_enchanter.txt svneol=native#text/plain res/cardsfolder/z/zuran_enchanter.txt svneol=native#text/plain res/cardsfolder/z/zuran_orb.txt svneol=native#text/plain res/cardsfolder/z/zuran_spellcaster.txt svneol=native#text/plain +res/cardsfolder/z/zurs_weirding.txt -text res/defaults/editor.preferences svneol=native#text/xml res/defaults/editor.xml svneol=native#text/xml res/defaults/gauntlet/LOCKED_DotP[!!-~]Preconstructed.dat -text @@ -12794,6 +12834,7 @@ res/quest/duels/Picard[!!-~]3.dck -text res/quest/duels/Pinky[!!-~]and[!!-~]the[!!-~]Brain[!!-~]2.dck -text res/quest/duels/Piper[!!-~]2.dck -text res/quest/duels/Pointy[!!-~]Haired[!!-~]Boss[!!-~]3.dck -text +res/quest/duels/Preacher[!!-~]3.dck -text res/quest/duels/Princess[!!-~]Selenia[!!-~]1.dck -text res/quest/duels/Professor[!!-~]X[!!-~]2.dck -text res/quest/duels/Professor[!!-~]X[!!-~]3.dck -text @@ -13485,6 +13526,7 @@ src/main/java/forge/card/ability/ai/AlwaysPlayAi.java -text src/main/java/forge/card/ability/ai/AnimateAi.java -text src/main/java/forge/card/ability/ai/AnimateAllAi.java -text src/main/java/forge/card/ability/ai/AttachAi.java -text +src/main/java/forge/card/ability/ai/BecomesBlockedAi.java -text src/main/java/forge/card/ability/ai/BondAi.java -text src/main/java/forge/card/ability/ai/CanPlayAsDrawbackAi.java -text src/main/java/forge/card/ability/ai/CannotPlayAi.java -text @@ -13584,6 +13626,7 @@ src/main/java/forge/card/ability/effects/AnimateAllEffect.java -text src/main/java/forge/card/ability/effects/AnimateEffect.java -text src/main/java/forge/card/ability/effects/AnimateEffectBase.java svneol=native#text/plain src/main/java/forge/card/ability/effects/AttachEffect.java -text +src/main/java/forge/card/ability/effects/BecomesBlockedEffect.java -text src/main/java/forge/card/ability/effects/BondEffect.java -text src/main/java/forge/card/ability/effects/ChangeZoneAllEffect.java -text src/main/java/forge/card/ability/effects/ChangeZoneEffect.java -text @@ -13744,11 +13787,11 @@ src/main/java/forge/card/spellability/AbilityManaPart.java svneol=native#text/pl src/main/java/forge/card/spellability/AbilityStatic.java svneol=native#text/plain src/main/java/forge/card/spellability/AbilitySub.java svneol=native#text/plain src/main/java/forge/card/spellability/AbilityTriggered.java svneol=native#text/plain +src/main/java/forge/card/spellability/HumanPlaySpellAbility.java svneol=native#text/plain src/main/java/forge/card/spellability/ISpellAbility.java -text src/main/java/forge/card/spellability/Spell.java svneol=native#text/plain src/main/java/forge/card/spellability/SpellAbility.java svneol=native#text/plain src/main/java/forge/card/spellability/SpellAbilityCondition.java svneol=native#text/plain -src/main/java/forge/card/spellability/SpellAbilityRequirements.java svneol=native#text/plain src/main/java/forge/card/spellability/SpellAbilityRestriction.java svneol=native#text/plain src/main/java/forge/card/spellability/SpellAbilityStackInstance.java svneol=native#text/plain src/main/java/forge/card/spellability/SpellAbilityVariables.java svneol=native#text/plain @@ -13780,8 +13823,11 @@ src/main/java/forge/card/trigger/TriggerChangesZone.java svneol=native#text/plai src/main/java/forge/card/trigger/TriggerClashed.java svneol=native#text/plain src/main/java/forge/card/trigger/TriggerCounterAdded.java svneol=native#text/plain src/main/java/forge/card/trigger/TriggerCounterRemoved.java -text +src/main/java/forge/card/trigger/TriggerCountered.java -text src/main/java/forge/card/trigger/TriggerCycled.java svneol=native#text/plain src/main/java/forge/card/trigger/TriggerDamageDone.java svneol=native#text/plain +src/main/java/forge/card/trigger/TriggerDestroyed.java -text +src/main/java/forge/card/trigger/TriggerDevoured.java -text src/main/java/forge/card/trigger/TriggerDiscarded.java svneol=native#text/plain src/main/java/forge/card/trigger/TriggerDrawn.java svneol=native#text/plain src/main/java/forge/card/trigger/TriggerHandler.java svneol=native#text/plain @@ -13817,12 +13863,14 @@ src/main/java/forge/control/bazaar/ControlStall.java -text src/main/java/forge/control/bazaar/package-info.java svneol=native#text/plain src/main/java/forge/control/input/Input.java -text src/main/java/forge/control/input/InputAttack.java svneol=native#text/plain +src/main/java/forge/control/input/InputAutoPassPriority.java -text src/main/java/forge/control/input/InputBase.java svneol=native#text/plain src/main/java/forge/control/input/InputBlock.java svneol=native#text/plain src/main/java/forge/control/input/InputCleanup.java svneol=native#text/plain src/main/java/forge/control/input/InputControl.java svneol=native#text/plain src/main/java/forge/control/input/InputLockUI.java -text src/main/java/forge/control/input/InputMulligan.java svneol=native#text/plain +src/main/java/forge/control/input/InputPartialParisMulligan.java -text src/main/java/forge/control/input/InputPassPriority.java svneol=native#text/plain src/main/java/forge/control/input/InputPayManaBase.java -text src/main/java/forge/control/input/InputPayManaExecuteCommands.java svneol=native#text/plain @@ -13834,6 +13882,7 @@ src/main/java/forge/control/input/InputSelectCards.java -text src/main/java/forge/control/input/InputSelectCardsFromList.java -text src/main/java/forge/control/input/InputSelectMany.java -text src/main/java/forge/control/input/InputSelectManyBase.java -text +src/main/java/forge/control/input/InputSelectTargets.java -text src/main/java/forge/control/input/InputSynchronized.java -text src/main/java/forge/control/input/InputSyncronizedBase.java -text src/main/java/forge/control/input/package-info.java svneol=native#text/plain @@ -13865,8 +13914,8 @@ src/main/java/forge/deck/package-info.java svneol=native#text/plain src/main/java/forge/error/ExceptionHandler.java svneol=native#text/plain src/main/java/forge/error/package-info.java svneol=native#text/plain src/main/java/forge/game/GameAction.java svneol=native#text/plain -src/main/java/forge/game/GameActionPlay.java -text src/main/java/forge/game/GameActionUtil.java svneol=native#text/plain +src/main/java/forge/game/GameAge.java -text src/main/java/forge/game/GameEndReason.java -text src/main/java/forge/game/GameFormat.java -text src/main/java/forge/game/GameLossReason.java -text @@ -14255,6 +14304,7 @@ src/main/java/forge/util/HttpUtil.java svneol=native#text/plain src/main/java/forge/util/IItemReader.java -text src/main/java/forge/util/IItemSerializer.java -text src/main/java/forge/util/IgnoringXStream.java -text +src/main/java/forge/util/Lang.java -text src/main/java/forge/util/LineReader.java -text src/main/java/forge/util/MultiplexOutputStream.java svneol=native#text/plain src/main/java/forge/util/MyObservable.java svneol=native#text/plain diff --git a/CHANGES.txt b/CHANGES.txt index 8bbcd07304c..a9cc5e8b34c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -47,6 +47,14 @@ The Import Pictures dialog, accessed via the Content Downloaders submenu, has re An importer option was added for including pictures in set-specific card directories that don't map to any currently known card. This handles the case where people have collected complete sets of pics in anticipation of when Forge supports them. +- Flip, Transform and Morph cards - +When you mouse over a flip, transform or Morph (controlled by you) card in battlefield, you may hold SHIFT to see other state of that card at the side panel that displays card picture and details. + + +- Milestone reached - +There is now under 700 unsupported cards, and we should be at or near 85% for all the sets. + + --------- New Cards --------- @@ -58,6 +66,30 @@ Breathstealer's Crypt Moonlight Bargain Auriok Siege Sled Burning-Tree Bloodscale +Soulbright Flamekin +Koskun Falls +Warp World +Zur's Weirding +Vexing Shusher +Benthic Explorers +Leech Bonder +Weight of Conscience +Multani's Presence +Lullmage Mentor +Fold into AEther +Hinder +Swift Silence +Cut the Tethers +Delay +Runed Halo +Heroism +Blazing Effigy +Karmic Justice +Harsh Mercy +Kamahl's Summons +Kaboom! +Curtain of Light +Dazzling Beauty ---------- @@ -65,6 +97,27 @@ New Planes ---------- Sea of Sand +Astral Arena +Bloodhill Bastion +Gavony +Hedron Fields of Agadeem +Kessig +Nephalia +The Zephyr Maze + + +-------------------- +New Vanguard Avatars +-------------------- + +Akroma, Angel of Wrath Avatar +Eight-and-a-Half-Tails Avatar +Peacekeeper Avatar +Stonehewer Giant Avatar +Sliver Queen Avatar +Jhoira of the Ghitu Avatar +Kresh the Bloodbraided Avatar +Karona, False God Avatar ------------ diff --git a/res/cardsfolder/a/acquire.txt b/res/cardsfolder/a/acquire.txt index 6505302fcd2..770faaa36c4 100644 --- a/res/cardsfolder/a/acquire.txt +++ b/res/cardsfolder/a/acquire.txt @@ -1,7 +1,7 @@ Name:Acquire ManaCost:3 U U Types:Sorcery -A:SP$ ChangeZone | Cost$ 3 U U | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | ChangeType$ Artifact | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | SpellDescription$ Search target opponent's library for an artifact card and put that card onto the battlefield under your control. Then that player shuffles his or her library. +A:SP$ ChangeZone | Cost$ 3 U U | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | ChangeType$ Artifact | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for an artifact card and put that card onto the battlefield under your control. Then that player shuffles his or her library. SVar:Picture:http://www.wizards.com/global/images/magic/general/acquire.jpg Oracle:Search target opponent's library for an artifact card and put that card onto the battlefield under your control. Then that player shuffles his or her library. SetInfo:5DN Rare \ No newline at end of file diff --git a/res/cardsfolder/a/akroma_angel_of_wrath_avatar.txt b/res/cardsfolder/a/akroma_angel_of_wrath_avatar.txt new file mode 100644 index 00000000000..df1c3c39866 --- /dev/null +++ b/res/cardsfolder/a/akroma_angel_of_wrath_avatar.txt @@ -0,0 +1,9 @@ +Name:Akroma, Angel of Wrath Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+1/+7 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | TriggerZones$ Command | ValidCard$ Creature.YouCtrl | Execute$ PumpRandom | TriggerDescription$ Whenever a creature enters the battlefield under your control, it gains two abilities chosen at random from flying, first strike, trample, haste, protection from black, protection from red, and vigilance. +SVar:PumpRandom:AB$ Pump | Cost$ 0 | Defined$ TriggeredCard | Permanent$ True | KW$ Flying & First Strike & Trample & Haste & Protection from black & Protection from red & Vigilance | RandomKeyword$ True | RandomKWNum$ 2 +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/akroma_angel_of_wrath_avatar.jpg +Oracle:Hand +1, life +7\nWhenever a creature enters the battlefield under your control, it gains two abilities chosen at random from flying, first strike, trample, haste, protection from black, protection from red, and vigilance. +SetInfo:VAN Special \ No newline at end of file diff --git a/res/cardsfolder/a/appetite_for_brains.txt b/res/cardsfolder/a/appetite_for_brains.txt index d4eb10f3cb1..d46696fafb8 100644 --- a/res/cardsfolder/a/appetite_for_brains.txt +++ b/res/cardsfolder/a/appetite_for_brains.txt @@ -1,7 +1,7 @@ Name:Appetite for Brains ManaCost:B Types:Sorcery -A:SP$ ChangeZone | Cost$ B | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | ChangeType$ Card.cmcGE4 | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | SpellDescription$ Target opponent reveals his or her hand. You choose a card from it with converted mana cost 4 or greater and exile that card. +A:SP$ ChangeZone | Cost$ B | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card.cmcGE4 | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | StackDescription$ SpellDescription | SpellDescription$ Target opponent reveals his or her hand. You choose a card from it with converted mana cost 4 or greater and exile that card. SVar:Picture:http://www.wizards.com/global/images/magic/general/appetite_for_brains.jpg Oracle:Target opponent reveals his or her hand. You choose a card from it with converted mana cost 4 or greater and exile that card. SetInfo:AVR Uncommon \ No newline at end of file diff --git a/res/cardsfolder/a/astral_arena.txt b/res/cardsfolder/a/astral_arena.txt new file mode 100644 index 00000000000..02feea43cb2 --- /dev/null +++ b/res/cardsfolder/a/astral_arena.txt @@ -0,0 +1,10 @@ +Name:Astral Arena +ManaCost:no cost +Types:Plane Kolbahan +S:Mode$ Continuous | EffectZone$ Command | GlobalRule$ No more than one creature can attack each combat. | Description$ No more than one creature can attack each combat. +S:Mode$ Continuous | EffectZone$ Command | GlobalRule$ No more than one creature can block each combat. | Description$ No more than one creature can block each combat. +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, CARDNAME deals 2 damage to each creature. +SVar:RolledChaos:AB$ DamageAll | Cost$ 0 | NumDmg$ 2 | ValidCards$ Creature | ValidDescription$ each creature. +SVar:Picture:http://www.wizards.com/global/images/magic/general/astral_arena.jpg +Oracle:No more than one creature can attack each combat.\nNo more than one creature can block each combat.\nWhenever you roll {C}, Astral Arena deals 2 damage to each creature. +SetInfo:PC2 Common \ No newline at end of file diff --git a/res/cardsfolder/a/auriok_siege_sled.txt b/res/cardsfolder/a/auriok_siege_sled.txt index 9bd63b014e6..cc275b3de89 100644 --- a/res/cardsfolder/a/auriok_siege_sled.txt +++ b/res/cardsfolder/a/auriok_siege_sled.txt @@ -2,9 +2,8 @@ Name:Auriok Siege Sled ManaCost:6 Types:Artifact Creature Juggernaut PT:3/5 -A:AB$ Pump | Cost$ 1 | ValidTgts$ Creature.Artifact | TgtPrompt$ Select target artifact creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBCantblock | StackDescription$ None | SpellDescription$ Target artifact creature can't block CARDNAME this turn. -SVar:DBCantblock:DB$ Effect | Name$ Auriok Siege Sled Effect | RememberObjects$ Targeted | StaticAbilities$ STCantBlock | ImprintCards$ Self -SVar:STCantBlock:Mode$ Continuous | Affected$ Card.IsImprinted | EffectZone$ Command | AddHiddenKeyword$ CantBeBlockedBy Creature.IsRemembered | Description$ Auriok Siege Sled can't be blocked by target artifact creature. +A:AB$ Pump | Cost$ 1 | ValidTgts$ Creature.Artifact | TgtPrompt$ Select target artifact creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBPump | StackDescription$ {c:Targeted} can't block CARDNAME this turn. | SpellDescription$ Target artifact creature can't block CARDNAME this turn. +SVar:DBPump:DB$ Pump | KW$ HIDDEN CantBeBlockedBy Creature.IsRemembered | StackDescription$ None T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/res/cardsfolder/b/benthic_explorers.txt b/res/cardsfolder/b/benthic_explorers.txt new file mode 100644 index 00000000000..0fb41939ce8 --- /dev/null +++ b/res/cardsfolder/b/benthic_explorers.txt @@ -0,0 +1,8 @@ +Name:Benthic Explorers +ManaCost:3 U +Types:Creature Merfolk Scout +PT:2/4 +A:AB$ ManaReflected | Cost$ T untapYType<1/Land.OppCtrl/land> | ColorOrType$ Type | Valid$ Defined.Untapped | ReflectProperty$ Produce | SpellDescription$ Add one mana of any type that land could produce to your mana pool. +SVar:Picture:http://www.wizards.com/global/images/magic/general/benthic_explorers.jpg +Oracle:{T}, Untap a tapped land an opponent controls: Add one mana of any type that land could produce to your mana pool. +SetInfo:ALL Common x2 \ No newline at end of file diff --git a/res/cardsfolder/b/blazing_effigy.txt b/res/cardsfolder/b/blazing_effigy.txt new file mode 100644 index 00000000000..35e9602f6c2 --- /dev/null +++ b/res/cardsfolder/b/blazing_effigy.txt @@ -0,0 +1,17 @@ +Name:Blazing Effigy +ManaCost:1 R +Types:Creature Elemental +PT:0/3 +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ BlazeDmg | TriggerDescription$ When CARDNAME dies, it deals X damage to target creature, where X is 3 plus the amount of damage dealt to CARDNAME this turn by other sources named Blazing Effigy. +SVar:BlazeDmg:DB$ DealDamage | ValidTgts$ Creature | TgtPrompt$ Select target creature to deal damage to | NumDmg$ BlazeSize | References$ BlazeSize,Contributions | SubAbility$ TrigReset +T:Mode$ DamageDone | ValidSource$ Card.Other+namedBlazing Effigy | ValidTarget$ Card.Self | Execute$ StoreContribution | Static$ True +SVar:StoreContribution:DB$ StoreSVar | SVar$ Contributions | Type$ CountSVar | Expression$ Contributions/Plus.Blazed +T:Mode$ Phase | Phase$ Cleanup | Execute$ TrigReset | Static$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Ante,Library,Hand,Exile | ValidCard$ Card.Self | Execute$ TrigReset | Static$ True +SVar:TrigReset:DB$ StoreSVar | SVar$ Contributions | Type$ Number | Expression$ 0 +SVar:BlazeSize:SVar$Contributions/Plus.3 +SVar:Contributions:Number$0 +SVar:Blazed:TriggerCount$DamageAmount +SVar:Picture:http://www.wizards.com/global/images/magic/general/blazing_effigy.jpg +Oracle:When Blazing Effigy dies, it deals X damage to target creature, where X is 3 plus the amount of damage dealt to Blazing Effigy this turn by other sources named Blazing Effigy. +SetInfo:LEG Common \ No newline at end of file diff --git a/res/cardsfolder/b/bloodhill_bastion.txt b/res/cardsfolder/b/bloodhill_bastion.txt new file mode 100644 index 00000000000..452fc00a54d --- /dev/null +++ b/res/cardsfolder/b/bloodhill_bastion.txt @@ -0,0 +1,11 @@ +Name:Bloodhill Bastion +ManaCost:no cost +Types:Plane Equilor +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | TriggerZones$ Command | ValidCard$ Creature | Execute$ TrigPump | TriggerDescription$ Whenever a creature enters the battlefield, it gains double strike and haste until end of turn. +SVar:TrigPump:AB$ Pump | Cost$ 0 | Defined$ TriggeredCard | KW$ Double Strike & Haste +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, exile target nontoken creature you control, then return it to the battlefield under your control. +SVar:RolledChaos:AB$ ChangeZone | Cost$ 0 | ValidTgts$ Creature.nonToken+YouCtrl | TgtPrompt$ Select target non-Token creature you control | Origin$ Battlefield | Destination$ Exile | RememberTargets$ True | ForgetOtherTargets$ True | SubAbility$ RestorationReturn +SVar:RestorationReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | GainControl$ True +SVar:Picture:http://www.wizards.com/global/images/magic/general/bloodhill_bastion.jpg +Oracle:Whenever a creature enters the battlefield, it gains double strike and haste until end of turn.\nWhenever you roll {C}, exile target nontoken creature you control, then return it to the battlefield under your control. +SetInfo:PC2 Common \ No newline at end of file diff --git a/res/cardsfolder/b/bribery.txt b/res/cardsfolder/b/bribery.txt index 0026c3d0096..24fb758b0e4 100644 --- a/res/cardsfolder/b/bribery.txt +++ b/res/cardsfolder/b/bribery.txt @@ -1,7 +1,7 @@ Name:Bribery ManaCost:3 U U Types:Sorcery -A:SP$ ChangeZone | Cost$ 3 U U | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | ChangeType$ Creature | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | SpellDescription$ Search target opponent's library for a creature card and put that card onto the battlefield under your control. Then that player shuffles his or her library. +A:SP$ ChangeZone | Cost$ 3 U U | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | ChangeType$ Creature | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for a creature card and put that card onto the battlefield under your control. Then that player shuffles his or her library. SVar:Picture:http://resources.wizards.com/magic/cards/mm/en-us/card21300.jpg Oracle:Search target opponent's library for a creature card and put that card onto the battlefield under your control. Then that player shuffles his or her library. SetInfo:MMQ Rare diff --git a/res/cardsfolder/b/brilliant_ultimatum.txt b/res/cardsfolder/b/brilliant_ultimatum.txt index ca4db3d50fa..da052cd2587 100644 --- a/res/cardsfolder/b/brilliant_ultimatum.txt +++ b/res/cardsfolder/b/brilliant_ultimatum.txt @@ -1,7 +1,13 @@ Name:Brilliant Ultimatum ManaCost:W W U U U B B Types:Sorcery -Text:Exile the top five cards of your library. An opponent separates those cards into two piles. You may play any number of cards from one of those piles without paying their mana costs. +A:SP$ Mill | Cost$ W W U U U B B | Defined$ You | NumCards$ 5 | Destination$ Exile | RememberMilled$ True | SubAbility$ DBTwoPiles | SpellDescription$ Exile the top five cards of your library. An opponent separates those cards into two piles. You may play any number of cards from one of those piles without paying their mana costs. +SVar:DBTwoPiles:DB$ TwoPiles | Defined$ You | DefinedCards$ Remembered | Separator$ Opponent | ChosenPile$ DBPlay | SubAbility$ DBCleanup +SVar:DBPlay:DB$ RepeatEach | UseImprinted$ True | RepeatCards$ Card.IsRemembered | ChooseOrder$ True | Zone$ Exile | RepeatSubAbility$ DBPlayCard +SVar:DBPlayCard:DB$ Play | Defined$ Imprinted | Controller$ You | WithoutManaCost$ True | Optional$ True +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Y:Count$InYourLibrary +SVar:NeedsToPlayVar:Y GE8 SVar:Picture:http://www.wizards.com/global/images/magic/general/brilliant_ultimatum.jpg Oracle:Exile the top five cards of your library. An opponent separates those cards into two piles. You may play any number of cards from one of those piles without paying their mana costs. SetInfo:ALA Rare \ No newline at end of file diff --git a/res/cardsfolder/b/burning_tree_bloodscale.txt b/res/cardsfolder/b/burning_tree_bloodscale.txt index 6f215e6eb80..3de8a45ef5c 100644 --- a/res/cardsfolder/b/burning_tree_bloodscale.txt +++ b/res/cardsfolder/b/burning_tree_bloodscale.txt @@ -3,13 +3,13 @@ ManaCost:2 R G Types:Creature Viashino Berserker PT:2/2 K:Bloodthirst 1 -A:AB$ Pump | Cost$ 2 R | ValidTgts$ Creature | TgtPrompt$ Select target creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBCantblock | StackDescription$ None | SpellDescription$ Target creature can't block CARDNAME this turn. -SVar:DBCantblock:DB$ Effect | Name$ Burning-Tree Bloodscale Effect | RememberObjects$ Targeted | StaticAbilities$ STCantBlock | ImprintCards$ Self -SVar:STCantBlock:Mode$ Continuous | Affected$ Card.IsImprinted | EffectZone$ Command | AddHiddenKeyword$ CantBeBlockedBy Creature.IsRemembered | Description$ Burning-Tree Bloodscale can't be blocked by target creature. +A:AB$ Pump | Cost$ 2 R | ValidTgts$ Creature | TgtPrompt$ Select target creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBPump | StackDescription$ {c:Targeted} can't block CARDNAME this turn. | SpellDescription$ Target creature can't block CARDNAME this turn. +SVar:DBPump:DB$ Pump | KW$ HIDDEN CantBeBlockedBy Creature.IsRemembered | StackDescription$ None T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True A:AB$ MustBlock | Cost$ 2 G | ValidTgts$ Creature | TgtPrompt$ Select target creature that must block this creature this turn | SpellDescription$ Target creature blocks CARDNAME this turn if able. +SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/burning_tree_bloodscale.jpg Oracle:Bloodthirst 1 (If an opponent was dealt damage this turn, this creature enters the battlefield with a +1/+1 counter on it.)\n{2}{R}: Target creature can't block Burning-Tree Bloodscale this turn.\n{2}{G}: Target creature blocks Burning-Tree Bloodscale this turn if able. SetInfo:GPT Common \ No newline at end of file diff --git a/res/cardsfolder/c/castigate.txt b/res/cardsfolder/c/castigate.txt index b8c90e3bbab..38105fcc76d 100644 --- a/res/cardsfolder/c/castigate.txt +++ b/res/cardsfolder/c/castigate.txt @@ -1,7 +1,7 @@ Name:Castigate ManaCost:W B Types:Sorcery -A:SP$ ChangeZone | Cost$ W B | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | SpellDescription$ Target opponent reveals his or her hand. You choose a nonland card from it and exile that card. +A:SP$ ChangeZone | Cost$ W B | Origin$ Hand | Destination$ Exile | DefinedPlayer$ Targeted | ValidTgts$ Opponent | Chooser$ You | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | StackDescription$ SpellDescription | SpellDescription$ Target opponent reveals his or her hand. You choose a nonland card from it and exile that card. SVar:Picture:http://www.wizards.com/global/images/magic/general/castigate.jpg Oracle:Target opponent reveals his or her hand. You choose a nonland card from it and exile that card. SetInfo:GPT Common \ No newline at end of file diff --git a/res/cardsfolder/c/cleansing.txt b/res/cardsfolder/c/cleansing.txt index d24016ad157..b5f31778bac 100644 --- a/res/cardsfolder/c/cleansing.txt +++ b/res/cardsfolder/c/cleansing.txt @@ -2,7 +2,7 @@ Name:Cleansing ManaCost:W W W Types:Sorcery A:SP$ RepeatEach | Cost$ W W W | RepeatSubAbility$ DBSac | RepeatCards$ Land | SpellDescription$ For each land, destroy that land unless any player pays 1 life. -SVar:DBSac:DB$ Destroy | UnlessCost$ PayLife<1> | UnlessPayer$ Player | Defined$ Remembered +SVar:DBSac:DB$ Destroy | UnlessCost$ PayLife<1> | UnlessPayer$ Player | UnlessAI$ DefinedRememberedController | Defined$ Remembered SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/cleansing.jpg Oracle:For each land, destroy that land unless any player pays 1 life. diff --git a/res/cardsfolder/c/comet_storm.txt b/res/cardsfolder/c/comet_storm.txt new file mode 100644 index 00000000000..b77e23e7078 --- /dev/null +++ b/res/cardsfolder/c/comet_storm.txt @@ -0,0 +1,11 @@ +Name:Comet Storm +ManaCost:X R R +Types:Instant +A:SP$ DealDamage | Cost$ X R R | Announce$ Multikicker,X | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player | NumDmg$ X | TargetMin$ TargetsNum | TargetMax$ TargetsNum | References$ X,TargetsNum | SpellDescription$ CARDNAME deals X damage to each target creature and/or player. +K:Multikicker 1 +SVar:TargetsNum:Count$TimesKicked/Plus.1 +SVar:Picture:http://www.wizards.com/global/images/magic/general/comet_storm.jpg +SVar:X:Count$xPaid +Oracle:Multikicker {1} (You may pay an additional {1} any number of times as you cast this spell.)\nChoose target creature or player, then choose another target creature or player for each time Comet Storm was kicked. Comet Storm deals X damage to each of them. +SetInfo:COM Mythic +SetInfo:WWK Mythic \ No newline at end of file diff --git a/res/cardsfolder/c/consume_spirit.txt b/res/cardsfolder/c/consume_spirit.txt index a6b7b2cfc31..1aba348946c 100644 --- a/res/cardsfolder/c/consume_spirit.txt +++ b/res/cardsfolder/c/consume_spirit.txt @@ -1,7 +1,7 @@ Name:Consume Spirit ManaCost:X 1 B Types:Sorcery -A:SP$DealDamage | Cost$ X 1 B | XColor$ B | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player | NumDmg$ X | SubAbility$ DBGainLife | References$ X | SpellDescription$ Spend only black mana on X. Consume Spirit deals X damage to target creature or player and you gain X life. +A:SP$DealDamage | Cost$ X 1 B | XColor$ B | Announce$ X | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player | NumDmg$ X | SubAbility$ DBGainLife | References$ X | SpellDescription$ Spend only black mana on X. Consume Spirit deals X damage to target creature or player and you gain X life. SVar:DBGainLife:DB$GainLife | Defined$ You | LifeAmount$ X | References$ X SVar:X:Count$xPaid SVar:RemAIDeck:True diff --git a/res/cardsfolder/c/crag_puca.txt b/res/cardsfolder/c/crag_puca.txt index 1558912759f..182506d73ab 100644 --- a/res/cardsfolder/c/crag_puca.txt +++ b/res/cardsfolder/c/crag_puca.txt @@ -3,6 +3,7 @@ ManaCost:UR UR UR Types:Creature Shapeshifter PT:2/4 A:AB$ Pump | Cost$ UR | KW$ HIDDEN CARDNAME's power and toughness are switched | SpellDescription$ Switch CARDNAME's power and toughness until end of turn. +SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/crag_puca.jpg Oracle:{U/R}: Switch Crag Puca's power and toughness until end of turn. SetInfo:EVE Uncommon \ No newline at end of file diff --git a/res/cardsfolder/c/curtain_of_light.txt b/res/cardsfolder/c/curtain_of_light.txt new file mode 100644 index 00000000000..56a53e94cd7 --- /dev/null +++ b/res/cardsfolder/c/curtain_of_light.txt @@ -0,0 +1,9 @@ +Name:Curtain of Light +ManaCost:1 W +Types:Instant +Text:Cast CARDNAME only during combat after blockers are declared. +A:SP$ BecomesBlocked | Cost$ 1 W | ValidTgts$ Creature.attacking+unblocked | TgtPrompt$ Select target unblocked attacking creature | SubAbility$ Draw | ActivationPhases$ Declare Blockers - Play Instants and Abilities->EndCombat | SpellDescription$ Target unblocked attacking creature becomes blocked. (This spell works on unblockable creatures.) Draw a card. +SVar:Draw:DB$ Draw | NumCards$ 1 +SVar:Picture:http://www.wizards.com/global/images/magic/general/curtain_of_light.jpg +Oracle:Cast Curtain of Light only during combat after blockers are declared.\nTarget unblocked attacking creature becomes blocked. (This spell works on unblockable creatures.)\nDraw a card. +SetInfo:SOK Common \ No newline at end of file diff --git a/res/cardsfolder/c/cut_the_tethers.txt b/res/cardsfolder/c/cut_the_tethers.txt new file mode 100644 index 00000000000..2b9898afcae --- /dev/null +++ b/res/cardsfolder/c/cut_the_tethers.txt @@ -0,0 +1,8 @@ +Name:Cut the Tethers +ManaCost:2 U U +Types:Sorcery +A:SP$ RepeatEach | Cost$ 2 U U | RepeatSubAbility$ DBReturn | RepeatCards$ Creature.Spirit | SpellDescription$ For each Spirit, return it to its owner's hand unless that player pays 3. +SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Battlefield | Destination$ Hand | UnlessCost$ 3 | UnlessPayer$ RememberedController +SVar:Picture:http://www.wizards.com.sixxs.org/global/images/magic/general/cut_the_tethers.jpg +Oracle:For each Spirit, return it to its owner's hand unless that player pays {3}. +SetInfo:CHK Uncommon \ No newline at end of file diff --git a/res/cardsfolder/d/dazzling_beauty.txt b/res/cardsfolder/d/dazzling_beauty.txt new file mode 100644 index 00000000000..3a2df04d736 --- /dev/null +++ b/res/cardsfolder/d/dazzling_beauty.txt @@ -0,0 +1,9 @@ +Name:Dazzling Beauty +ManaCost:2 W +Types:Instant +Text:Cast CARDNAME only during combat after blockers are declared. +A:SP$ BecomesBlocked | Cost$ 2 W | ValidTgts$ Creature.attacking+unblocked | TgtPrompt$ Select target unblocked attacking creature | SubAbility$ Draw | ActivationPhases$ Declare Blockers - Play Instants and Abilities->EndCombat | SpellDescription$ Target unblocked attacking creature becomes blocked. (This spell works on unblockable creatures.) Draw a card at the beginning of the next turn's upkeep. +SVar:Draw:DB$ Draw | NumCards$ 1 | NextUpkeep$ True +SVar:Picture:http://www.wizards.com/global/images/magic/general/dazzling_beauty.jpg +Oracle:Cast Dazzling Beauty only during the declare blockers step.\nTarget unblocked attacking creature becomes blocked. (This spell works on unblockable creatures.)\nDraw a card at the beginning of the next turn's upkeep. +SetInfo:MIR Common \ No newline at end of file diff --git a/res/cardsfolder/d/delay.txt b/res/cardsfolder/d/delay.txt new file mode 100644 index 00000000000..27e2db89cac --- /dev/null +++ b/res/cardsfolder/d/delay.txt @@ -0,0 +1,10 @@ +Name:Delay +ManaCost:1 U +Types:Instant +A:SP$ Counter | Cost$ 1 U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | RememberCountered$ True | Destination$ Exile | SubAbility$ DBPutCounter | SpellDescription$ Counter target spell. If the spell is countered this way, exile it with three time counters on it instead of putting it into its owner's graveyard. If it doesn't have suspend, it gains suspend. (At the beginning of its owner's upkeep, remove a counter from that card. When the last is removed, the player plays it without paying its mana cost. If it's a creature, it has haste.) +SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterNum$ 3 | CounterType$ TIME | SubAbility$ DBPump +SVar:DBPump:DB$ PumpAll | ValidCards$ Card.IsRemembered+withoutSuspend | PumpZone$ Exile | KW$ Suspend | Permanent$ True | SubAbility$ DBCleanup | StackDescription$ If it doesn't have suspend, it gains suspend. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Picture:http://www.wizards.com/global/images/magic/general/delay.jpg +Oracle:Counter target spell. If the spell is countered this way, exile it with three time counters on it instead of putting it into its owner's graveyard. If it doesn't have suspend, it gains suspend. (At the beginning of its owner's upkeep, remove a counter from that card. When the last is removed, the player plays it without paying its mana cost. If it's a creature, it has haste.) +SetInfo:FUT Uncommon \ No newline at end of file diff --git a/res/cardsfolder/d/duct_crawler.txt b/res/cardsfolder/d/duct_crawler.txt index eafb15e634b..dfae84eaf1e 100644 --- a/res/cardsfolder/d/duct_crawler.txt +++ b/res/cardsfolder/d/duct_crawler.txt @@ -2,6 +2,11 @@ Name:Duct Crawler ManaCost:R Types:Creature Insect PT:1/1 +A:AB$ Pump | Cost$ 1 R | ValidTgts$ Creature | TgtPrompt$ Select target creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBPump | StackDescription$ {c:Targeted} can't block CARDNAME this turn. | SpellDescription$ Target creature can't block CARDNAME this turn. +SVar:DBPump:DB$ Pump | KW$ HIDDEN CantBeBlockedBy Creature.IsRemembered | StackDescription$ None +T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/duct_crawler.jpg Oracle:{1}{R}: Target creature can't block Duct Crawler this turn. diff --git a/res/cardsfolder/e/eight_and_a_half_tails_avatar.txt b/res/cardsfolder/e/eight_and_a_half_tails_avatar.txt new file mode 100644 index 00000000000..bc50216bca7 --- /dev/null +++ b/res/cardsfolder/e/eight_and_a_half_tails_avatar.txt @@ -0,0 +1,8 @@ +Name:Eight-and-a-Half-Tails Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+2/-3 +A:AB$ Pump | Cost$ 1 | ActivationZone$ Command | ValidTgts$ Permanent.YouCtrl | TgtPrompt$ Select target permanent you control | KW$ Protection from red & Protection from blue & Protection from black & Protection from white & Protection from green | RandomKeyword$ True | NoRepetition$ True | StackDescription$ SpellDescription | SpellDescription$ Until end of turn, target permanent you control gains protection from a color chosen at random from colors it doesn't have protection from. +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/eight_and_a_half_tails_avatar.jpg +Oracle:Hand +2, life -3\n{1}: Until end of turn, target permanent you control gains protection from a color chosen at random from colors it doesn't have protection from. +SetInfo:VAN Special \ No newline at end of file diff --git a/res/cardsfolder/e/ensouled_scimitar.txt b/res/cardsfolder/e/ensouled_scimitar.txt index 772d7f39083..d46ef558847 100644 --- a/res/cardsfolder/e/ensouled_scimitar.txt +++ b/res/cardsfolder/e/ensouled_scimitar.txt @@ -3,7 +3,7 @@ ManaCost:3 Types:Artifact Equipment K:Equip 2 A:AB$ Animate | Cost$ 3 | Defined$ Self | Power$ 1 | Toughness$ 5 | Types$ Creature,Artifact,Spirit | Keywords$ Flying | OverwriteTypes$ True | SpellDescription$ CARDNAME becomes a 1/5 Spirit artifact creature with flying until end of turn. (Equipment that's a creature can't equip a creature.) -S:Mode$ Continuous | Affected$ Card.EquippedBy | IsPresent$ Card.Self+nonCreature | AddPower$ 1 | AddToughness$ 5 | Description$ Equipped creature gets +1/+5. +S:Mode$ Continuous | Affected$ Card.EquippedBy | AddPower$ 1 | AddToughness$ 5 | Description$ Equipped creature gets +1/+5. SVar:Picture:http://www.wizards.com/global/images/magic/general/ensouled_scimitar.jpg Oracle:{3}: Ensouled Scimitar becomes a 1/5 Spirit artifact creature with flying until end of turn. (Equipment that's a creature can't equip a creature.)\nEquipped creature gets +1/+5.\nEquip {2} ({2}: Attach to target creature you control. Equip only as a sorcery.) SetInfo:5DN Uncommon \ No newline at end of file diff --git a/res/cardsfolder/e/entreat_the_angels.txt b/res/cardsfolder/e/entreat_the_angels.txt index e2829e377d1..ec651773ad5 100644 --- a/res/cardsfolder/e/entreat_the_angels.txt +++ b/res/cardsfolder/e/entreat_the_angels.txt @@ -1,7 +1,7 @@ Name:Entreat the Angels ManaCost:X X W W W Types:Sorcery -A:SP$ Token | Cost$ X X W W W | TokenAmount$ X | TokenName$ Angel | TokenTypes$ Creature,Angel | TokenOwner$ You | TokenColors$ White | TokenPower$ 4 | TokenToughness$ 4 | TokenKeywords$ Flying | TokenImage$ w 4 4 angel avr | References$ X | SpellDescription$ Put X 4/4 white Angel creature tokens with flying onto the battlefield. +A:SP$ Token | Cost$ X X W W W | Announce$ X | TokenAmount$ X | TokenName$ Angel | TokenTypes$ Creature,Angel | TokenOwner$ You | TokenColors$ White | TokenPower$ 4 | TokenToughness$ 4 | TokenKeywords$ Flying | TokenImage$ w 4 4 angel avr | References$ X | SpellDescription$ Put X 4/4 white Angel creature tokens with flying onto the battlefield. K:Miracle:X W W SVar:X:Count$xPaid SVar:Picture:http://www.wizards.com/global/images/magic/general/entreat_the_angels.jpg diff --git a/res/cardsfolder/e/eternal_dominion.txt b/res/cardsfolder/e/eternal_dominion.txt index 6298f313d8a..66ab16604bc 100644 --- a/res/cardsfolder/e/eternal_dominion.txt +++ b/res/cardsfolder/e/eternal_dominion.txt @@ -2,7 +2,7 @@ Name:Eternal Dominion ManaCost:7 U U U Types:Sorcery K:Epic -A:SP$ ChangeZone | Cost$ 7 U U U | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | ChangeType$ Artifact,Creature,Enchantment,Land | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | SpellDescription$ Search target opponent's library for a creature card and put that card onto the battlefield under your control. Then that player shuffles his or her library. +A:SP$ ChangeZone | Cost$ 7 U U U | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | ChangeType$ Artifact,Creature,Enchantment,Land | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for a creature card and put that card onto the battlefield under your control. Then that player shuffles his or her library. SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/eternal_dominion.jpg Oracle:Search target opponent's library for an artifact, creature, enchantment, or land card. Put that card onto the battlefield under your control. Then that player shuffles his or her library.\nEpic (For the rest of the game, you can't cast spells. At the beginning of each of your upkeeps, copy this spell except for its epic ability. You may choose a new target for the copy.) diff --git a/res/cardsfolder/f/fallow_wurm.txt b/res/cardsfolder/f/fallow_wurm.txt index d483aa87ceb..87b6046c263 100644 --- a/res/cardsfolder/f/fallow_wurm.txt +++ b/res/cardsfolder/f/fallow_wurm.txt @@ -2,11 +2,8 @@ Name:Fallow Wurm ManaCost:2 G Types:Creature Wurm PT:4/4 -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDiscard | TriggerDescription$ When CARDNAME enters the battlefield, sacrifice it unless you discard a land card. -SVar:TrigDiscard:AB$ Discard | Cost$ 0 | NumCards$ 1 | DiscardValid$ Land | Mode$ TgtChoose | Optional$ True | RememberDiscarded$ True | SubAbility$ DBSacSelf -SVar:DBSacSelf:DB$ Sacrifice | Cost$ 0 | Defined$ Self | SubAbility$ DBCleanup | ConditionCheckSVar$ X | ConditionSVarCompare$ LT1 | References$ X -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Remembered$Amount +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBSacSelf | TriggerDescription$ When CARDNAME enters the battlefield, sacrifice it unless you discard a land card. +SVar:DBSacSelf:DB$ Sacrifice | Defined$ Self | UnlessCost$ Discard<1/Land> | UnlessPayer$ You SVar:NeedsToPlayVar:Y GE1 SVar:Y:Count$TypeInYourHand.Land SVar:Picture:http://www.wizards.com/global/images/magic/general/fallow_wurm.jpg diff --git a/res/cardsfolder/f/fold_into_aether.txt b/res/cardsfolder/f/fold_into_aether.txt new file mode 100644 index 00000000000..ba33f8e2617 --- /dev/null +++ b/res/cardsfolder/f/fold_into_aether.txt @@ -0,0 +1,9 @@ +Name:Fold into AEther +ManaCost:2 U U +Types:Instant +A:SP$ Counter | Cost$ 2 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | RememberCountered$ True | SubAbility$ DBChangeZone | SpellDescription$ Counter target spell. If that spell is countered this way, its controller may put a creature card from his or her hand onto the battlefield. +SVar:DBChangeZone:DB$ ChangeZone | ChangeType$ Creature | Origin$ Hand | Destination$ Battlefield | ChangeNum$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Picture:http://www.wizards.com/global/images/magic/general/fold_into_aether.jpg +Oracle:Counter target spell. If that spell is countered this way, its controller may put a creature card from his or her hand onto the battlefield. +SetInfo:5DN Uncommon \ No newline at end of file diff --git a/res/cardsfolder/g/gavony.txt b/res/cardsfolder/g/gavony.txt new file mode 100644 index 00000000000..084f42ebde9 --- /dev/null +++ b/res/cardsfolder/g/gavony.txt @@ -0,0 +1,9 @@ +Name:Gavony +ManaCost:no cost +Types:Plane Innistrad +S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature | AddKeyword$ Vigilance | Description$ All creatures have vigilance. +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, creatures you control are indestructible this turn. +SVar:RolledChaos:AB$ PumpAll | Cost$ 0 | ValidCards$ Creature.YouCtrl | KW$ HIDDEN Indestructible +SVar:Picture:http://www.wizards.com/global/images/magic/general/gavony.jpg +Oracle:All creatures have vigilance.\nWhenever you roll {C}, creatures you control are indestructible this turn. +SetInfo:PC2 Common \ No newline at end of file diff --git a/res/cardsfolder/g/ghastlord_of_fugue.txt b/res/cardsfolder/g/ghastlord_of_fugue.txt index 4692ed1927b..047d496ca0a 100644 --- a/res/cardsfolder/g/ghastlord_of_fugue.txt +++ b/res/cardsfolder/g/ghastlord_of_fugue.txt @@ -4,7 +4,7 @@ Types:Creature Spirit Avatar PT:4/4 K:Unblockable T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigChangeZone | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player reveals his or her hand. You choose a card from it. That player exiles that card. -SVar:TrigChangeZone:AB$ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Exile | DefinedPlayer$ TriggeredTarget | Chooser$ You | ChangeType$ Card | ChangeNum$ 1 +SVar:TrigChangeZone:AB$ ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Exile | DefinedPlayer$ TriggeredTarget | Chooser$ You | ChangeType$ Card | ChangeNum$ 1 SVar:Picture:http://www.wizards.com/global/images/magic/general/ghastlord_of_fugue.jpg Oracle:Ghastlord of Fugue is unblockable.\nWhenever Ghastlord of Fugue deals combat damage to a player, that player reveals his or her hand. You choose a card from it. That player exiles that card. SetInfo:SHM Rare \ No newline at end of file diff --git a/res/cardsfolder/h/harsh_mercy.txt b/res/cardsfolder/h/harsh_mercy.txt new file mode 100644 index 00000000000..f44848ac79f --- /dev/null +++ b/res/cardsfolder/h/harsh_mercy.txt @@ -0,0 +1,12 @@ +Name:Harsh Mercy +ManaCost:2 W +Types:Sorcery +A:SP$ RepeatEach | Cost$ 2 W | RepeatPlayers$ Player | RepeatSubAbility$ DBChooseType | SubAbility$ DBDestroyAll | StackDescription$ SpellDescription | SpellDescription$ Each player chooses a creature type. Destroy all creatures that aren't of a type chosen this way. They can't be regenerated. +SVar:DBChooseType:DB$ ChooseType | Defined$ Player.IsRemembered | Type$ Creature | AILogic$ MostProminentComputerControls | SubAbility$ DBRemember +SVar:DBRemember:DB$ PumpAll | ValidCards$ Creature.ChosenType | RememberAllPumped$ True +SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Creature.IsNotRemembered | NoRegen$ True | SubAbility$ DBCleanup | StackDescription$ None +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/harsh_mercy.jpg +Oracle:Each player chooses a creature type. Destroy all creatures that aren't of a type chosen this way. They can't be regenerated. +SetInfo:ONS Rare \ No newline at end of file diff --git a/res/cardsfolder/h/hedron_fields_of_agadeem.txt b/res/cardsfolder/h/hedron_fields_of_agadeem.txt new file mode 100644 index 00000000000..42e770b6a9e --- /dev/null +++ b/res/cardsfolder/h/hedron_fields_of_agadeem.txt @@ -0,0 +1,9 @@ +Name:Hedron Fields of Agadeem +ManaCost:no cost +Types:Plane Zendikar +S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.powerGE7 | AddHiddenKeyword$ CARDNAME can't attack or block. | Description$ Creatures with power 7 or greater can't attack or block. +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, put a 7/7 colorless Eldrazi creature token with annihilator 1 onto the battlefield. (Whenever it attacks, defending player sacrifices a permanent.) +SVar:RolledChaos:AB$ Token | Cost$ 0 | TokenAmount$ 1 | TokenName$ Eldrazi | TokenTypes$ Creature,Eldrazi | TokenOwner$ You | TokenColors$ Colorless | TokenPower$ 7 | TokenToughness$ 7 | TokenKeywords$ Annihilator 1 +SVar:Picture:http://www.wizards.com/global/images/magic/general/hedron_fields_of_agadeem.jpg +Oracle:Creatures with power 7 or greater can't attack or block.\nWhenever you roll {C}, put a 7/7 colorless Eldrazi creature token with annihilator 1 onto the battlefield. (Whenever it attacks, defending player sacrifices a permanent.) +SetInfo:PC2 Common \ No newline at end of file diff --git a/res/cardsfolder/h/heroism.txt b/res/cardsfolder/h/heroism.txt new file mode 100644 index 00000000000..6c626fbaa5c --- /dev/null +++ b/res/cardsfolder/h/heroism.txt @@ -0,0 +1,10 @@ +Name:Heroism +ManaCost:2 W +Types:Enchantment +A:AB$ RepeatEach | Cost$ Sac<1/Creature.White/White Creature> | RepeatCards$ Creature.attacking+Red | RepeatSubAbility$ DBPump | SpellDescription$ For each attacking red creature, prevent all combat damage that would be dealt by that creature this turn unless its controller pays 2 R. +SVar:DBPump:DB$ Pump | Defined$ Remembered | KW$ HIDDEN Prevent all combat damage that would be dealt by CARDNAME. | UnlessCost$ 2 R | UnlessPayer$ RememberedController +SVar:RemRandomDeck:True +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/heroism.jpg +Oracle:Sacrifice a white creature: For each attacking red creature, prevent all combat damage that would be dealt by that creature this turn unless its controller pays {2}{R}. +SetInfo:FEM Uncommon \ No newline at end of file diff --git a/res/cardsfolder/h/hide_seek.txt b/res/cardsfolder/h/hide_seek.txt index c5a0d04cdb1..521c315ae8f 100644 --- a/res/cardsfolder/h/hide_seek.txt +++ b/res/cardsfolder/h/hide_seek.txt @@ -12,7 +12,7 @@ ALTERNATE Name:Seek ManaCost:W B Types:Instant -A:SP$ ChangeZone | Cost$ W B | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | Origin$ Library | DefinedPlayer$ Targeted | Chooser$ You | Destination$ Exile | Changetype$ Card | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBGainLife | SpellDescription$ Search target opponent's library for a card and exile it. You gain life equal to its converted mana cost. Then that player shuffles his or her library. +A:SP$ ChangeZone | Cost$ W B | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | Origin$ Library | DefinedPlayer$ Targeted | Chooser$ You | Destination$ Exile | Changetype$ Card | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBGainLife | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for a card and exile it. You gain life equal to its converted mana cost. Then that player shuffles his or her library. SVar:DBGainLife:DB$ GainLife | LifeAmount$ X | References$ X | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Remembered$CardManaCost diff --git a/res/cardsfolder/h/hinder.txt b/res/cardsfolder/h/hinder.txt new file mode 100644 index 00000000000..807dbbd58a0 --- /dev/null +++ b/res/cardsfolder/h/hinder.txt @@ -0,0 +1,7 @@ +Name:Hinder +ManaCost:1 U U +Types:Instant +A:SP$ Counter | Cost$ 1 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | DestinationChoice$ BottomOfLibrary,TopOfLibrary | SpellDescription$ Counter target spell. If that spell is countered this way, put that card on the top or bottom of its owner's library instead of into that player's graveyard. +SVar:Picture:http://www.wizards.com/global/images/magic/general/hinder.jpg +Oracle:Counter target spell. If that spell is countered this way, put that card on the top or bottom of its owner's library instead of into that player's graveyard. +SetInfo:CHK Uncommon \ No newline at end of file diff --git a/res/cardsfolder/j/jhoira_of_the_ghitu_avatar.txt b/res/cardsfolder/j/jhoira_of_the_ghitu_avatar.txt new file mode 100644 index 00000000000..ea5b76077e2 --- /dev/null +++ b/res/cardsfolder/j/jhoira_of_the_ghitu_avatar.txt @@ -0,0 +1,9 @@ +Name:Jhoira of the Ghitu Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+1/+0 +A:AB$ Play | Cost$ 3 Discard<1/Card> | ActivationZone$ Command | AnySupportedCard$ Instant | RandomCopied$ True | RandomNum$ 3 | ChoiceNum$ 1 | CopyCard$ True | WithoutManaCost$ True | SpellDescription$ Copy three instant cards chosen at random. You may cast one of the copies without paying its mana cost. +A:AB$ Play | Cost$ 3 Discard<1/Card> | ActivationZone$ Command | AnySupportedCard$ Sorcery | RandomCopied$ True | RandomNum$ 3 | ChoiceNum$ 1 | CopyCard$ True | WithoutManaCost$ True | SorcerySpeed$ True | SpellDescription$ Copy three sorcery cards chosen at random. You may cast one of the copies without paying its mana cost. Activate this ability only any time you could cast a sorcery. +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/jhoira_of_the_ghitu_avatar.jpg +Oracle:Hand +1, life +0\n{3}, Discard a card: Copy three instant cards chosen at random. You may cast one of the copies without paying its mana cost.\n{3}, Discard a card: Copy three sorcery cards chosen at random. You may cast one of the copies without paying its mana cost. Activate this ability only any time you could cast a sorcery. +SetInfo:VAN Special \ No newline at end of file diff --git a/res/cardsfolder/j/jihad.txt b/res/cardsfolder/j/jihad.txt index f5e7aef6286..c0a22479a43 100644 --- a/res/cardsfolder/j/jihad.txt +++ b/res/cardsfolder/j/jihad.txt @@ -4,9 +4,10 @@ Types:Enchantment K:ETBReplacement:Other:ChooseColor SVar:ChooseColor:DB$ ChooseColor | Defined$ You | SubAbility$ ChooseP | SpellDescription$ As CARDNAME enters the battlefield, choose a color and an opponent. SVar:ChooseP:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent -S:Mode$ Continuous | Affected$ Creature.White | AddPower$ 2 | AddToughness$ 1 | IsPresent$ Permanent.nontoken+ChosenColor+YouDontCtrl | Description$ White creatures get +2/+1 as long as the chosen player controls a nontoken permanent of the chosen color. -T:Mode$ Always | TriggerZones$ Battlefield | IsPresent$ Permanent.nontoken+ChosenColor+YouDontCtrl | PresentCompare$ EQ0 | Execute$ TrigSac | TriggerDescription$ When the chosen player controls no nontoken permanents of the chosen color, sacrifice CARDNAME. +S:Mode$ Continuous | Affected$ Creature.White | AddPower$ 2 | AddToughness$ 1 | CheckSVar$ X | Description$ White creatures get +2/+1 as long as the chosen player controls a nontoken permanent of the chosen color. +T:Mode$ Always | TriggerZones$ Battlefield | IsPresent$ Permanent.nontoken+ChosenColor+ChosenCtrl | PresentCompare$ EQ0 | Execute$ TrigSac | TriggerDescription$ When the chosen player controls no nontoken permanents of the chosen color, sacrifice CARDNAME. SVar:TrigSac:AB$ Sacrifice | Cost$ 0 | Defined$ Self +SVar:X:Count$Valid Permanent.nontoken+ChosenColor+ChosenCtrl SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/jihad.jpg Oracle:As Jihad enters the battlefield, choose a color and an opponent.\nWhite creatures get +2/+1 as long as the chosen player controls a nontoken permanent of the chosen color.\nWhen the chosen player controls no nontoken permanents of the chosen color, sacrifice Jihad. diff --git a/res/cardsfolder/k/kaboom!.txt b/res/cardsfolder/k/kaboom!.txt new file mode 100644 index 00000000000..301bc75535a --- /dev/null +++ b/res/cardsfolder/k/kaboom!.txt @@ -0,0 +1,13 @@ +Name:Kaboom! +ManaCost:4 R +Types:Sorcery +A:SP$ RepeatEach | Cost$ 4 R | ValidTgts$ Player | TargetMin$ 0 | TargetMax$ Maxplayer | References$ Maxplayer | RepeatPlayers$ Targeted | RepeatSubAbility$ DBDigUntil | StackDescription$ SpellDescription | SpellDescription$ Choose any number of target players. For each of those players, reveal cards from the top of your library until you reveal a nonland card. Kaboom deals damage equal to that card's converted mana cost to that player, then you put the revealed cards on the bottom of your library in any order. +SVar:DBDigUntil:DB$ DigUntil | Defined$ You | Valid$ Card.nonLand | ValidDescription$ nonland card | FoundDestination$ Library | FoundLibraryPosition$ -1 | RevealedDestination$ Library | RevealedLibraryPosition$ -1 | RememberFound$ True | SubAbility$ DBDmg +SVar:DBDmg:DB$ DealDamage | Defined$ Player.IsRemembered | NumDmg$ X | References$ X | SubAbility$ DBCleanup +SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True +SVar:X:Remembered$CardManaCost +SVar:Maxplayer:PlayerCountPlayers$Amount +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/kaboom!.jpg +Oracle:Choose any number of target players. For each of those players, reveal cards from the top of your library until you reveal a nonland card. Kaboom deals damage equal to that card's converted mana cost to that player, then you put the revealed cards on the bottom of your library in any order. +SetInfo:ONS Rare \ No newline at end of file diff --git a/res/cardsfolder/k/kamahls_summons.txt b/res/cardsfolder/k/kamahls_summons.txt new file mode 100644 index 00000000000..25dc5ebec89 --- /dev/null +++ b/res/cardsfolder/k/kamahls_summons.txt @@ -0,0 +1,15 @@ +Name:Kamahl's Summons +ManaCost:3 G +Types:Sorcery +A:SP$ RepeatEach | Cost$ 3 G | RepeatPlayers$ Player | RepeatSubAbility$ DBChoose | StackDescription$ SpellDescription | SubAbility$ DBRepeatToken | SpellDescription$ Each player may reveal any number of creature cards from his or her hand. Then each player puts a 2/2 green Bear creature token onto the battlefield for each card he or she revealed this way. +SVar:DBChoose:DB$ Reveal | Defined$ Player.IsRemembered | AnyNumber$ True | RevealValid$ Creature | RememberRevealed$ True +SVar:DBRepeatToken:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBToken | SubAbility$ DBCleanup +SVar:DBToken:DB$ Token | TokenAmount$ X | References$ X | TokenName$ Bear | TokenTypes$ Creature,Bear | TokenOwner$ Player.IsRemembered | TokenColors$ Green | TokenPower$ 2 | TokenToughness$ 2 +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Count$ValidHand Card.IsRemembered+RememberedPlayerCtrl +SVar:NeedsToPlayVar:Y GE3 +SVar:Y:Count$ValidHand Creature.YouCtrl +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/kamahls_summons.jpg +Oracle:Each player may reveal any number of creature cards from his or her hand. Then each player puts a 2/2 green Bear creature token onto the battlefield for each card he or she revealed this way. +SetInfo:ONS Uncommon \ No newline at end of file diff --git a/res/cardsfolder/k/karma.txt b/res/cardsfolder/k/karma.txt index 80220eb9e90..33ddcbfe78f 100644 --- a/res/cardsfolder/k/karma.txt +++ b/res/cardsfolder/k/karma.txt @@ -1,7 +1,9 @@ Name:Karma ManaCost:2 W W Types:Enchantment -Text:At the beginning of each player's upkeep, Karma deals damage to that player equal to the number of Swamps he or she controls. +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigDamage | TriggerDescription$ At the beginning of each player's upkeep, CARDNAME deals damage to that player equal to the number of Swamps he or she controls. +SVar:TrigDamage:AB$ DealDamage | Cost$ 0 | Defined$ TriggeredPlayer | NumDmg$ X | References$ X +SVar:X:Count$Valid Swamp.ActivePlayerCtrl SVar:RemRandomDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/karma.jpg Oracle:At the beginning of each player's upkeep, Karma deals damage to that player equal to the number of Swamps he or she controls. diff --git a/res/cardsfolder/k/karmic_justice.txt b/res/cardsfolder/k/karmic_justice.txt new file mode 100644 index 00000000000..009a27a3e9e --- /dev/null +++ b/res/cardsfolder/k/karmic_justice.txt @@ -0,0 +1,8 @@ +Name:Karmic Justice +ManaCost:2 W +Types:Enchantment +T:Mode$ Destroyed | ValidCauser$ Player.Opponent | ValidCard$ Permanent.nonCreature+YouCtrl | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigDestroy | TriggerDescription$ Whenever a spell or ability an opponent controls destroys a noncreature permanent you control, you may destroy target permanent that opponent controls. +SVar:TrigDestroy:AB$ Destroy | Cost$ 0 | ValidTgts$ Permanent | TargetsWithDefinedController$ TriggeredCauser +SVar:Picture:http://www.wizards.com/global/images/magic/general/karmic_justice.jpg +Oracle:Whenever a spell or ability an opponent controls destroys a noncreature permanent you control, you may destroy target permanent that opponent controls. +SetInfo:ODY Rare \ No newline at end of file diff --git a/res/cardsfolder/k/karona_false_god_avatar.txt b/res/cardsfolder/k/karona_false_god_avatar.txt new file mode 100644 index 00000000000..bccf8b36ab2 --- /dev/null +++ b/res/cardsfolder/k/karona_false_god_avatar.txt @@ -0,0 +1,12 @@ +Name:Karona, False God Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:-1/+8 +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | Execute$ TrigExchangeChoose | TriggerDescription$ At the beginning of your upkeep, exchange control of a permanent you control chosen at random and a permanent target opponent controls chosen at random. +SVar:TrigExchangeChoose:AB$ ChooseCard | Cost$ 0 | ValidTgts$ Opponent | Choices$ Permanent.TargetedPlayerCtrl | AtRandom$ True | Amount$ 1 | RememberChosen$ True | SubAbility$ ChooseYou +SVar:ChooseYou:DB$ ChooseCard | Choices$ Permanent.YouCtrl | Amount$ 1 | AtRandom$ True | RememberChosen$ True | SubAbility$ DBExchange +SVar:DBExchange:DB$ ExchangeControl | Defined$ Remembered | BothDefined$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/karona_false_god_avatar.jpg +Oracle:Hand -1, life +8\nAt the beginning of your upkeep, exchange control of a permanent you control chosen at random and a permanent target opponent controls chosen at random. +SetInfo:VAN Special \ No newline at end of file diff --git a/res/cardsfolder/k/kessig.txt b/res/cardsfolder/k/kessig.txt new file mode 100644 index 00000000000..8d5ef288522 --- /dev/null +++ b/res/cardsfolder/k/kessig.txt @@ -0,0 +1,10 @@ +Name:Kessig +ManaCost:no cost +Types:Plane Innistrad +S:Mode$ Continuous | Affected$ Creature.nonWerewolf | EffectZone$ Command | AddHiddenKeyword$ Prevent all combat damage that would be dealt by CARDNAME. | Description$ Prevent all combat damage that would be dealt by non-Werewolf creatures. +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, each creature you control gets +2/+2, gains trample, and becomes a Werewolf in addition to its other types until end of turn. +SVar:RolledChaos:AB$ AnimateAll | Cost$ 0 | ValidCards$ Creature.YouCtrl | Types$ Werewolf | SubAbility$ DBPump +SVar:DBPump:DB$ PumpAll | ValidCards$ Creature.YouCtrl | KW$ Trample | NumAtt$ 2 | NumDef$ 2 +SVar:Picture:http://www.wizards.com/global/images/magic/general/kessig.jpg +Oracle:Prevent all combat damage that would be dealt by non-Werewolf creatures.\nWhenever you roll {C}, each creature you control gets +2/+2, gains trample, and becomes a Werewolf in addition to its other types until end of turn. +SetInfo:PC2 Common \ No newline at end of file diff --git a/res/cardsfolder/k/knowledge_exploitation.txt b/res/cardsfolder/k/knowledge_exploitation.txt index c9b8e1c34c6..fea12faee6d 100644 --- a/res/cardsfolder/k/knowledge_exploitation.txt +++ b/res/cardsfolder/k/knowledge_exploitation.txt @@ -1,8 +1,8 @@ Name:Knowledge Exploitation ManaCost:5 U U Types:Tribal Instant Rogue -A:SP$ ChangeZone | Cost$ 5 U U | ValidTgts$ Opponent | Origin$ Library | Destination$ Library | ChangeType$ Instant,Sorcery | ChangeNum$ 1 | RememberChanged$ True | Reveal$ True | Shuffle$ False | DefinedPlayer$ Targeted | Chooser$ You | Mandatory$ True | SubAbility$ DBPlay | SpellDescription$ Search target opponent's library for an instant or sorcery card. You may cast that card without paying its mana cost. Then that player shuffles his or her library. -A:SP$ ChangeZone | Cost$ 3 U | Activation$ Prowl | ValidTgts$ Opponent | Origin$ Library | Destination$ Library | ChangeType$ Instant,Sorcery | ChangeNum$ 1 | RememberChanged$ True | Reveal$ True | Shuffle$ False | PrecostDesc$ Prowl 3 U | DefinedPlayer$ Targeted | Chooser$ You | Mandatory$ True | SubAbility$ DBPlay | SpellDescription$ (You may cast this for its prowl cost if you dealt combat damage to a player this turn with a Rogue.) +A:SP$ ChangeZone | Cost$ 5 U U | ValidTgts$ Opponent | Origin$ Library | Destination$ Library | ChangeType$ Instant,Sorcery | ChangeNum$ 1 | RememberChanged$ True | Reveal$ True | Shuffle$ False | DefinedPlayer$ Targeted | Chooser$ You | Mandatory$ True | SubAbility$ DBPlay | StackDescription$ Search {p:Targeted}'s library for an instant or sorcery card | SpellDescription$ Search target opponent's library for an instant or sorcery card. You may cast that card without paying its mana cost. Then that player shuffles his or her library. +A:SP$ ChangeZone | Cost$ 3 U | Activation$ Prowl | ValidTgts$ Opponent | Origin$ Library | Destination$ Library | ChangeType$ Instant,Sorcery | ChangeNum$ 1 | RememberChanged$ True | Reveal$ True | Shuffle$ False | PrecostDesc$ Prowl 3 U | DefinedPlayer$ Targeted | Chooser$ You | Mandatory$ True | SubAbility$ DBPlay | StackDescription$ Search {p:Targeted}'s library for an instant or sorcery card | SpellDescription$ (You may cast this for its prowl cost if you dealt combat damage to a player this turn with a Rogue.) SVar:DBPlay:DB$ Play | Defined$ Remembered | Controller$ You | WithoutManaCost$ True | Optional$ True | SubAbility$ DBShuffle SVar:DBShuffle:DB$ Shuffle | Defined$ RememberedController | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/res/cardsfolder/k/koskun_falls.txt b/res/cardsfolder/k/koskun_falls.txt new file mode 100644 index 00000000000..759f892b56c --- /dev/null +++ b/res/cardsfolder/k/koskun_falls.txt @@ -0,0 +1,11 @@ +Name:Koskun Falls +ManaCost:2 B B +Types:World Enchantment +S:Mode$ CantAttackUnless | ValidCard$ Creature | Target$ You | Cost$ 2 | Description$ Creatures can't attack you unless their controller pays 2 for each creature he or she controls that's attacking you. +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSac | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you tap an untapped creature you control. +SVar:TrigSac:AB$ Sacrifice | Cost$ 0 | Defined$ Self | UnlessCost$ tapXType<1/Creature> | UnlessPayer$ You +SVar:NeedsToPlayVar:Y GE1 +SVar:Y:Count$Valid Creature.YouCtrl +SVar:Picture:http://www.wizards.com/global/images/magic/general/koskun_falls.jpg +Oracle:At the beginning of your upkeep, sacrifice Koskun Falls unless you tap an untapped creature you control.\nCreatures can't attack you unless their controller pays {2} for each creature he or she controls that's attacking you. +SetInfo:HML Rare \ No newline at end of file diff --git a/res/cardsfolder/k/kresh_the_bloodbraided_avatar.txt b/res/cardsfolder/k/kresh_the_bloodbraided_avatar.txt new file mode 100644 index 00000000000..b058c1437b6 --- /dev/null +++ b/res/cardsfolder/k/kresh_the_bloodbraided_avatar.txt @@ -0,0 +1,10 @@ +Name:Kresh the Bloodbraided Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+1/-3 +T:Mode$ Devoured | ValidDevoured$ Creature.YouCtrl | TriggerZones$ Command | Execute$ TrigToken | TriggerDescription$ Whenever a creature you control is devoured, put an X/X green Ooze creature token onto the battlefield, where X is the devoured creature's power. +SVar:TrigToken:AB$ Token | Cost$ 0 | TokenImage$ G X X Ooze | TokenAmount$ 1 | TokenName$ Ooze | TokenTypes$ Creature,Ooze | TokenOwner$ You | TokenColors$ Green | TokenPower$ X | TokenToughness$ X | References$ X | TokenImage$ g x x ooze rtr | SpellDescription$ Put an X/X green Ooze creature token onto the battlefield. +SVar:X:TriggeredDevoured$CardPower +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/kresh_the_bloodbraided_avatar.jpg +Oracle:Hand +1, life -3\nWhenever a creature you control is devoured, put an X/X green Ooze creature token onto the battlefield, where X is the devoured creature's power. +SetInfo:VAN Special \ No newline at end of file diff --git a/res/cardsfolder/l/leech_bonder.txt b/res/cardsfolder/l/leech_bonder.txt new file mode 100644 index 00000000000..a7f0e03063e --- /dev/null +++ b/res/cardsfolder/l/leech_bonder.txt @@ -0,0 +1,11 @@ +Name:Leech Bonder +ManaCost:2 U +Types:Creature Merfolk Soldier +PT:3/3 +K:etbCounter:M1M1:2 +A:AB$ Pump | Cost$ U Q | ValidTgts$ Creature | TgtPrompt$ Select target creature to remove counters | SubAbility$ DBMove | StackDescription$ None | SpellDescription$ Move a counter from target creature onto another target creature. +SVar:DBMove:DB$ MoveCounter | Source$ ParentTarget | ValidTgts$ Creature | TgtPrompt$ Select target creature to add counters | TargetUnique$ True | CounterType$ Any | CounterNum$ 1 +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/leech_bonder.jpg +Oracle:Leech Bonder enters the battlefield with two -1/-1 counters on it.\n{U}, {Q}: Move a counter from target creature onto another target creature. ({Q} is the untap symbol.) +SetInfo:SHM Uncommon \ No newline at end of file diff --git a/res/cardsfolder/l/lobotomy.txt b/res/cardsfolder/l/lobotomy.txt index 02fa8c2e03c..1f0c66dc03b 100644 --- a/res/cardsfolder/l/lobotomy.txt +++ b/res/cardsfolder/l/lobotomy.txt @@ -1,9 +1,9 @@ Name:Lobotomy ManaCost:2 U B Types:Sorcery -A:SP$ ChangeZone | Cost$ 2 U B | Origin$ Hand | Destination$ Exile | ValidTgts$ Player | ChangeType$ Card.nonBasic | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | ForgetOtherRemembered$ True | SubAbility$ DBSearch | SpellDescription$ Target player reveals his or her hand, then you choose a card other than a basic land card from it. Search that player's graveyard, hand, and library for all cards with the same name as the chosen card and exile them. Then that player shuffles his or her library. -SVar:DBSearch:DB$ChangeZoneAll | Origin$ Graveyard,Hand,Library | Destination$ Exile | ChangeType$ Remembered.sameName | Shuffle$ True| Defined$ Targeted | SubAbility$ DBCleanup -SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True +A:SP$ ChangeZone | Cost$ 2 U B | Origin$ Hand | Destination$ Exile | ValidTgts$ Player | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card.nonBasic | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | ForgetOtherRemembered$ True | SubAbility$ DBSearch | SpellDescription$ Target player reveals his or her hand, then you choose a card other than a basic land card from it. Search that player's graveyard, hand, and library for all cards with the same name as the chosen card and exile them. Then that player shuffles his or her library. +SVar:DBSearch:DB$ ChangeZoneAll | Origin$ Graveyard,Hand,Library | Destination$ Exile | ChangeType$ Remembered.sameName | Shuffle$ True| Defined$ Targeted | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:Picture:http://www.wizards.com/global/images/magic/general/lobotomy.jpg Oracle:Target player reveals his or her hand, then you choose a card other than a basic land card from it. Search that player's graveyard, hand, and library for all cards with the same name as the chosen card and exile them. Then that player shuffles his or her library. SetInfo:TMP Uncommon diff --git a/res/cardsfolder/l/lost_hours.txt b/res/cardsfolder/l/lost_hours.txt index eac0a38338e..15de3164d2d 100644 --- a/res/cardsfolder/l/lost_hours.txt +++ b/res/cardsfolder/l/lost_hours.txt @@ -1,7 +1,7 @@ Name:Lost Hours ManaCost:1 B Types:Sorcery -A:SP$ ChangeZone | Cost$ 1 B | Origin$ Hand | Destination$ Library | LibraryPosition$ 2 | ValidTgts$ Player | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | SpellDescription$ Target player reveals his or her hand. You choose a nonland card from it. That player puts that card into his or her library third from the top. +A:SP$ ChangeZone | Cost$ 1 B | Origin$ Hand | Destination$ Library | LibraryPosition$ 2 | ValidTgts$ Player | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | SpellDescription$ Target player reveals his or her hand. You choose a nonland card from it. That player puts that card into his or her library third from the top. SVar:Picture:http://www.wizards.com/global/images/magic/general/lost_hours.jpg Oracle:Target player reveals his or her hand. You choose a nonland card from it. That player puts that card into his or her library third from the top. SetInfo:FUT Common \ No newline at end of file diff --git a/res/cardsfolder/l/lullmage_mentor.txt b/res/cardsfolder/l/lullmage_mentor.txt new file mode 100644 index 00000000000..8e9e0a00e64 --- /dev/null +++ b/res/cardsfolder/l/lullmage_mentor.txt @@ -0,0 +1,10 @@ +Name:Lullmage Mentor +ManaCost:1 U U +Types:Creature Merfolk Wizard +PT:2/2 +T:Mode$ Countered | ValidCause$ Card.YouCtrl | ValidCard$ Card | OptionalDecider$ You | Execute$ TrigToken | TriggerDescription$ Whenever a spell or ability you control counters a spell, you may put a 1/1 blue Merfolk creature token onto the battlefield. +SVar:TrigToken:AB$ Token | Cost$ 0 | TokenImage$ U 1 1 Merfolk | TokenAmount$ 1 | TokenName$ Merfolk | TokenTypes$ Creature,Merfolk | TokenOwner$ You | TokenColors$ Blue | TokenPower$ 1 | TokenToughness$ 1 +A:AB$ Counter | Cost$ tapXType<7/Merfolk> | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | SpellDescription$ Counter target spell. +SVar:Picture:http://www.wizards.com/global/images/magic/general/lullmage_mentor.jpg +Oracle:Whenever a spell or ability you control counters a spell, you may put a 1/1 blue Merfolk creature token onto the battlefield.\nTap seven untapped Merfolk you control: Counter target spell. +SetInfo:ZEN Rare \ No newline at end of file diff --git a/res/cardsfolder/m/mercadian_atlas.txt b/res/cardsfolder/m/mercadian_atlas.txt index f56da9590af..382d01bae7f 100644 --- a/res/cardsfolder/m/mercadian_atlas.txt +++ b/res/cardsfolder/m/mercadian_atlas.txt @@ -1,7 +1,7 @@ Name:Mercadian Atlas ManaCost:5 Types:Artifact -T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Execute$ AtlasDraw | CheckSVar$ X | SVarCompare$ EQ0 | Optional$ True | TriggerDescription$ At the beginning of your end step, if you didn't play a land this turn, you may draw a card. +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Execute$ AtlasDraw | CheckSVar$ X | SVarCompare$ EQ0 | Optional$ True | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your end step, if you didn't play a land this turn, you may draw a card. SVar:AtlasDraw:DB$ Draw | Defined$ You | NumCards$ 1 SVar:X:Count$YourLandsPlayed SVar:Picture:http://www.wizards.com/global/images/magic/general/mercadian_atlas.jpg diff --git a/res/cardsfolder/m/mesmeric_fiend.txt b/res/cardsfolder/m/mesmeric_fiend.txt index a8c2e34d3ef..44bb38f73ea 100644 --- a/res/cardsfolder/m/mesmeric_fiend.txt +++ b/res/cardsfolder/m/mesmeric_fiend.txt @@ -4,7 +4,7 @@ Types:Creature Nightmare Horror PT:1/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMesmericExile | TriggerDescription$ When CARDNAME enters the battlefield, target opponent reveals his or her hand and you choose a nonland card from it. Exile that card. When CARDNAME leaves the battlefield, return the exiled card to its owner's hand. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigMesmericBounce | Secondary$ True | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME enters the battlefield, target opponent reveals his or her hand and you choose a nonland card from it. Exile that card. When CARDNAME leaves the battlefield, return the exiled card to its owner's hand. -SVar:TrigMesmericExile:AB$ ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True +SVar:TrigMesmericExile:AB$ ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | DefinedPlayer$ Targeted | Chooser$ You | TgtPrompt$ Select target opponent | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True SVar:TrigMesmericBounce:AB$ ChangeZone | Cost$ 0 | Origin$ Exile | Destination$ Hand | Defined$ Remembered | SubAbility$ DBMesmericCleanup SVar:DBMesmericCleanup:DB$ Cleanup | ClearRemembered$ True SVar:Picture:http://www.wizards.com/global/images/magic/general/mesmeric_fiend.jpg diff --git a/res/cardsfolder/m/multanis_presence.txt b/res/cardsfolder/m/multanis_presence.txt new file mode 100644 index 00000000000..856e077dbfb --- /dev/null +++ b/res/cardsfolder/m/multanis_presence.txt @@ -0,0 +1,9 @@ +Name:Multani's Presence +ManaCost:G +Types:Enchantment +T:Mode$ Countered | ValidCard$ Card | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ DBDraw | TriggerDescription$ Whenever a spell you've cast is countered, draw a card. +SVar:DBDraw:AB$ Draw | Cost$ 0 | NumCards$ 1 +SVar:RemRandomDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/multanis_presence.jpg +Oracle:Whenever a spell you've cast is countered, draw a card. +SetInfo:ULG Uncommon \ No newline at end of file diff --git a/res/cardsfolder/n/nebuchadnezzar.txt b/res/cardsfolder/n/nebuchadnezzar.txt index ab711332369..d5a64f06c47 100644 --- a/res/cardsfolder/n/nebuchadnezzar.txt +++ b/res/cardsfolder/n/nebuchadnezzar.txt @@ -2,7 +2,10 @@ Name:Nebuchadnezzar ManaCost:3 U B Types:Legendary Creature Human Wizard PT:3/3 -#This is necessary for cost payment, but not used. +A:AB$ NameCard | Cost$ X T | Defined$ You | SubAbility$ DBReveal | PlayerTurn$ True | SpellDescription$ Name a card. Target opponent reveals X cards at random from his or her hand. Then that player discards all cards with that name revealed this way. Activate this ability only during your turn. +SVar:DBReveal:DB$ Reveal | ValidTgts$ Opponent | Random$ True | NumCards$ X | References$ X | RememberRevealed$ True | SubAbility$ DBDiscard +SVar:DBDiscard:DB$ Discard | DefinedCards$ ValidHand Card.IsRemembered+NamedCard | Defined$ Targeted | Mode$ Defined | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$xPaid SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/nebuchadnezzar.jpg diff --git a/res/cardsfolder/n/nephalia.txt b/res/cardsfolder/n/nephalia.txt new file mode 100644 index 00000000000..4fdc48d2a96 --- /dev/null +++ b/res/cardsfolder/n/nephalia.txt @@ -0,0 +1,12 @@ +Name:Nephalia +ManaCost:no cost +Types:Plane Innistrad +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Command | Execute$ TrigMill | TriggerDescription$ At the beginning of your end step, put the top seven cards of your library into your graveyard. Then return a card at random from your graveyard to your hand. +SVar:TrigMill:AB$ Mill | Cost$ 0 | NumCards$ 7 | SubAbility$ DBRandom +SVar:DBRandom:DB$ ChooseCard | Choices$ Card.YouOwn | ChoiceZone$ Graveyard | AtRandom$ True | Amount$ 1 | SubAbility$ DBReturn +SVar:DBReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | Defined$ ChosenCard +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, return target card from your graveyard to your hand. +SVar:RolledChaos:AB$ ChangeZone | Cost$ 0 | Origin$ Graveyard | Destination$ Hand | TgtPrompt$ Select target card in your graveyard | ValidTgts$ Card.YouOwn +SVar:Picture:http://www.wizards.com/global/images/magic/general/nephalia.jpg +Oracle:At the beginning of your end step, put the top seven cards of your library into your graveyard. Then return a card at random from your graveyard to your hand.\nWhenever you roll {C}, return target card from your graveyard to your hand. +SetInfo:PC2 Common \ No newline at end of file diff --git a/res/cardsfolder/n/night_terrors.txt b/res/cardsfolder/n/night_terrors.txt index 2fe4fb283b0..fe66dd34220 100644 --- a/res/cardsfolder/n/night_terrors.txt +++ b/res/cardsfolder/n/night_terrors.txt @@ -1,7 +1,7 @@ Name:Night Terrors ManaCost:2 B Types:Sorcery -A:SP$ ChangeZone | Cost$ 2 B | Origin$ Hand | Destination$ Exile | ValidTgts$ Player | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | SpellDescription$ Target player reveals his or her hand. You choose a nonland card from it. Exile that card. +A:SP$ ChangeZone | Cost$ 2 B | Origin$ Hand | Destination$ Exile | ValidTgts$ Player | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | SpellDescription$ Target player reveals his or her hand. You choose a nonland card from it. Exile that card. SVar:Picture:http://www.wizards.com/global/images/magic/general/night_terrors.jpg Oracle:Target player reveals his or her hand. You choose a nonland card from it. Exile that card. SetInfo:ISD Common \ No newline at end of file diff --git a/res/cardsfolder/p/painful_memories.txt b/res/cardsfolder/p/painful_memories.txt index 7e717e433fa..bf26199db3a 100644 --- a/res/cardsfolder/p/painful_memories.txt +++ b/res/cardsfolder/p/painful_memories.txt @@ -1,7 +1,7 @@ Name:Painful Memories ManaCost:1 B Types:Sorcery -A:SP$ ChangeZone | Cost$ 1 B | Origin$ Hand | Destination$ Library | LibraryPosition$ 0 | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | SpellDescription$ Look at target opponent's hand and choose a card from it. Put that card on top of that player's library. +A:SP$ ChangeZone | Cost$ 1 B | Origin$ Hand | Destination$ Library | LibraryPosition$ 0 | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | StackDescription$ SpellDescription | SpellDescription$ Look at target opponent's hand and choose a card from it. Put that card on top of that player's library. SVar:Picture:http://www.wizards.com/global/images/magic/general/painful_memories.jpg Oracle:Look at target opponent's hand and choose a card from it. Put that card on top of that player's library. SetInfo:MIR Uncommon diff --git a/res/cardsfolder/p/parallax_nexus.txt b/res/cardsfolder/p/parallax_nexus.txt index 27cc94eb053..7b3bbb499c6 100644 --- a/res/cardsfolder/p/parallax_nexus.txt +++ b/res/cardsfolder/p/parallax_nexus.txt @@ -2,7 +2,7 @@ Name:Parallax Nexus ManaCost:2 B Types:Enchantment K:Fading:5 -A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Opponent | SorcerySpeed$ True | TgtPrompt$ Select target opponent | Origin$ Hand | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | RememberChanged$ True | Chooser$ Targeted | IsCurse$ True | Mandatory$ True | Hidden$ True | SpellDescription$ Target opponent exiles a card from his or her hand. Activate this ability only any time you could cast a sorcery. +A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Opponent | SorcerySpeed$ True | TgtPrompt$ Select target opponent | Origin$ Hand | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | RememberChanged$ True | Chooser$ Targeted | IsCurse$ True | Mandatory$ True | Hidden$ True | StackDescription$ SpellDescription | SpellDescription$ Target opponent exiles a card from his or her hand. Activate this ability only any time you could cast a sorcery. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME leaves the battlefield, each player returns to his or her hand all cards he or she owns exiled with CARDNAME. SVar:TrigReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Hand | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/res/cardsfolder/p/peacekeeper_avatar.txt b/res/cardsfolder/p/peacekeeper_avatar.txt new file mode 100644 index 00000000000..5cdb64ff96a --- /dev/null +++ b/res/cardsfolder/p/peacekeeper_avatar.txt @@ -0,0 +1,10 @@ +Name:Peacekeeper Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+0/+9 +A:AB$ RepeatEach | Cost$ 3 | ActivationZone$ Command | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ ArrestEach | StackDescription$ SpellDescription | SpellDescription$ For each opponent who controls a creature, put a token onto the battlefield that's a copy of a card named Arrest and attach it to a creature that player controls chosen at random. +SVar:ArrestEach:DB$ ChooseCard | Amount$ 1 | Choices$ Creature.RememberedPlayerCtrl | AtRandom$ True | SubAbility$ DBAttach +SVar:DBAttach:DB$ CopyPermanent | NumCopies$ 1 | ValidSupportedCopy$ Card.namedArrest | DefinedName$ Arrest | AttachedTo$ ChosenCard | ConditionDefined$ ChosenCard | ConditionPresent$ Creature | ConditionCompare$ GE1 +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/peacekeeper_avatar.jpg +Oracle:Hand +0, life +9\n{3}: For each opponent who controls a creature, put a token onto the battlefield that's a copy of a card named Arrest and attach it to a creature that player controls chosen at random. +SetInfo:VAN Special \ No newline at end of file diff --git a/res/cardsfolder/p/perish_the_thought.txt b/res/cardsfolder/p/perish_the_thought.txt index ebc0b3ffad0..1944e0f12af 100644 --- a/res/cardsfolder/p/perish_the_thought.txt +++ b/res/cardsfolder/p/perish_the_thought.txt @@ -1,7 +1,7 @@ Name:Perish the Thought ManaCost:2 B Types:Sorcery -A:SP$ ChangeZone | Cost$ 2 B | Origin$ Hand | Destination$ Library | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | Shuffle$ True | Mandatory$ True | SpellDescription$ Target opponent reveals his or her hand. You choose a card from it. That player shuffles that card into his or her library. +A:SP$ ChangeZone | Cost$ 2 B | Origin$ Hand | Destination$ Library | ValidTgts$ Opponent | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | Shuffle$ True | Mandatory$ True | StackDescription$ SpellDescription | SpellDescription$ Target opponent reveals his or her hand. You choose a card from it. That player shuffles that card into his or her library. SVar:Picture:http://www.wizards.com/global/images/magic/general/perish_the_thought.jpg Oracle:Target opponent reveals his or her hand. You choose a card from it. That player shuffles that card into his or her library. SetInfo:ROE Common \ No newline at end of file diff --git a/res/cardsfolder/p/praetors_grasp.txt b/res/cardsfolder/p/praetors_grasp.txt index 6fb4fec4d62..10937cc0982 100644 --- a/res/cardsfolder/p/praetors_grasp.txt +++ b/res/cardsfolder/p/praetors_grasp.txt @@ -1,7 +1,7 @@ Name:Praetor's Grasp ManaCost:1 B B Types:Sorcery -A:SP$ ChangeZone | Cost$ 1 B B | Origin$ Library | Destination$ Exile | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | SubAbility$ DBPump | SpellDescription$ Search target opponent's library for a card and exile it face down. Then that player shuffles his or her library. You may look at and play that card for as long as it remains exiled. +A:SP$ ChangeZone | Cost$ 1 B B | Origin$ Library | Destination$ Exile | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | SubAbility$ DBPump | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for a card and exile it face down. Then that player shuffles his or her library. You may look at and play that card for as long as it remains exiled. SVar:DBPump:DB$ Pump | Defined$ Remembered | KW$ Your opponent may look at this card. & May be played by your opponent | PumpZone$ Exile | Permanent$ True SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/praetors_grasp.jpg diff --git a/res/cardsfolder/r/reconnaissance.txt b/res/cardsfolder/r/reconnaissance.txt index 6488600f54f..8a1c56c5ad4 100644 --- a/res/cardsfolder/r/reconnaissance.txt +++ b/res/cardsfolder/r/reconnaissance.txt @@ -2,7 +2,7 @@ Name:Reconnaissance ManaCost:W Types:Enchantment A:AB$ RemoveFromCombat | Cost$ 0 | ValidTgts$ Creature.attacking+YouCtrl | TgtPrompt$ Select target attacking creature you control. | SubAbility$ DBUntap | SpellDescription$ Remove target attacking creature you control from combat and untap it. -SVar:DBUntap:DB$Untap | Defined$ Targeted +SVar:DBUntap:DB$ Untap | Defined$ Targeted SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/reconnaissance.jpg Oracle:{0}: Remove target attacking creature you control from combat and untap it. diff --git a/res/cardsfolder/r/runed_halo.txt b/res/cardsfolder/r/runed_halo.txt new file mode 100644 index 00000000000..95b189b4d5e --- /dev/null +++ b/res/cardsfolder/r/runed_halo.txt @@ -0,0 +1,11 @@ +Name:Runed Halo +ManaCost:W W +Types:Enchantment +K:ETBReplacement:Other:DBNameCard +SVar:DBNameCard:DB$ NameCard | Defined$ You | SpellDescription$ As CARDNAME enters the battlefield, name a card. +S:Mode$ Continuous | Affected$ You | AddKeyword$ Protection:ChosenName | Description$ You have protection from the chosen name. (You can't be targeted, dealt damage, or enchanted by anything with that name.) +SVar:RemAIDeck:True +SVar:RemRandomDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/runed_halo.jpg +Oracle:As Runed Halo enters the battlefield, name a card.\nYou have protection from the chosen name. (You can't be targeted, dealt damage, or enchanted by anything with that name.) +SetInfo:SHM Rare \ No newline at end of file diff --git a/res/cardsfolder/s/screeching_griffin.txt b/res/cardsfolder/s/screeching_griffin.txt index 64fae6fc2ab..9916a1c4843 100644 --- a/res/cardsfolder/s/screeching_griffin.txt +++ b/res/cardsfolder/s/screeching_griffin.txt @@ -3,6 +3,11 @@ ManaCost:3 W Types:Creature Griffin PT:2/2 K:Flying +A:AB$ Pump | Cost$ R | ValidTgts$ Creature | TgtPrompt$ Select target creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBPump | StackDescription$ {c:Targeted} can't block CARDNAME this turn. | SpellDescription$ Target creature can't block CARDNAME this turn. +SVar:DBPump:DB$ Pump | KW$ HIDDEN CantBeBlockedBy Creature.IsRemembered | StackDescription$ None +T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/screeching_griffin.jpg Oracle:Flying\n{R}: Target creature can't block Screeching Griffin this turn. diff --git a/res/cardsfolder/s/shrewd_hatchling.txt b/res/cardsfolder/s/shrewd_hatchling.txt index d687f9fe3f6..2de4d013a60 100644 --- a/res/cardsfolder/s/shrewd_hatchling.txt +++ b/res/cardsfolder/s/shrewd_hatchling.txt @@ -3,6 +3,11 @@ ManaCost:3 UR Types:Creature Elemental PT:6/6 K:etbCounter:M1M1:4 +A:AB$ Pump | Cost$ UR | ValidTgts$ Creature | TgtPrompt$ Select target creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBPump | StackDescription$ {c:Targeted} can't block CARDNAME this turn. | SpellDescription$ Target creature can't block CARDNAME this turn. +SVar:DBPump:DB$ Pump | KW$ HIDDEN CantBeBlockedBy Creature.IsRemembered | StackDescription$ None +T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True T:Mode$ SpellCast | ValidCard$ Card.Red | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigRemoveCounter | TriggerDescription$ Whenever you cast a red spell, remove a -1/-1 counter from CARDNAME. T:Mode$ SpellCast | ValidCard$ Card.Blue | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigRemoveCounter | TriggerDescription$ Whenever you cast a blue spell, remove a -1/-1 counter from CARDNAME. SVar:TrigRemoveCounter:AB$ RemoveCounter | Cost$ 0 | Defined$ Self | CounterType$ M1M1 | CounterNum$ 1 diff --git a/res/cardsfolder/s/signal_the_clans.txt b/res/cardsfolder/s/signal_the_clans.txt index c951eaf2365..75b7b8bf9d9 100644 --- a/res/cardsfolder/s/signal_the_clans.txt +++ b/res/cardsfolder/s/signal_the_clans.txt @@ -6,7 +6,7 @@ SVar:DBChoose:DB$ ChooseCard | Defined$ You | Amount$ 1 | AtRandom$ True | Choic SVar:DBChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | Defined$ ChosenCard | StackDescription$ None | SubAbility$ DBShuffle | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ3 SVar:DBShuffle:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Library | Shuffle$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Count$DifferentCardNamesRemembered +SVar:X:Count$DifferentCardNames_Creature.IsRemembered SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/signal_the_clans.jpg Oracle:Search your library for three creature cards and reveal them. If you reveal three cards with different names, choose one of them at random and put that card into your hand. Shuffle the rest into your library. diff --git a/res/cardsfolder/s/sliver_queen_avatar.txt b/res/cardsfolder/s/sliver_queen_avatar.txt new file mode 100644 index 00000000000..acd9342d67b --- /dev/null +++ b/res/cardsfolder/s/sliver_queen_avatar.txt @@ -0,0 +1,13 @@ +Name:Sliver Queen Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+0/+7 +T:Mode$ SpellCast | ValidCard$ Creature.nonSliver | ValidActivatingPlayer$ You | TriggerZones$ Command | Execute$ TrigCopy | TriggerDescription$ Whenever you cast a non-Sliver creature spell, exile that spell. If you do, put a token onto the battlefield that's a copy of a random non-Shapeshifter Sliver creature card with the same converted mana cost as that spell. +SVar:TrigCopy:AB$ ChangeZone | Cost$ 0 | Defined$ TriggeredCard | Origin$ Stack | Destination$ Exile | Fizzle$ True | RememberChanged$ True | SubAbility$ DBCopy +SVar:DBCopy:DB$ CopyPermanent | NumCopies$ 1 | ValidSupportedCopy$ Creature.Sliver+nonShapeshifter+cmcEQX | RandomCopied$ True | RandomNum$ 1 | ConditionCheckSVar$ RememberedSize | ConditionSVarCompare$ GE1 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:TriggeredCard$CardManaCost +SVar:RememberedSize:Remembered$Amount +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/sliver_queen_avatar.jpg +Oracle:Hand +0, life +7\nWhenever you cast a non-Sliver creature spell, exile that spell. If you do, put a token onto the battlefield that's a copy of a random non-Shapeshifter Sliver creature card with the same converted mana cost as that spell. +SetInfo:VAN Special \ No newline at end of file diff --git a/res/cardsfolder/s/soulbright_flamekin.txt b/res/cardsfolder/s/soulbright_flamekin.txt new file mode 100644 index 00000000000..358d61a0e3c --- /dev/null +++ b/res/cardsfolder/s/soulbright_flamekin.txt @@ -0,0 +1,16 @@ +Name:Soulbright Flamekin +ManaCost:1 R +Types:Creature Elemental Shaman +PT:2/1 +A:AB$ Pump | Cost$ 2 | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ Trample | SubAbility$ StoreNum | SpellDescription$ Target creature gains trample until end of turn. If this is the third time this ability has resolved this turn, you may add R R R R R R R R to your mana pool. +SVar:StoreNum:DB$ StoreSVar | SVar$ SoulbrightNum | Type$ CountSVar | Expression$ SoulbrightNum/Plus.1 | SubAbility$ SoulbrightMana +SVar:SoulbrightMana:DB$ Mana | Produced$ R R R R R R R R | ConditionCheckSVar$ SoulbrightNum | ConditionSVarCompare$ EQ3 | Optional$ True +SVar:SoulbrightNum:Number$0 +T:Mode$ Phase | Phase$ Cleanup | Execute$ TrigReset | Static$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReset | Static$ True +SVar:TrigReset:AB$ StoreSVar | Cost$ 0 | SVar$ SoulbrightNum | Type$ Number | Expression$ 0 +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/soulbright_flamekin.jpg +Oracle:{2}: Target creature gains trample until end of turn. If this is the third time this ability has resolved this turn, you may add {R}{R}{R}{R}{R}{R}{R}{R} to your mana pool. +SetInfo:DD2 Common +SetInfo:LRW Common \ No newline at end of file diff --git a/res/cardsfolder/s/spelljack.txt b/res/cardsfolder/s/spelljack.txt index 1aedf658b6e..d9f36a757d0 100644 --- a/res/cardsfolder/s/spelljack.txt +++ b/res/cardsfolder/s/spelljack.txt @@ -1,7 +1,7 @@ Name:Spelljack ManaCost:3 U U U Types:Instant -A:SP$ Counter | Cost$ 3 U U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | RememberTargets$ True | ForgetOtherTargets$ True | Destination$ Exile | SubAbility$ DBEffect | SpellDescription$ Counter target spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyard. You may play it without paying its mana cost for as long as it remains exiled. (If it has X in its mana cost, X is 0.) +A:SP$ Counter | Cost$ 3 U U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | RememberCountered$ True | ForgetOtherTargets$ True | Destination$ Exile | SubAbility$ DBEffect | SpellDescription$ Counter target spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyard. You may play it without paying its mana cost for as long as it remains exiled. (If it has X in its mana cost, X is 0.) SVar:DBEffect:DB$ Effect | Name$ Spelljack Effect | RememberObjects$ Remembered | StaticAbilities$ PlayOpp,PlayYou | Duration$ Permanent | Triggers$ TrigCleanup | SVars$ DBCleanup | References$ PlayOpp,PlayYou,TrigCleanup,DBCleanup SVar:PlayOpp:Mode$ Continuous | EffectZone$ Command | Affected$ Card.IsRemembered+OppOwn | AffectedZone$ Exile | AddHiddenKeyword$ May be played by your opponent without paying its mana cost | Description$ You may play cards exiled with Spelljack. SVar:PlayYou:Mode$ Continuous | EffectZone$ Command | Affected$ Card.IsRemembered+YouOwn | AffectedZone$ Exile | AddHiddenKeyword$ May be played without paying its mana cost diff --git a/res/cardsfolder/s/spin_engine.txt b/res/cardsfolder/s/spin_engine.txt index 680775e7c34..2bdbb53bc82 100644 --- a/res/cardsfolder/s/spin_engine.txt +++ b/res/cardsfolder/s/spin_engine.txt @@ -2,6 +2,11 @@ Name:Spin Engine ManaCost:3 Types:Artifact Creature Construct PT:3/1 +A:AB$ Pump | Cost$ R | ValidTgts$ Creature | TgtPrompt$ Select target creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBPump | StackDescription$ {c:Targeted} can't block CARDNAME this turn. | SpellDescription$ Target creature can't block CARDNAME this turn. +SVar:DBPump:DB$ Pump | KW$ HIDDEN CantBeBlockedBy Creature.IsRemembered | StackDescription$ None +T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/spin_engine.jpg Oracle:{R}: Target creature can't block Spin Engine this turn. diff --git a/res/cardsfolder/s/splitting_headache.txt b/res/cardsfolder/s/splitting_headache.txt index ed9bb6e4a0d..aa47a6ce60d 100644 --- a/res/cardsfolder/s/splitting_headache.txt +++ b/res/cardsfolder/s/splitting_headache.txt @@ -1,8 +1,9 @@ Name:Splitting Headache ManaCost:3 B Types:Sorcery -A:SP$ Discard | Cost$ 3 B | ValidTgts$ Player | NumCards$ 2 | Mode$ TgtChoose | SpellDescription$ Choose one - Target player discards two cards; -A:SP$ Discard | Cost$ 3 B | ValidTgts$ Player | NumCards$ 1 | Mode$ RevealYouChoose | SpellDescription$ or target player reveals his or her hand, you choose a card from it, then that player discards that card. +A:SP$ Charm | Cost$ 3 B | Choices$ SplitDiscard,FocusDiscard | CharmNum$ 1 | SpellDescription$ Choose one - Target player discards two cards; or target player reveals his or her hand, you choose a card from it, then that player discards that card. +SVar:SplitDiscard:DB$ Discard | ValidTgts$ Player | NumCards$ 2 | Mode$ TgtChoose | SpellDescription$ Choose one - Target player discards two cards; +SVar:FocusDiscard:DB$ Discard | ValidTgts$ Player | NumCards$ 1 | Mode$ RevealYouChoose | SpellDescription$ Target player reveals his or her hand, you choose a card from it, then that player discards that card. SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/splitting_headache.jpg Oracle:Choose one - Target player discards two cards; or target player reveals his or her hand, you choose a card from it, then that player discards that card. diff --git a/res/cardsfolder/s/starke_of_rath.txt b/res/cardsfolder/s/starke_of_rath.txt index 4a14492b82e..fff730cf691 100644 --- a/res/cardsfolder/s/starke_of_rath.txt +++ b/res/cardsfolder/s/starke_of_rath.txt @@ -3,7 +3,7 @@ ManaCost:1 R R Types:Legendary Creature Human Rogue PT:2/2 A:AB$ Destroy | Cost$ T | ValidTgts$ Artifact,Creature | TgtPrompt$ Select target artifact or creature | SubAbility$ TrigControl | SpellDescription$ Destroy target artifact or creature. -SVar:TrigControl:DB$GainControl | Cost$ 0 | Defined$ Self | NewController$ TargetedController | SubAbility$ RemCombat | SpellDescription$ That permanent's controller gains control of CARDNAME. (This effect lasts indefinitely.) +SVar:TrigControl:DB$ GainControl | Cost$ 0 | Defined$ Self | NewController$ TargetedController | SubAbility$ RemCombat | SpellDescription$ That permanent's controller gains control of CARDNAME. (This effect lasts indefinitely.) SVar:RemCombat:DB$ RemoveFromCombat | Defined$ Targeted SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/starke_of_rath.jpg diff --git a/res/cardsfolder/s/stonehewer_giant_avatar.txt b/res/cardsfolder/s/stonehewer_giant_avatar.txt new file mode 100644 index 00000000000..656eb7b6b60 --- /dev/null +++ b/res/cardsfolder/s/stonehewer_giant_avatar.txt @@ -0,0 +1,10 @@ +Name:Stonehewer Giant Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+1/-5 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl | TriggerZones$ Command | Execute$ TrigCopyEquip | TriggerDescription$ Whenever a creature enters the battlefield under your control, put a token onto the battlefield that's a copy of a random Equipment card with converted mana cost less than or equal to that creature's converted mana cost. Attach that Equipment to that creature. +SVar:TrigCopyEquip:AB$ CopyPermanent | Cost$ 0 | Defined$ TriggeredCard | NumCopies$ 1 | ValidSupportedCopy$ Equipment.cmcLEX | References$ X | RandomCopied$ True | RandomNum$ 1 | AttachedTo$ TriggeredCard +SVar:X:TriggeredCard$CardManaCost +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/stonehewer_giant_avatar.jpg +Oracle:Hand +1, life -5\nWhenever a creature enters the battlefield under your control, put a token onto the battlefield that's a copy of a random Equipment card with converted mana cost less than or equal to that creature's converted mana cost. Attach that Equipment to that creature. +SetInfo:VAN Special \ No newline at end of file diff --git a/res/cardsfolder/s/swift_silence.txt b/res/cardsfolder/s/swift_silence.txt new file mode 100644 index 00000000000..890e5e1a7c0 --- /dev/null +++ b/res/cardsfolder/s/swift_silence.txt @@ -0,0 +1,11 @@ +Name:Swift Silence +ManaCost:2 W U U +Types:Instant +A:SP$ Counter | Cost$ 2 W U U | AllType$ Spell | AllValid$ Card.Other | RememberCountered$ True | SubAbility$ DBDraw | SpellDescription$ Counter all other spells. Draw a card for each spell countered this way. +SVar:DBDraw:DB$ Draw | NumCards$ X | References$ X | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Remembered$Amount +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/swift_silence.jpg +Oracle:Counter all other spells. Draw a card for each spell countered this way. +SetInfo:DIS Rare \ No newline at end of file diff --git a/res/cardsfolder/t/the_zephyr_maze.txt b/res/cardsfolder/t/the_zephyr_maze.txt new file mode 100644 index 00000000000..3b8edb3407a --- /dev/null +++ b/res/cardsfolder/t/the_zephyr_maze.txt @@ -0,0 +1,10 @@ +Name:The Zephyr Maze +ManaCost:no cost +Types:Plane Kyneth +S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.withFlying | AddPower$ 2 | Description$ Creatures with flying get +2/+0. +S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.withoutFlying | AddPower$ -2 | Description$ Creatures without flying get -2/-0. +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, target creature gains flying until end of turn. +SVar:RolledChaos:AB$ Pump | Cost$ 0 | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ Flying +SVar:Picture:http://www.wizards.com/global/images/magic/general/the_zephyr_maze.jpg +Oracle:Creatures with flying get +2/+0.\nCreatures without flying get -2/-0.\nWhenever you roll {C}, target creature gains flying until end of turn. +SetInfo:PC2 Common \ No newline at end of file diff --git a/res/cardsfolder/t/thoughtseize.txt b/res/cardsfolder/t/thoughtseize.txt index ac97202c52e..7fac1fb24b1 100644 --- a/res/cardsfolder/t/thoughtseize.txt +++ b/res/cardsfolder/t/thoughtseize.txt @@ -2,7 +2,7 @@ Name:Thoughtseize ManaCost:B Types:Sorcery A:SP$ Discard | Cost$ B | ValidTgts$ Player | NumCards$ 1 | DiscardValid$ Card.nonLand | Mode$ RevealYouChoose | SubAbility$ DBLoseLife | SpellDescription$ Target player reveals his or her hand. You choose a nonland card from it. That player discards that card. You lose 2 life. -SVar:DBLoseLife:DB$LoseLife | LifeAmount$ 2 +SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 2 SVar:Picture:http://resources.wizards.com/magic/cards/lrw/en/card145969.jpg Oracle:Target player reveals his or her hand. You choose a nonland card from it. That player discards that card. You lose 2 life. SetInfo:LRW Rare \ No newline at end of file diff --git a/res/cardsfolder/t/thundering_wurm.txt b/res/cardsfolder/t/thundering_wurm.txt index 1349e4ae6e3..2cb47aad7dd 100644 --- a/res/cardsfolder/t/thundering_wurm.txt +++ b/res/cardsfolder/t/thundering_wurm.txt @@ -2,11 +2,8 @@ Name:Thundering Wurm ManaCost:2 G Types:Creature Wurm PT:4/4 -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDiscard | TriggerDescription$ When CARDNAME enters the battlefield, sacrifice it unless you discard a land card. -SVar:TrigDiscard:AB$ Discard | Cost$ 0 | NumCards$ 1 | DiscardValid$ Land | Mode$ TgtChoose | Optional$ True | RememberDiscarded$ True | SubAbility$ DBSacSelf -SVar:DBSacSelf:DB$ Sacrifice | Cost$ 0 | Defined$ Self | SubAbility$ DBCleanup | ConditionCheckSVar$ X | References$ X | ConditionSVarCompare$ LT1 -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Remembered$Amount +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBSacSelf | TriggerDescription$ When CARDNAME enters the battlefield, sacrifice it unless you discard a land card. +SVar:DBSacSelf:DB$ Sacrifice | Defined$ Self | UnlessCost$ Discard<1/Land> | UnlessPayer$ You SVar:NeedsToPlayVar:Y GE1 SVar:Y:Count$TypeInYourHand.Land SVar:Picture:http://www.wizards.com/global/images/magic/general/thundering_wurm.jpg diff --git a/res/cardsfolder/t/tidehollow_sculler.txt b/res/cardsfolder/t/tidehollow_sculler.txt index a937bd3a609..7e02b51b715 100644 --- a/res/cardsfolder/t/tidehollow_sculler.txt +++ b/res/cardsfolder/t/tidehollow_sculler.txt @@ -4,9 +4,9 @@ Types:Artifact Creature Zombie PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, target opponent reveals his or her hand and you choose a nonland card from it. Exile that card. When CARDNAME leaves the battlefield, return the exiled card to its owner's hand. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigBounce | Secondary$ True | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME enters the battlefield, target opponent reveals his or her hand and you choose a nonland card from it. Exile that card. When CARDNAME leaves the battlefield, return the exiled card to its owner's hand. -SVar:TrigExile:AB$ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | TgtPrompt$ Select target player | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | ForgetOtherRemembered$ True -SVar:TrigBounce:AB$ChangeZone | Cost$ 0 | Origin$ Exile | Destination$ Hand | Defined$ Remembered | SubAbility$ DBCleanup -SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True +SVar:TrigExile:AB$ ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | DefinedPlayer$ Targeted | Chooser$ You | TgtPrompt$ Select target opponent | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | ForgetOtherRemembered$ True +SVar:TrigBounce:AB$ ChangeZone | Cost$ 0 | Origin$ Exile | Destination$ Hand | Defined$ Remembered | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:Picture:http://www.wizards.com/global/images/magic/general/tidehollow_sculler.jpg Oracle:When Tidehollow Sculler enters the battlefield, target opponent reveals his or her hand and you choose a nonland card from it. Exile that card.\nWhen Tidehollow Sculler leaves the battlefield, return the exiled card to its owner's hand. SetInfo:ALA Uncommon \ No newline at end of file diff --git a/res/cardsfolder/t/trickbind.txt b/res/cardsfolder/t/trickbind.txt index bde9d2c6b57..d7e1d044ade 100644 --- a/res/cardsfolder/t/trickbind.txt +++ b/res/cardsfolder/t/trickbind.txt @@ -2,7 +2,7 @@ Name:Trickbind ManaCost:1 U Types:Instant K:Split second -A:SP$ Counter | Cost$ 1 U | TargetType$ Activated,Triggered | TgtPrompt$ Select target Activated or Triggered Ability | RememberTargets$ True | ValidTgts$ Card | SubAbility$ DBEffect | SpellDescription$ Counter target activated or triggered ability. If a permanent's ability is countered this way, activated abilities of that permanent can't be activated this turn. (Mana abilities can't be targeted.) +A:SP$ Counter | Cost$ 1 U | TargetType$ Activated,Triggered | TgtPrompt$ Select target Activated or Triggered Ability | RememberCountered$ True | ValidTgts$ Card | SubAbility$ DBEffect | SpellDescription$ Counter target activated or triggered ability. If a permanent's ability is countered this way, activated abilities of that permanent can't be activated this turn. (Mana abilities can't be targeted.) SVar:DBEffect:DB$ Effect | Name$ Trickbind Effect | StaticAbilities$ STCantBeActivated | RememberObjects$ Remembered | SubAbility$ DBCleanup | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ1 SVar:STCantBeActivated:Mode$ CantBeActivated | EffectZone$ Command | ValidCard$ Permanent.IsRemembered SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/res/cardsfolder/v/vexing_shusher.txt b/res/cardsfolder/v/vexing_shusher.txt new file mode 100644 index 00000000000..6d23bec2d3a --- /dev/null +++ b/res/cardsfolder/v/vexing_shusher.txt @@ -0,0 +1,11 @@ +Name:Vexing Shusher +ManaCost:RG RG +Types:Creature Goblin Shaman +PT:2/2 +K:CARDNAME can't be countered. +A:AB$ Pump | Cost$ RG | ValidTgts$ Card.inZoneStack | TgtZone$ Stack,Battlefield | PumpZone$ Stack | KW$ HIDDEN CARDNAME can't be countered. | SpellDescription$ Target spell can't be countered by spells or abilities. +#Should include another zone otherwise the target would not be defined as a card +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/vexing_shusher.jpg +Oracle:Vexing Shusher can't be countered.\n{R/G}: Target spell can't be countered by spells or abilities. +SetInfo:SHM Rare \ No newline at end of file diff --git a/res/cardsfolder/w/warp_world.txt b/res/cardsfolder/w/warp_world.txt new file mode 100644 index 00000000000..ee611675c7f --- /dev/null +++ b/res/cardsfolder/w/warp_world.txt @@ -0,0 +1,17 @@ +Name:Warp World +ManaCost:5 R R R +Types:Sorcery +A:SP$ RepeatEach | Cost$ 5 R R R | RepeatPlayers$ Player | RepeatSubAbility$ DBShuffle | SubAbility$ ChangePermanent | StackDescription$ SpellDescription | SpellDescription$ Each player shuffles all permanents he or she owns into his or her library, then reveals that many cards from the top of his or her library. Each player puts all artifact, creature, and land cards revealed this way onto the battlefield, then does the same for enchantment cards, then puts all cards revealed this way that weren't put onto the battlefield on the bottom of his or her library. +SVar:DBShuffle:DB$ ChangeZoneAll | ChangeType$ Permanent.RememberedPlayerCtrl | Imprint$ True | Origin$ Battlefield | Destination$ Library | Shuffle$ True | SubAbility$ DBDig +SVar:DBDig:DB$ Dig | Defined$ Remembered | NoMove$ True | DigNum$ WarpX | References$ WarpX | RememberRevealed$ True | Reveal$ True | SubAbility$ DBCleanImprint +SVar:DBCleanImprint:DB$ Cleanup | ClearImprinted$ True +SVar:WarpX:Imprinted$Amount +SVar:ChangePermanent:DB$ ChangeZoneAll | ChangeType$ Artifact.IsRemembered,Creature.IsRemembered,Land.IsRemembered | Origin$ Library | Destination$ Battlefield | ForgetChanged$ True | SubAbility$ ChangeEnchantment +SVar:ChangeEnchantment:DB$ ChangeZoneAll | ChangeType$ Enchantment.IsRemembered | Origin$ Library | Destination$ Battlefield | ForgetChanged$ True | SubAbility$ GotoBottom +SVar:GotoBottom:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Picture:http://www.wizards.com/global/images/magic/general/warp_world.jpg +Oracle:Each player shuffles all permanents he or she owns into his or her library, then reveals that many cards from the top of his or her library. Each player puts all artifact, creature, and land cards revealed this way onto the battlefield, then does the same for enchantment cards, then puts all cards revealed this way that weren't put onto the battlefield on the bottom of his or her library. +SetInfo:RAV Rare +SetInfo:M10 Rare +SetInfo:10E Rare \ No newline at end of file diff --git a/res/cardsfolder/w/weight_of_conscience.txt b/res/cardsfolder/w/weight_of_conscience.txt new file mode 100644 index 00000000000..0d929197ba5 --- /dev/null +++ b/res/cardsfolder/w/weight_of_conscience.txt @@ -0,0 +1,10 @@ +Name:Weight of Conscience +ManaCost:1 W +Types:Enchantment Aura +K:Enchant creature +A:SP$ Attach | Cost$ 1 W | ValidTgts$ Creature | AILogic$ Curse +S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ CARDNAME can't attack. | Description$ Enchanted creature can't attack. +A:AB$ ChangeZone | Cost$ tapXType<2/Creature.sharesCreatureTypeWith/creatures that share a creature type> | Defined$ Enchanted | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile enchanted creature. +SVar:Picture:http://www.wizards.com/global/images/magic/general/weight_of_conscience.jpg +Oracle:Enchant creature\nEnchanted creature can't attack.\nTap two untapped creatures you control that share a creature type: Exile enchanted creature. +SetInfo:MOR Common \ No newline at end of file diff --git a/res/cardsfolder/w/wound_reflection.txt b/res/cardsfolder/w/wound_reflection.txt index 0e10d06490c..fee9e1b1822 100644 --- a/res/cardsfolder/w/wound_reflection.txt +++ b/res/cardsfolder/w/wound_reflection.txt @@ -2,7 +2,7 @@ Name:Wound Reflection ManaCost:5 B Types:Enchantment T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | Execute$ RepeatOpps | TriggerDescription$ At the beginning of each end step, each opponent loses life equal to the life he or she lost this turn. (Damage causes loss of life.) -SVar:RepeatOpps:AB$ RepeatEach | Cost$ 0 | RepeatPlayers$ Player | RepeatSubAbility$ TrigLoseLife +SVar:RepeatOpps:AB$ RepeatEach | Cost$ 0 | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ TrigLoseLife SVar:TrigLoseLife:DB$ LoseLife | Defined$ Remembered | LifeAmount$ X | References$ X SVar:X:PlayerCountRemembered$LifeLostThisTurn SVar:Picture:http://www.wizards.com/global/images/magic/general/wound_reflection.jpg diff --git a/res/cardsfolder/z/zurs_weirding.txt b/res/cardsfolder/z/zurs_weirding.txt new file mode 100644 index 00000000000..7ef0031af01 --- /dev/null +++ b/res/cardsfolder/z/zurs_weirding.txt @@ -0,0 +1,20 @@ +Name:Zur's Weirding +ManaCost:3 U +Types:Enchantment +S:Mode$ Continuous | Affected$ Player | AddKeyword$ Play with your hand revealed. | Description$ Players play with their hands revealed. +R:Event$ Draw | ActiveZones$ Battlefield | ValidPlayer$ Player | ReplaceWith$ RevealTop | Description$ If a player would draw a card, he or she reveals it instead. Then any other player may pay 2 life. If a player does, put that card into its owner's graveyard. Otherwise, that player draws a card. +SVar:RevealTop:AB$ Dig | Cost$ 0 | Defined$ ReplacedPlayer | DigNum$ 1 | NoMove$ True | Reveal$ True | SubAbility$ DBCheck +SVar:DBCheck:DB$ StoreSVar | SVar$ ZurCheck | Type$ Number | Expression$ 1 | UnlessPayer$ NonReplacedPlayer | UnlessCost$ PayLife<2> | SubAbility$ DBMill | StackDescription$ None +SVar:DBMill:DB$ Mill | Defined$ ReplacedPlayer | NumCards$ 1 | SubAbility$ DBDraw | ConditionCheckSVar$ ZurCheck | ConditionSVarCompare$ EQ0 | StackDescription$ None +SVar:DBDraw:DB$ Draw | Defined$ ReplacedPlayer | NumCards$ 1 | SubAbility$ DBReset | ConditionCheckSVar$ ZurCheck | ConditionSVarCompare$ EQ1 | StackDescription$ that player draws a card +SVar:DBReset:DB$ StoreSVar | SVar$ ZurCheck | Type$ Number | Expression$ 0 +SVar:ZurCheck:Number$0 +SVar:RemAIDeck:True +SVar:RemMultiplayer:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/zurs_weirding.jpg +Oracle:Players play with their hands revealed.\nIf a player would draw a card, he or she reveals it instead. Then any other player may pay 2 life. If a player does, put that card into its owner's graveyard. Otherwise, that player draws a card. +SetInfo:ICE Rare +SetInfo:5ED Rare +SetInfo:8ED Rare +SetInfo:6ED Rare +SetInfo:9ED Rare \ No newline at end of file diff --git a/res/lists/token-images.txt b/res/lists/token-images.txt index 56b2c996242..94c94c43f1c 100644 --- a/res/lists/token-images.txt +++ b/res/lists/token-images.txt @@ -166,6 +166,7 @@ u_1_1_camarid.jpg http://www.cardforge.org/fpics/tokens/u_1 u_1_1_cloud_sprite.jpg http://www.cardforge.org/fpics/tokens/u_1_1_cloud_sprite.jpg u_1_1_faerie.jpg http://www.cardforge.org/fpics/tokens/u_1_1_faerie.jpg u_1_1_illusion.jpg http://www.cardforge.org/fpics/tokens/u_1_1_illusion.jpg +u_1_1_merfolk.jpg http://www.cardforge.org/fpics/tokens/u_1_1_merfolk.jpg u_1_1_merfolk_wizard.jpg http://www.cardforge.org/fpics/tokens/u_1_1_merfolk_wizard.jpg u_1_1_thopter.jpg http://www.cardforge.org/fpics/tokens/u_1_1_thopter.jpg u_1_1_spirit_avr.jpg http://www.cardforge.org/fpics/tokens/u_1_1_spirit_avr.jpg @@ -236,5 +237,4 @@ morph.jpg http://www.cardforge.org/fpics/effects/mo # //These tokens are not currently used by any cards in Forge, but links provided should they be scripted so the correct name is used: # //g_1_1_wolves_of_the_hunt.jpg http://www.cardforge.org/fpics/tokens/g_1_1_wolves_of_the_hunt.jpg [LEG] Master of the Hunt # //rg_1_1_goblin_warrior.jpg http://www.cardforge.org/fpics/tokens/rg_1_1_goblin_warrior.jpg [SHM] Wort, the Raidmother -# //u_1_1_merfolk.jpg http://www.cardforge.org/fpics/tokens/u_1_1_merfolk.jpg [ZEN] Lullmage Mentor # //w_1_1_knight.jpg http://www.cardforge.org/fpics/tokens/w_1_1_knight.jpg [ALL] Errand of Duty diff --git a/res/quest/duels/Preacher 3.dck b/res/quest/duels/Preacher 3.dck new file mode 100644 index 00000000000..158d2111621 --- /dev/null +++ b/res/quest/duels/Preacher 3.dck @@ -0,0 +1,34 @@ +[duel] +[metadata] +Name=Preacher 3 +Title=Preacher +Difficulty=hard +Description=WB Cleric deck +Icon=Preacher.jpg +Deck Type=constructed +[main] +1 Akroma's Devoted +3 Ancestor's Prophet +1 Battlefield Medic +3 Battletide Alchemist +2 Cathedral Sanctifier +2 Celestial Gatekeeper +4 Doubtless One +4 Edgewalker +4 Hedron-Field Purists +2 High Priest of Penance +1 Mox Jet +1 Mox Pearl +1 Order of Whiteclay +12 Plains +1 Profane Prayers +1 Rotlung Reanimator +4 Scrubland +1 Skirsdag High Priest +5 Swamp +1 Syndic of Tithes +1 Temple Acolyte +1 Urborg, Tomb of Yawgmoth +2 Vile Deacon +2 Weathered Wayfarer +[sideboard] diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index d4c11d29f3d..4d48a54f78a 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -133,6 +133,7 @@ public class Card extends GameEntity implements Comparable { private List blockedThisTurn = null; private List blockedByThisTurn = null; + private boolean isCommander = false; private boolean startsGameInPlay = false; private boolean drawnThisTurn = false; private boolean tapped = false; @@ -1258,13 +1259,12 @@ public class Card extends GameEntity implements Comparable { if (this.hasKeyword("CARDNAME can't have counters placed on it.")) { return false; } - if (this.isCreature() && counterName.equals(CounterType.M1M1)) { + if (this.isCreature() && counterName == 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; } } - } return true; } @@ -4470,26 +4470,10 @@ public class Card extends GameEntity implements Comparable { public final void addMultiKickerMagnitude(final int n) { this.multiKickerMagnitude += n; } - - /** - *

- * Setter for the field multiKickerMagnitude. - *

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

- * Getter for the field multiKickerMagnitude. - *

- * - * @return a int. - */ public final int getMultiKickerMagnitude() { return this.multiKickerMagnitude; } @@ -7321,6 +7305,10 @@ public class Card extends GameEntity implements Comparable { if (!this.isType(source.getChosenType())) { return false; } + } else if (property.equals("IsCommander")) { + if(!this.isCommander) { + return false; + } } else { if (!this.isType(property)) { return false; @@ -8358,7 +8346,7 @@ public class Card extends GameEntity implements Comparable { additionalLog = "(As -1/-1 Counters)"; } if (source.hasKeyword("Deathtouch") && this.isCreature()) { - Singletons.getModel().getGame().getAction().destroy(this); + Singletons.getModel().getGame().getAction().destroy(this, null); additionalLog = "(Deathtouch)"; } else if (this.isInPlay() && !wither) { this.damage += damageToAdd; @@ -9138,6 +9126,35 @@ public class Card extends GameEntity implements Comparable { } 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 + List leftSplitAbilities = getState(CardCharacteristicName.LeftSplit).getSpellAbility(); + List rightSplitAbilities = getState(CardCharacteristicName.RightSplit).getSpellAbility(); + for (SpellAbility a : leftSplitAbilities) { + if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { + setState(CardCharacteristicName.LeftSplit); + return; + } + } + for (SpellAbility a : rightSplitAbilities) { + if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { + setState(CardCharacteristicName.RightSplit); + return; + } + } + throw new RuntimeException("Not found which part to choose for ability " + sa + " from card " + this); + } + } // end Card class diff --git a/src/main/java/forge/FThreads.java b/src/main/java/forge/FThreads.java index 1c0a0aaa86a..a5e4be097b0 100644 --- a/src/main/java/forge/FThreads.java +++ b/src/main/java/forge/FThreads.java @@ -15,6 +15,7 @@ import forge.control.input.InputSynchronized; * */ public class FThreads { + static { System.out.printf("(FThreads static ctor): Running on a machine with %d cpu core(s)%n", Runtime.getRuntime().availableProcessors() ); } @@ -123,5 +124,9 @@ public class FThreads { public static void delay(int milliseconds, Runnable inputUpdater) { getScheduledPool().schedule(inputUpdater, milliseconds, TimeUnit.MILLISECONDS); } + + public static String debugGetCurrThreadId() { + return isEDT() ? "EDT" : Long.toString(Thread.currentThread().getId()); + } } diff --git a/src/main/java/forge/card/CardFace.java b/src/main/java/forge/card/CardFace.java index da406256ae8..dd2d75df76b 100644 --- a/src/main/java/forge/card/CardFace.java +++ b/src/main/java/forge/card/CardFace.java @@ -58,7 +58,7 @@ final class CardFace implements ICardFace { @Override public CardType getType() { return this.type; } @Override public ManaCost getManaCost() { return this.manaCost; } @Override public ColorSet getColor() { return this.color; } - + // these are raw and unparsed used for Card creation @Override public Iterable getKeywords() { return keywords; } @Override public Iterable getAbilities() { return abilities; } @@ -114,6 +114,4 @@ final class CardFace implements ICardFace { if ( variables == null ) variables = emptyMap; if ( null == nonAbilityText ) nonAbilityText = ""; } - - } diff --git a/src/main/java/forge/card/CardRules.java b/src/main/java/forge/card/CardRules.java index 7c1549e469c..6d4ae58380a 100644 --- a/src/main/java/forge/card/CardRules.java +++ b/src/main/java/forge/card/CardRules.java @@ -39,6 +39,8 @@ public final class CardRules implements ICardCharacteristics { private final Map setsPrinted = new TreeMap(String.CASE_INSENSITIVE_ORDER); private CardAiHints aiHints; + + private ColorSet colorIdentity = null; public CardRules(ICardFace[] faces, CardSplitType altMode, CardAiHints cah, Map sets) { splitType = altMode; @@ -57,6 +59,45 @@ public final class CardRules implements ICardCharacteristics { System.err.println(getName() + " was not assigned any set."); setsPrinted.put(CardEdition.UNKNOWN.getCode(), new CardInSet(CardRarity.Common, 1) ); } + + //Calculate Color Identity + byte colMask = calculateColorIdentity(mainPart); + + if(otherPart != null) + { + colMask |= calculateColorIdentity(otherPart); + } + colorIdentity = ColorSet.fromMask(colMask); + } + + private byte calculateColorIdentity(ICardFace face) + { + byte res = face.getColor().getColor(); + boolean isReminder = false; + boolean isSymbol = false; + String oracleText = face.getOracleText(); + int len = oracleText.length(); + for(int i = 0; i < len; i++) { + char c = oracleText.charAt(i); // This is to avoid needless allocations performed by toCharArray() + switch(c) { + case('('): isReminder = true; break; + case(')'): isReminder = false; break; + case('{'): isSymbol = true; break; + case('}'): isSymbol = false; break; + default: + if(isSymbol && !isReminder) { + switch(c) { + case('W'): res |= MagicColor.WHITE; break; + case('U'): res |= MagicColor.BLUE; break; + case('B'): res |= MagicColor.BLACK; break; + case('R'): res |= MagicColor.RED; break; + case('G'): res |= MagicColor.GREEN; break; + } + } + break; + } + } + return res; } public boolean isTraditional() { @@ -177,4 +218,9 @@ public final class CardRules implements ICardCharacteristics { public final List getAbilities() { return null; } + + public ColorSet getColorIdentity() { + return colorIdentity; + + } } diff --git a/src/main/java/forge/card/CardRulesPredicates.java b/src/main/java/forge/card/CardRulesPredicates.java index a6723e4515a..37a5f18fd6e 100644 --- a/src/main/java/forge/card/CardRulesPredicates.java +++ b/src/main/java/forge/card/CardRulesPredicates.java @@ -455,10 +455,17 @@ public final class CardRulesPredicates { /** The Constant isCreature. */ public static final Predicate IS_CREATURE = CardRulesPredicates .coreType(true, CardCoreType.Creature); + + public static final Predicate IS_LEGENDARY = CardRulesPredicates + .superType(true, CardSuperType.Legendary); /** The Constant isArtifact. */ public static final Predicate IS_ARTIFACT = CardRulesPredicates .coreType(true, CardCoreType.Artifact); + + /** The Constant isEquipment. */ + public static final Predicate IS_EQUIPMENT = CardRulesPredicates + .subType("Equipment"); /** The Constant isLand. */ public static final Predicate IS_LAND = CardRulesPredicates.coreType(true, CardCoreType.Land); diff --git a/src/main/java/forge/card/ability/AbilityUtils.java b/src/main/java/forge/card/ability/AbilityUtils.java index f00452e5e84..95df7a8d2d3 100644 --- a/src/main/java/forge/card/ability/AbilityUtils.java +++ b/src/main/java/forge/card/ability/AbilityUtils.java @@ -219,6 +219,10 @@ public class AbilityUtils { else if (defined.startsWith("Tapped")) { list = sa.getRootAbility().getPaidList("Tapped"); } + + else if (defined.startsWith("Untapped")) { + list = sa.getRootAbility().getPaidList("Untapped"); + } else if (defined.startsWith("Valid ")) { String validDefined = defined.substring("Valid ".length()); @@ -286,250 +290,21 @@ public class AbilityUtils { * @return a int. */ public static int calculateAmount(final Card card, String amount, final SpellAbility ability) { - // amount can be anything, not just 'X' as long as sVar exists + // return empty strings and constants + if (StringUtils.isBlank(amount)) return 0; + final boolean startsWithPlus = amount.charAt(0) == '+'; + if(startsWithPlus) amount = amount.substring(1); - if (amount == null || amount.isEmpty()) { - return 0; - } - - // If Amount is -X, strip the minus sign before looking for an SVar of - // that kind - int multiplier = 1; - if (amount.startsWith("-")) { - multiplier = -1; + // Strip and save sign for calculations + boolean startsWithMinus = amount.charAt(0) == '-'; + int multiplier = startsWithMinus ? -1 : 1; + if(startsWithMinus) amount = amount.substring(1); - } else if (amount.startsWith("+")) { - amount = amount.substring(1); - } - String svarval; - if (ability != null) { + // return result soon for plain numbers + if (StringUtils.isNumeric(amount)) return Integer.parseInt(amount) * multiplier; - svarval = ability.getSVar(amount); - if (svarval.equals("")) { - try { - Integer.parseInt(amount); - } - catch (NumberFormatException ignored) { - //If this is reached, amount wasn't an integer - //Print a warning to console to help debug if an ability is not stolen properly. - StringBuilder sb = new StringBuilder("WARNING:SVar fallback to Card ("); - sb.append(card.getName()).append(") and Ability(").append(ability.toString()).append(")"); - System.out.println(sb.toString()); - svarval = card.getSVar(amount); - } - } - } else { - svarval = card.getSVar(amount); - } - - if (!svarval.equals("")) { - final String[] calcX = svarval.split("\\$"); - if ((calcX.length == 1) || calcX[1].equals("none")) { - return 0; - } - - if (calcX[0].startsWith("Count")) { - return AbilityUtils.xCount(card, calcX[1], ability) * multiplier; - } else if (calcX[0].startsWith("Number")) { - return CardFactoryUtil.xCount(card, svarval) * multiplier; - } else if (calcX[0].startsWith("SVar")) { - final String[] l = calcX[1].split("/"); - final String[] m = CardFactoryUtil.parseMath(l); - return CardFactoryUtil.doXMath(AbilityUtils.calculateAmount(card, l[0], ability), m, card) - * multiplier; - } else if (calcX[0].startsWith("PlayerCount")) { - final String hType = calcX[0].substring(11); - final ArrayList players = new ArrayList(); - if (hType.equals("Players") || hType.equals("")) { - players.addAll(Singletons.getModel().getGame().getPlayers()); - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } else if (hType.equals("Opponents")) { - players.addAll(card.getController().getOpponents()); - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } else if (hType.equals("Other")) { - players.addAll(card.getController().getAllOtherPlayers()); - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } else if (hType.equals("Remembered")) { - for (final Object o : card.getRemembered()) { - if (o instanceof Player) { - players.add((Player) o); - } - } - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } else if (hType.equals("NonActive")) { - players.addAll(Singletons.getModel().getGame().getPlayers()); - players.remove(Singletons.getModel().getGame().getPhaseHandler().getPlayerTurn()); - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } - } else if (calcX[0].startsWith("Remembered")) { - // Add whole Remembered list to handlePaid - final List list = new ArrayList(); - if (card.getRemembered().isEmpty()) { - final Card newCard = Singletons.getModel().getGame().getCardState(card); - for (final Object o : newCard.getRemembered()) { - if (o instanceof Card) { - list.add(Singletons.getModel().getGame().getCardState((Card) o)); - } - } - } - - if (calcX[0].endsWith("LKI")) { // last known information - for (final Object o : card.getRemembered()) { - if (o instanceof Card) { - list.add((Card) o); - } - } - } else { - for (final Object o : card.getRemembered()) { - if (o instanceof Card) { - list.add(Singletons.getModel().getGame().getCardState((Card) o)); - } - } - } - - return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; - } else if (calcX[0].startsWith("Imprinted")) { - // Add whole Imprinted list to handlePaid - final List list = new ArrayList(); - for (final Card c : card.getImprinted()) { - list.add(Singletons.getModel().getGame().getCardState(c)); - } - - return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; - } else if (calcX[0].matches("Enchanted")) { - // Add whole Enchanted list to handlePaid - final List list = new ArrayList(); - if (card.isEnchanting()) { - Object o = card.getEnchanting(); - if (o instanceof Card) { - list.add(Singletons.getModel().getGame().getCardState((Card) o)); - } - } - return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; - } else if (ability != null) { - // Player attribute counting - if (calcX[0].startsWith("TargetedPlayer")) { - final ArrayList players = new ArrayList(); - final SpellAbility saTargeting = ability.getSATargetingPlayer(); - if (null != saTargeting) { - players.addAll(saTargeting.getTarget().getTargetPlayers()); - } - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } - if (calcX[0].startsWith("TargetedObjects")) { - final ArrayList objects = new ArrayList(); - // Make list of all targeted objects starting with the root SpellAbility - SpellAbility loopSA = ability.getRootAbility(); - while (loopSA != null) { - if (loopSA.getTarget() != null) { - objects.addAll(loopSA.getTarget().getTargets()); - } - loopSA = loopSA.getSubAbility(); - } - return CardFactoryUtil.objectXCount(objects, calcX[1], card) * multiplier; - } - if (calcX[0].startsWith("TargetedController")) { - final ArrayList players = new ArrayList(); - final List list = getDefinedCards(card, "Targeted", ability); - final List sas = AbilityUtils.getDefinedSpellAbilities(card, "Targeted", - ability); - - for (final Card c : list) { - final Player p = c.getController(); - if (!players.contains(p)) { - players.add(p); - } - } - for (final SpellAbility s : sas) { - final Player p = s.getSourceCard().getController(); - if (!players.contains(p)) { - players.add(p); - } - } - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } - if (calcX[0].startsWith("TargetedByTarget")) { - final List tgtList = new ArrayList(); - final List saList = getDefinedSpellAbilities(card, "Targeted", ability); - - for (final SpellAbility s : saList) { - tgtList.addAll(getDefinedCards(s.getSourceCard(), "Targeted", s)); - } - return CardFactoryUtil.handlePaid(tgtList, calcX[1], card) * multiplier; - } - if (calcX[0].startsWith("TriggeredPlayer") || calcX[0].startsWith("TriggeredTarget")) { - final SpellAbility root = ability.getRootAbility(); - Object o = root.getTriggeringObject(calcX[0].substring(9)); - final List players = new ArrayList(); - if (o instanceof Player) { - players.add((Player) o); - } - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } - // Added on 9/30/12 (ArsenalNut) - Ended up not using but might be useful in future - /* - if (calcX[0].startsWith("EnchantedController")) { - final ArrayList players = new ArrayList(); - players.addAll(AbilityFactory.getDefinedPlayers(card, "EnchantedController", ability)); - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } - */ - - List list = new ArrayList(); - if (calcX[0].startsWith("Sacrificed")) { - list = ability.getRootAbility().getPaidList("Sacrificed"); - } else if (calcX[0].startsWith("Discarded")) { - final SpellAbility root = ability.getRootAbility(); - list = root.getPaidList("Discarded"); - if ((null == list) && root.isTrigger()) { - list = root.getSourceCard().getSpellPermanent().getPaidList("Discarded"); - } - } else if (calcX[0].startsWith("Exiled")) { - list = ability.getRootAbility().getPaidList("Exiled"); - } else if (calcX[0].startsWith("Tapped")) { - list = ability.getRootAbility().getPaidList("Tapped"); - } else if (calcX[0].startsWith("Revealed")) { - list = ability.getRootAbility().getPaidList("Revealed"); - } else if (calcX[0].startsWith("Targeted")) { - list = ability.findTargetedCards(); - } else if (calcX[0].startsWith("Triggered")) { - final SpellAbility root = ability.getRootAbility(); - list = new ArrayList(); - list.add((Card) root.getTriggeringObject(calcX[0].substring(9))); - } else if (calcX[0].startsWith("TriggerCount")) { - // TriggerCount is similar to a regular Count, but just - // pulls Integer Values from Trigger objects - final SpellAbility root = ability.getRootAbility(); - final String[] l = calcX[1].split("/"); - final String[] m = CardFactoryUtil.parseMath(l); - final int count = (Integer) root.getTriggeringObject(l[0]); - - return CardFactoryUtil.doXMath(count, m, card) * multiplier; - } else if (calcX[0].startsWith("Replaced")) { - final SpellAbility root = ability.getRootAbility(); - list = new ArrayList(); - list.add((Card) root.getReplacingObject(calcX[0].substring(8))); - } else if (calcX[0].startsWith("ReplaceCount")) { - // ReplaceCount is similar to a regular Count, but just - // pulls Integer Values from Replacement objects - final SpellAbility root = ability.getRootAbility(); - final String[] l = calcX[1].split("/"); - final String[] m = CardFactoryUtil.parseMath(l); - final int count = (Integer) root.getReplacingObject(l[0]); - - return CardFactoryUtil.doXMath(count, m, card) * multiplier; - } else { - - return 0; - } - - return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; - - } else { - return 0; - } - } + // These are some special cases - who is implementing them? if (amount.equals("ChosenX") || amount.equals("ChosenY")) { // isn't made yet return 0; @@ -539,7 +314,244 @@ public class AbilityUtils { return 0; } - return Integer.parseInt(amount) * multiplier; + // Try to fetch variable, try ability first, then card. + String svarval = null; + if (ability != null) { + svarval = ability.getSVar(amount); + } + if (StringUtils.isBlank(svarval)) { + if( ability != null) { + System.err.printf("SVar '%s' not found in ability, fallback to Card (%s). Ability is (%s)%n", amount, card.getName(), ability); + } + svarval = card.getSVar(amount); + } + + // Nothing to do here if value is missing or blank + if (StringUtils.isBlank(svarval)) { + System.err.printf("SVar '%s' not defined in Card (%s)%n", amount, card.getName()); + return 0; + } + + // Handle numeric constant coming in svar value + if( StringUtils.isNumeric(svarval) ) + return multiplier * Integer.parseInt(svarval); + + // Parse Object$Property string + final String[] calcX = svarval.split("\\$"); + + // Incorrect parses mean zero. + if ((calcX.length == 1) || calcX[1].equals("none")) + return 0; + + if (calcX[0].startsWith("Count")) + return AbilityUtils.xCount(card, calcX[1], ability) * multiplier; + + if (calcX[0].startsWith("Number")) + return CardFactoryUtil.xCount(card, svarval) * multiplier; + + if (calcX[0].startsWith("SVar")) { + final String[] l = calcX[1].split("/"); + final String m = CardFactoryUtil.extractOperators(calcX[1]); + return CardFactoryUtil.doXMath(AbilityUtils.calculateAmount(card, l[0], ability), m, card) * multiplier; + } + + if (calcX[0].startsWith("PlayerCount")) { + final String hType = calcX[0].substring(11); + final ArrayList players = new ArrayList(); + if (hType.equals("Players") || hType.equals("")) { + players.addAll(Singletons.getModel().getGame().getPlayers()); + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } else if (hType.equals("Opponents")) { + players.addAll(card.getController().getOpponents()); + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } else if (hType.equals("Other")) { + players.addAll(card.getController().getAllOtherPlayers()); + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } else if (hType.equals("Remembered")) { + for (final Object o : card.getRemembered()) { + if (o instanceof Player) { + players.add((Player) o); + } + } + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } else if (hType.equals("NonActive")) { + players.addAll(Singletons.getModel().getGame().getPlayers()); + players.remove(Singletons.getModel().getGame().getPhaseHandler().getPlayerTurn()); + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } + return 0; + } + + if (calcX[0].startsWith("Remembered")) { + // Add whole Remembered list to handlePaid + final List list = new ArrayList(); + if (card.getRemembered().isEmpty()) { + final Card newCard = Singletons.getModel().getGame().getCardState(card); + for (final Object o : newCard.getRemembered()) { + if (o instanceof Card) { + list.add(Singletons.getModel().getGame().getCardState((Card) o)); + } + } + } + + if (calcX[0].endsWith("LKI")) { // last known information + for (final Object o : card.getRemembered()) { + if (o instanceof Card) { + list.add((Card) o); + } + } + } else { + for (final Object o : card.getRemembered()) { + if (o instanceof Card) { + list.add(Singletons.getModel().getGame().getCardState((Card) o)); + } + } + } + + return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; + } + + if (calcX[0].startsWith("Imprinted")) { + // Add whole Imprinted list to handlePaid + final List list = new ArrayList(); + for (final Card c : card.getImprinted()) { + list.add(Singletons.getModel().getGame().getCardState(c)); + } + + return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; + } + + if (calcX[0].matches("Enchanted")) { + // Add whole Enchanted list to handlePaid + final List list = new ArrayList(); + if (card.isEnchanting()) { + Object o = card.getEnchanting(); + if (o instanceof Card) { + list.add(Singletons.getModel().getGame().getCardState((Card) o)); + } + } + return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; + } + + if (ability == null) + return 0; + + // Player attribute counting + if (calcX[0].startsWith("TargetedPlayer")) { + final ArrayList players = new ArrayList(); + final SpellAbility saTargeting = ability.getSATargetingPlayer(); + if (null != saTargeting) { + players.addAll(saTargeting.getTarget().getTargetPlayers()); + } + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } + if (calcX[0].startsWith("TargetedObjects")) { + final ArrayList objects = new ArrayList(); + // Make list of all targeted objects starting with the root SpellAbility + SpellAbility loopSA = ability.getRootAbility(); + while (loopSA != null) { + if (loopSA.getTarget() != null) { + objects.addAll(loopSA.getTarget().getTargets()); + } + loopSA = loopSA.getSubAbility(); + } + return CardFactoryUtil.objectXCount(objects, calcX[1], card) * multiplier; + } + if (calcX[0].startsWith("TargetedController")) { + final ArrayList players = new ArrayList(); + final List list = getDefinedCards(card, "Targeted", ability); + final List sas = AbilityUtils.getDefinedSpellAbilities(card, "Targeted", + ability); + + for (final Card c : list) { + final Player p = c.getController(); + if (!players.contains(p)) { + players.add(p); + } + } + for (final SpellAbility s : sas) { + final Player p = s.getSourceCard().getController(); + if (!players.contains(p)) { + players.add(p); + } + } + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } + if (calcX[0].startsWith("TargetedByTarget")) { + final List tgtList = new ArrayList(); + final List saList = getDefinedSpellAbilities(card, "Targeted", ability); + + for (final SpellAbility s : saList) { + tgtList.addAll(getDefinedCards(s.getSourceCard(), "Targeted", s)); + } + return CardFactoryUtil.handlePaid(tgtList, calcX[1], card) * multiplier; + } + if (calcX[0].startsWith("TriggeredPlayer") || calcX[0].startsWith("TriggeredTarget")) { + final SpellAbility root = ability.getRootAbility(); + Object o = root.getTriggeringObject(calcX[0].substring(9)); + final List players = new ArrayList(); + if (o instanceof Player) { + players.add((Player) o); + } + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } + // Added on 9/30/12 (ArsenalNut) - Ended up not using but might be useful in future + /* + if (calcX[0].startsWith("EnchantedController")) { + final ArrayList players = new ArrayList(); + players.addAll(AbilityFactory.getDefinedPlayers(card, "EnchantedController", ability)); + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } + */ + + List list = new ArrayList(); + if (calcX[0].startsWith("Sacrificed")) { + list = ability.getRootAbility().getPaidList("Sacrificed"); + } else if (calcX[0].startsWith("Discarded")) { + final SpellAbility root = ability.getRootAbility(); + list = root.getPaidList("Discarded"); + if ((null == list) && root.isTrigger()) { + list = root.getSourceCard().getSpellPermanent().getPaidList("Discarded"); + } + } else if (calcX[0].startsWith("Exiled")) { + list = ability.getRootAbility().getPaidList("Exiled"); + } else if (calcX[0].startsWith("Tapped")) { + list = ability.getRootAbility().getPaidList("Tapped"); + } else if (calcX[0].startsWith("Revealed")) { + list = ability.getRootAbility().getPaidList("Revealed"); + } else if (calcX[0].startsWith("Targeted")) { + list = ability.findTargetedCards(); + } else if (calcX[0].startsWith("Triggered")) { + final SpellAbility root = ability.getRootAbility(); + list = new ArrayList(); + list.add((Card) root.getTriggeringObject(calcX[0].substring(9))); + } else if (calcX[0].startsWith("TriggerCount")) { + // TriggerCount is similar to a regular Count, but just + // pulls Integer Values from Trigger objects + final SpellAbility root = ability.getRootAbility(); + final String[] l = calcX[1].split("/"); + final String m = CardFactoryUtil.extractOperators(calcX[1]); + final int count = (Integer) root.getTriggeringObject(l[0]); + + return CardFactoryUtil.doXMath(count, m, card) * multiplier; + } else if (calcX[0].startsWith("Replaced")) { + final SpellAbility root = ability.getRootAbility(); + list = new ArrayList(); + list.add((Card) root.getReplacingObject(calcX[0].substring(8))); + } else if (calcX[0].startsWith("ReplaceCount")) { + // ReplaceCount is similar to a regular Count, but just + // pulls Integer Values from Replacement objects + final SpellAbility root = ability.getRootAbility(); + final String[] l = calcX[1].split("/"); + final String m = CardFactoryUtil.extractOperators(calcX[1]); + final int count = (Integer) root.getReplacingObject(l[0]); + + return CardFactoryUtil.doXMath(count, m, card) * multiplier; + } else { + return 0; + } + + return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; } /** @@ -619,7 +631,7 @@ public class AbilityUtils { } else if (type.startsWith("Targeted")) { source = null; - ArrayList tgts = sa.findTargetedCards(); + List tgts = sa.findTargetedCards(); if (!tgts.isEmpty()) { source = tgts.get(0); } @@ -829,6 +841,11 @@ public class AbilityUtils { } } } + } else if (defined.equals("NonReplacedPlayer")) { + final SpellAbility root = sa.getRootAbility(); + Player p = (Player) root.getReplacingObject("Player"); + players.addAll(sa.getActivatingPlayer().getGame().getPlayers()); + players.remove(p); } else if (defined.equals("EnchantedController")) { if (card.getEnchantingCard() == null) { return players; @@ -1102,7 +1119,7 @@ public class AbilityUtils { } } else { // if it's paid by the AI already the human can pay, but it won't change anything - paid |= GameActionUtil.payCostDuringAbilityResolve(payer, ability, cost, sa, game); + paid |= GameActionUtil.payCostDuringAbilityResolve(ability, cost, sa, game); } } @@ -1172,11 +1189,19 @@ public class AbilityUtils { private static boolean willAIPayForAbility(SpellAbility sa, Player payer, SpellAbility ability, boolean paid, List payers) { Card source = sa.getSourceCard(); boolean payForOwnOnly = "OnlyOwn".equals(sa.getParam("UnlessAI")); + boolean payOwner = sa.hasParam("UnlessAI") ? sa.getParam("UnlessAI").startsWith("Defined") : false; boolean payNever = "Never".equals(sa.getParam("UnlessAI")); boolean isMine = sa.getActivatingPlayer().equals(payer); if (payNever) { return false; } if (payForOwnOnly && !isMine) { return false; } + if (payOwner) { + final String defined = sa.getParam("UnlessAI").substring(7); + final Player player = AbilityUtils.getDefinedPlayers(source, defined, sa).get(0); + if (!payer.equals(player)) { + return false; + } + } // AI will only pay when it's not already payed and only opponents abilities if (paid || (payers.size() > 1 && (isMine && !payForOwnOnly))) { @@ -1214,7 +1239,7 @@ public class AbilityUtils { public static int xCount(final Card c, final String s, final SpellAbility sa) { final String[] l = s.split("/"); - final String[] m = CardFactoryUtil.parseMath(l); + final String expr = CardFactoryUtil.extractOperators(s); final String[] sq; sq = l[0].split("\\."); @@ -1223,9 +1248,9 @@ public class AbilityUtils { // Count$Kicked.. if (sq[0].startsWith("Kicked")) { if (sa.isKicked()) { - return CardFactoryUtil.doXMath(Integer.parseInt(sq[1]), m, c); // Kicked + return CardFactoryUtil.doXMath(Integer.parseInt(sq[1]), expr, c); // Kicked } else { - return CardFactoryUtil.doXMath(Integer.parseInt(sq[2]), m, c); // not Kicked + return CardFactoryUtil.doXMath(Integer.parseInt(sq[2]), expr, c); // not Kicked } } @@ -1235,9 +1260,9 @@ public class AbilityUtils { final int lhs = calculateAmount(c, compString[1], sa); final int rhs = calculateAmount(c, compString[2].substring(2), sa); if (Expressions.compare(lhs, compString[2], rhs)) { - return CardFactoryUtil.doXMath(Integer.parseInt(sq[1]), m, c); + return CardFactoryUtil.doXMath(Integer.parseInt(sq[1]), expr, c); } else { - return CardFactoryUtil.doXMath(Integer.parseInt(sq[2]), m, c); + return CardFactoryUtil.doXMath(Integer.parseInt(sq[2]), expr, c); } } } diff --git a/src/main/java/forge/card/ability/ApiType.java b/src/main/java/forge/card/ability/ApiType.java index 0459b15f268..ae794f7c796 100644 --- a/src/main/java/forge/card/ability/ApiType.java +++ b/src/main/java/forge/card/ability/ApiType.java @@ -10,6 +10,7 @@ import forge.card.ability.ai.AlwaysPlayAi; import forge.card.ability.ai.AnimateAi; import forge.card.ability.ai.AnimateAllAi; import forge.card.ability.ai.AttachAi; +import forge.card.ability.ai.BecomesBlockedAi; import forge.card.ability.ai.BondAi; import forge.card.ability.ai.CanPlayAsDrawbackAi; import forge.card.ability.ai.CannotPlayAi; @@ -112,6 +113,7 @@ public enum ApiType { Animate (AnimateEffect.class, AnimateAi.class), AnimateAll (AnimateAllEffect.class, AnimateAllAi.class), Attach (AttachEffect.class, AttachAi.class), + BecomesBlocked (BecomesBlockedEffect.class, BecomesBlockedAi.class), Bond (BondEffect.class, BondAi.class), ChangeZone(ChangeZoneEffect.class, ChangeZoneAi.class), ChangeZoneAll(ChangeZoneAllEffect.class, ChangeZoneAllAi.class), diff --git a/src/main/java/forge/card/ability/ai/BecomesBlockedAi.java b/src/main/java/forge/card/ability/ai/BecomesBlockedAi.java new file mode 100644 index 00000000000..a5c6609c673 --- /dev/null +++ b/src/main/java/forge/card/ability/ai/BecomesBlockedAi.java @@ -0,0 +1,65 @@ +package forge.card.ability.ai; + + +import java.util.List; + +import forge.Card; +import forge.CardLists; +import forge.Singletons; +import forge.card.ability.SpellAbilityAi; +import forge.card.spellability.SpellAbility; +import forge.card.spellability.Target; +import forge.game.ai.ComputerUtilCard; +import forge.game.player.AIPlayer; +import forge.game.zone.ZoneType; + +public class BecomesBlockedAi extends SpellAbilityAi { + + @Override + protected boolean canPlayAI(AIPlayer aiPlayer, SpellAbility sa) { + final Card source = sa.getSourceCard(); + final Target tgt = sa.getTarget(); + + List list = Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield); + list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source); + list = CardLists.getTargetableCards(list, sa); + + while (tgt.getNumTargeted() < tgt.getMaxTargets(source, sa)) { + Card choice = null; + + if (list.isEmpty()) { + return false; + } + + choice = ComputerUtilCard.getBestCreatureAI(list); + + if (choice == null) { // can't find anything left + return false; + } + + list.remove(choice); + tgt.addTarget(choice); + } + return true; + } + + @Override + public boolean chkAIDrawback(SpellAbility sa, AIPlayer aiPlayer) { + + // TODO - implement AI + return false; + } + + /* (non-Javadoc) + * @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean) + */ + @Override + protected boolean doTriggerAINoCost(AIPlayer aiPlayer, SpellAbility sa, boolean mandatory) { + boolean chance; + + // TODO - implement AI + chance = false; + + return chance; + } +} diff --git a/src/main/java/forge/card/ability/ai/ChangeZoneAi.java b/src/main/java/forge/card/ability/ai/ChangeZoneAi.java index d7a73922bf8..24638acd72b 100644 --- a/src/main/java/forge/card/ability/ai/ChangeZoneAi.java +++ b/src/main/java/forge/card/ability/ai/ChangeZoneAi.java @@ -1071,7 +1071,7 @@ public class ChangeZoneAi extends SpellAbilityAi { if (tgt != null) { if (!tgt.getTargetPlayers().isEmpty()) { - player = player != null ? player : tgt.getTargetPlayers().get(0); + player = sa.hasParam("DefinedPlayer") ? player : tgt.getTargetPlayers().get(0); if (!player.canBeTargetedBy(sa)) { return; } diff --git a/src/main/java/forge/card/ability/ai/CounterAi.java b/src/main/java/forge/card/ability/ai/CounterAi.java index 9e084635dfb..2cf860964d1 100644 --- a/src/main/java/forge/card/ability/ai/CounterAi.java +++ b/src/main/java/forge/card/ability/ai/CounterAi.java @@ -8,7 +8,6 @@ import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; -import forge.card.spellability.TargetSelection; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilMana; @@ -50,7 +49,7 @@ public class CounterAi extends SpellAbilityAi { } tgt.resetTargets(); - if (TargetSelection.matchSpellAbility(sa, topSA, tgt)) { + if (sa.canTargetSpellAbility(topSA)) { tgt.addTarget(topSA); } else { return false; @@ -120,7 +119,7 @@ public class CounterAi extends SpellAbilityAi { } tgt.resetTargets(); - if (TargetSelection.matchSpellAbility(sa, topSA, tgt)) { + if (sa.canTargetSpellAbility(topSA)) { tgt.addTarget(topSA); } else { return false; diff --git a/src/main/java/forge/card/ability/ai/CountersPutAi.java b/src/main/java/forge/card/ability/ai/CountersPutAi.java index 18c0e4c67c3..0fcc4087c96 100644 --- a/src/main/java/forge/card/ability/ai/CountersPutAi.java +++ b/src/main/java/forge/card/ability/ai/CountersPutAi.java @@ -49,8 +49,7 @@ public class CountersPutAi extends SpellAbilityAi { list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), new Predicate() { @Override public boolean apply(final Card c) { - return c.canBeTargetedBy(sa) && !c.hasKeyword("CARDNAME can't have counters placed on it.") - && !(c.hasKeyword("CARDNAME can't have -1/-1 counters placed on it.") && type.equals("M1M1")); + return c.canBeTargetedBy(sa) && c.canHaveCountersPlacedOnIt(CounterType.valueOf(type)); } }); diff --git a/src/main/java/forge/card/ability/ai/DamageDealAi.java b/src/main/java/forge/card/ability/ai/DamageDealAi.java index f554e23c259..baac5c59c1d 100644 --- a/src/main/java/forge/card/ability/ai/DamageDealAi.java +++ b/src/main/java/forge/card/ability/ai/DamageDealAi.java @@ -15,7 +15,6 @@ import forge.card.cost.Cost; import forge.card.spellability.AbilitySub; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; -import forge.card.spellability.TargetSelection; import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCombat; @@ -103,8 +102,7 @@ public class DamageDealAi extends DamageAiBase { if (tgt != null && tgt.getTargetPlayers().isEmpty() && !sa.hasParam("DividedAsYouChoose")) { int actualPay = 0; final boolean noPrevention = sa.hasParam("NoPrevention"); - final ArrayList cards = tgt.getTargetCards(); - for (final Card c : cards) { + for (final Card c : tgt.getTargetCards()) { final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention); if ((adjDamage > actualPay) && (adjDamage <= dmg)) { actualPay = adjDamage; @@ -144,7 +142,7 @@ public class DamageDealAi extends DamageAiBase { final ArrayList objects = tgt.getTargets(); if (saMe.hasParam("TargetUnique")) { - objects.addAll(TargetSelection.getUniqueTargets(saMe)); + objects.addAll(saMe.getUniqueTargets()); } for (final Object o : objects) { if (o instanceof Card) { @@ -472,7 +470,7 @@ public class DamageDealAi extends DamageAiBase { // If I can kill my target by paying less mana, do it int actualPay = 0; final boolean noPrevention = sa.hasParam("NoPrevention"); - final ArrayList cards = tgt.getTargetCards(); + final List cards = tgt.getTargetCards(); //target is a player if (cards.isEmpty()) { actualPay = dmg; diff --git a/src/main/java/forge/card/ability/ai/DrawAi.java b/src/main/java/forge/card/ability/ai/DrawAi.java index decde1c2179..d7cff15a780 100644 --- a/src/main/java/forge/card/ability/ai/DrawAi.java +++ b/src/main/java/forge/card/ability/ai/DrawAi.java @@ -18,7 +18,7 @@ */ package forge.card.ability.ai; -import java.util.ArrayList; +import java.util.List; import java.util.Random; import forge.Card; @@ -96,7 +96,7 @@ public class DrawAi extends SpellAbilityAi { } if (tgt != null) { - final ArrayList players = tgt.getTargetPlayers(); + final List players = tgt.getTargetPlayers(); if ((players.size() > 0) && players.get(0).isOpponentOf(ai)) { return true; } diff --git a/src/main/java/forge/card/ability/ai/PeekAndRevealAi.java b/src/main/java/forge/card/ability/ai/PeekAndRevealAi.java index b7c5c273750..d4175d770b5 100644 --- a/src/main/java/forge/card/ability/ai/PeekAndRevealAi.java +++ b/src/main/java/forge/card/ability/ai/PeekAndRevealAi.java @@ -17,7 +17,7 @@ public class PeekAndRevealAi extends SpellAbilityAi { protected boolean canPlayAI(AIPlayer aiPlayer, SpellAbility sa) { // So far this only appears on Triggers, but will expand // once things get converted from Dig + NoMove - return false; + return true; } } diff --git a/src/main/java/forge/card/ability/effects/BecomesBlockedEffect.java b/src/main/java/forge/card/ability/effects/BecomesBlockedEffect.java new file mode 100644 index 00000000000..10d1922ad99 --- /dev/null +++ b/src/main/java/forge/card/ability/effects/BecomesBlockedEffect.java @@ -0,0 +1,54 @@ +package forge.card.ability.effects; + +import java.util.HashMap; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +import forge.Card; +import forge.Singletons; +import forge.card.ability.SpellAbilityEffect; +import forge.card.cardfactory.CardFactoryUtil; +import forge.card.spellability.Ability; +import forge.card.spellability.SpellAbility; +import forge.card.spellability.Target; +import forge.card.trigger.TriggerType; + +public class BecomesBlockedEffect extends SpellAbilityEffect { + + @Override + protected String getStackDescription(SpellAbility sa) { + final StringBuilder sb = new StringBuilder(); + + final List tgtCards = getTargetCards(sa); + + sb.append(StringUtils.join(tgtCards, ", ")); + sb.append(" becomes blocked."); + + return sb.toString(); + } + + @Override + public void resolve(SpellAbility sa) { + + final Target tgt = sa.getTarget(); + for (final Card c : getTargetCards(sa)) { + if ((tgt == null) || c.canBeTargetedBy(sa)) { + Singletons.getModel().getGame().getCombat().setBlocked(c); + if (!c.getDamageHistory().getCreatureGotBlockedThisCombat()) { + final HashMap runParams = new HashMap(); + runParams.put("Attacker", c); + runParams.put("Blocker", null); + runParams.put("NumBlockers", 0); + Singletons.getModel().getGame().getTriggerHandler().runTrigger(TriggerType.AttackerBlocked, runParams, false); + + // Bushido + for (final Ability ab : CardFactoryUtil.getBushidoEffects(c)) { + Singletons.getModel().getGame().getStack().add(ab); + } + } + } + } + + } +} diff --git a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java index 96996ba07d1..8ea33b4d38e 100644 --- a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java +++ b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java @@ -22,12 +22,14 @@ import forge.card.spellability.Target; import forge.card.trigger.TriggerType; import forge.game.ai.ComputerUtilCard; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; import forge.gui.GuiDialog; import forge.util.Aggregates; +import forge.util.Lang; public class ChangeZoneEffect extends SpellAbilityEffect { @Override @@ -63,10 +65,46 @@ public class ChangeZoneEffect extends SpellAbilityEffect { final Card host = sa.getSourceCard(); if (!(sa instanceof AbilitySub)) { - sb.append(host.getName()).append(" -"); + sb.append(" -"); } sb.append(" "); + + // Player whose cards will change zones + List fetchers = new ArrayList(); + if (sa.hasParam("DefinedPlayer")) { + fetchers = AbilityUtils.getDefinedPlayers(sa.getSourceCard(), sa.getParam("DefinedPlayer"), sa); + } + if (fetchers.isEmpty() && sa.hasParam("ValidTgts") && sa.getTarget() != null) { + fetchers = sa.getTarget().getTargetPlayers(); + } + if (fetchers.isEmpty()) { + fetchers.add(sa.getSourceCard().getController()); + } + + final String fetcherNames = Lang.joinHomogenous(fetchers, Player.Accessors.FN_GET_NAME); + + // Player who chooses the cards to move + List choosers = new ArrayList(); + if (sa.hasParam("Chooser")) { + choosers = AbilityUtils.getDefinedPlayers(sa.getSourceCard(), sa.getParam("Chooser"), sa); + } + if (choosers.isEmpty()) { + choosers.add(sa.getActivatingPlayer()); + } + + final StringBuilder chooserSB = new StringBuilder(); + for (int i = 0; i < choosers.size(); i++) { + chooserSB.append(choosers.get(i).getName()); + chooserSB.append((i + 2) == choosers.size() ? " and " : (i + 1) == choosers.size() ? "" : ", "); + } + final String chooserNames = chooserSB.toString(); + + String fetchPlayer = fetcherNames; + if (chooserNames.equals(fetcherNames)) { + fetchPlayer = fetchers.size() > 1 ? "their" : "his/her"; + } + String origin = ""; if (sa.hasParam("Origin")) { origin = sa.getParam("Origin"); @@ -90,49 +128,79 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } sb.append("."); } else if (origin.equals("Library")) { - sb.append("Search your library for ").append(num).append(" ").append(type).append(" and "); + sb.append(chooserNames); + sb.append(" search").append(choosers.size() > 1 ? " " : "es "); + sb.append(fetchPlayer); + sb.append("'s library for ").append(num).append(" ").append(type).append(" and "); - if (num == 1) { - sb.append("put that card "); + if (destination.equals("Exile")) { + if (num == 1) { + sb.append("exiles that card "); + } else { + sb.append("exiles those cards "); + } } else { - sb.append("put those cards "); + if (num == 1) { + sb.append("puts that card "); + } else { + sb.append("puts those cards "); + } + + if (destination.equals("Battlefield")) { + sb.append("onto the battlefield"); + if (sa.hasParam("Tapped")) { + sb.append(" tapped"); + } + if (sa.hasParam("GainControl")) { + sb.append(" under ").append(chooserNames).append("'s control"); + } + + sb.append("."); + + } + if (destination.equals("Hand")) { + sb.append("into its owner's hand."); + } + if (destination.equals("Graveyard")) { + sb.append("into its owners's graveyard."); + } + } + sb.append(" Then shuffle that library."); + } else if (origin.equals("Hand")) { + sb.append(chooserNames); + if (!chooserNames.equals(fetcherNames)) { + sb.append(" looks at " + fetcherNames + "'s hand and "); + sb.append(destination.equals("Exile") ? "exiles " : "puts "); + sb.append(num).append(" of those ").append(type).append(" card(s)"); + } else { + sb.append(destination.equals("Exile") ? " exiles " : " puts "); + sb.append(num).append(" ").append(type).append(" card(s) from"); + sb.append(fetchPlayer).append(" hand"); } if (destination.equals("Battlefield")) { - sb.append("onto the battlefield"); + sb.append(" onto the battlefield"); if (sa.hasParam("Tapped")) { sb.append(" tapped"); } - - sb.append("."); - - } - if (destination.equals("Hand")) { - sb.append("into your hand."); - } - if (destination.equals("Graveyard")) { - sb.append("into your graveyard."); - } - - sb.append(" Then shuffle your library."); - } else if (origin.equals("Hand")) { - sb.append("Put ").append(num).append(" ").append(type).append(" card(s) from your hand "); - - if (destination.equals("Battlefield")) { - sb.append("onto the battlefield."); + if (sa.hasParam("GainControl")) { + sb.append(" under ").append(chooserNames).append("'s control"); + } } if (destination.equals("Library")) { final int libraryPos = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : 0; if (libraryPos == 0) { - sb.append("on top"); + sb.append(" on top"); } if (libraryPos == -1) { - sb.append("on bottom"); + sb.append(" on the bottom"); } - sb.append(" of your library."); + sb.append(" of ").append(fetchPlayer).append("'s library"); } + + sb.append("."); } else if (origin.equals("Battlefield")) { // TODO Expand on this Description as more cards use it // for the non-targeted SAs when you choose what is returned on @@ -171,7 +239,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { final StringBuilder sbTargets = new StringBuilder(); - ArrayList tgts; + List tgts; if (sa.getTarget() != null) { tgts = sa.getTarget().getTargetCards(); } else { @@ -301,8 +369,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect { * a {@link forge.card.spellability.SpellAbility} object. */ private static void changeKnownOriginResolve(final SpellAbility sa) { - ArrayList tgtCards; - ArrayList sas; + List tgtCards; + List sas; final Target tgt = sa.getTarget(); final Player player = sa.getActivatingPlayer(); @@ -527,7 +595,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (decider instanceof AIPlayer) { ChangeZoneAi.hiddenOriginResolveAI(decider, sa, player); } else { - changeHiddenOriginResolveHuman(sa, player); + changeHiddenOriginResolveHuman((HumanPlayer) decider, sa, player); } } } @@ -544,7 +612,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { * @param player * a {@link forge.game.player.Player} object. */ - private static void changeHiddenOriginResolveHuman(final SpellAbility sa, Player player) { + private static void changeHiddenOriginResolveHuman(final HumanPlayer decider, final SpellAbility sa, Player player) { final Card card = sa.getSourceCard(); final List movedCards = new ArrayList(); final boolean defined = sa.hasParam("Defined"); @@ -552,8 +620,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect { final Target tgt = sa.getTarget(); if (tgt != null) { - final ArrayList players = tgt.getTargetPlayers(); - player = player != null ? player : players.get(0); + final List players = tgt.getTargetPlayers(); + player = sa.hasParam("DefinedPlayer") ? player : players.get(0); if (players.contains(player) && !player.canBeTargetedBy(sa)) { return; } @@ -622,7 +690,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } // Look at opponents hand before moving onto choosing a card - if (origin.contains(ZoneType.Hand) && player.isOpponentOf(player)) { + if (origin.contains(ZoneType.Hand) && player.isOpponentOf(decider)) { GuiChoose.oneOrNone(sa.getSourceCard().getName() + " - Looking at Opponent's Hand", player .getCardsIn(ZoneType.Hand)); } diff --git a/src/main/java/forge/card/ability/effects/ChooseColorEffect.java b/src/main/java/forge/card/ability/effects/ChooseColorEffect.java index 5a89059afd1..cfefeffb9af 100644 --- a/src/main/java/forge/card/ability/effects/ChooseColorEffect.java +++ b/src/main/java/forge/card/ability/effects/ChooseColorEffect.java @@ -46,7 +46,7 @@ public class ChooseColorEffect extends SpellAbilityEffect { for (final Player p : tgtPlayers) { if ((tgt == null) || p.canBeTargetedBy(sa)) { - if (sa.getActivatingPlayer().isHuman()) { + if (p.isHuman()) { if (sa.hasParam("OrColors")) { ImmutableList choices = Constant.Color.ONLY_COLORS; final List o = GuiChoose.getChoices("Choose a color or colors", 1, choices.size(), choices); diff --git a/src/main/java/forge/card/ability/effects/ChooseNumberEffect.java b/src/main/java/forge/card/ability/effects/ChooseNumberEffect.java index 85e0a511038..ddfc6b971ac 100644 --- a/src/main/java/forge/card/ability/effects/ChooseNumberEffect.java +++ b/src/main/java/forge/card/ability/effects/ChooseNumberEffect.java @@ -68,7 +68,7 @@ public class ChooseNumberEffect extends SpellAbilityEffect { for (final Player p : tgtPlayers) { if ((tgt == null) || p.canBeTargetedBy(sa)) { - if (sa.getActivatingPlayer().isHuman()) { + if (p.isHuman()) { int chosen; if (random) { final Random randomGen = new Random(); diff --git a/src/main/java/forge/card/ability/effects/ChooseTypeEffect.java b/src/main/java/forge/card/ability/effects/ChooseTypeEffect.java index 72cd09f3c13..59c7c6bfb65 100644 --- a/src/main/java/forge/card/ability/effects/ChooseTypeEffect.java +++ b/src/main/java/forge/card/ability/effects/ChooseTypeEffect.java @@ -60,7 +60,7 @@ public class ChooseTypeEffect extends SpellAbilityEffect { } boolean valid = false; while (!valid) { - if (sa.getActivatingPlayer().isHuman()) { + if (p.isHuman()) { final Object o = GuiChoose.one("Choose a card type", validTypes); if (null == o) { return; @@ -82,14 +82,14 @@ public class ChooseTypeEffect extends SpellAbilityEffect { String chosenType = ""; boolean valid = false; while (!valid) { - if (sa.getActivatingPlayer().isHuman()) { + if (p.isHuman()) { final ArrayList validChoices = CardType.getCreatureTypes(); for (final String s : invalidTypes) { validChoices.remove(s); } chosenType = GuiChoose.one("Choose a creature type", validChoices); } else { - Player ai = sa.getActivatingPlayer(); + Player ai = p; Player opp = ai.getOpponent(); String chosen = ""; if (sa.hasParam("AILogic")) { @@ -128,7 +128,7 @@ public class ChooseTypeEffect extends SpellAbilityEffect { } else if (type.equals("Basic Land")) { boolean valid = false; while (!valid) { - if (sa.getActivatingPlayer().isHuman()) { + if (p.isHuman()) { final String choice = GuiChoose.one("Choose a basic land type", CardType.getBasicTypes()); if (null == choice) { return; @@ -138,7 +138,7 @@ public class ChooseTypeEffect extends SpellAbilityEffect { card.setChosenType(choice); } } else { - Player ai = sa.getActivatingPlayer(); + Player ai = p; String chosen = ""; if (sa.hasParam("AILogic")) { final String logic = sa.getParam("AILogic"); @@ -176,7 +176,7 @@ public class ChooseTypeEffect extends SpellAbilityEffect { } else if (type.equals("Land")) { boolean valid = false; while (!valid) { - if (sa.getActivatingPlayer().isHuman()) { + if (p.isHuman()) { final String choice = GuiChoose .one("Choose a land type", CardType.getLandTypes()); if (null == choice) { diff --git a/src/main/java/forge/card/ability/effects/ControlExchangeEffect.java b/src/main/java/forge/card/ability/effects/ControlExchangeEffect.java index 5f63b87e90b..1b22e74431e 100644 --- a/src/main/java/forge/card/ability/effects/ControlExchangeEffect.java +++ b/src/main/java/forge/card/ability/effects/ControlExchangeEffect.java @@ -1,6 +1,7 @@ package forge.card.ability.effects; import java.util.ArrayList; +import java.util.List; import forge.Card; import forge.Singletons; @@ -21,12 +22,16 @@ public class ControlExchangeEffect extends SpellAbilityEffect { Card object1 = null; Card object2 = null; final Target tgt = sa.getTarget(); - ArrayList tgts = tgt.getTargetCards(); + List tgts = tgt == null ? new ArrayList() : tgt.getTargetCards(); if (tgts.size() > 0) { object1 = tgts.get(0); } if (sa.hasParam("Defined")) { - object2 = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa).get(0); + List cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa); + object2 = cards.isEmpty() ? null : cards.get(0); + if (cards.size() > 1 && sa.hasParam("BothDefined")) { + object1 = cards.get(1); + } } else if (tgts.size() > 1) { object2 = tgts.get(1); } @@ -42,12 +47,16 @@ public class ControlExchangeEffect extends SpellAbilityEffect { Card object1 = null; Card object2 = null; final Target tgt = sa.getTarget(); - ArrayList tgts = tgt.getTargetCards(); + List tgts = tgt == null ? new ArrayList() : tgt.getTargetCards(); if (tgts.size() > 0) { object1 = tgts.get(0); } if (sa.hasParam("Defined")) { - object2 = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa).get(0); + final List cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa); + object2 = cards.isEmpty() ? null : cards.get(0); + if (cards.size() > 1 && sa.hasParam("BothDefined")) { + object1 = cards.get(1); + } } else if (tgts.size() > 1) { object2 = tgts.get(1); } diff --git a/src/main/java/forge/card/ability/effects/ControlGainEffect.java b/src/main/java/forge/card/ability/effects/ControlGainEffect.java index e0263bd79d3..839d143435f 100644 --- a/src/main/java/forge/card/ability/effects/ControlGainEffect.java +++ b/src/main/java/forge/card/ability/effects/ControlGainEffect.java @@ -197,9 +197,9 @@ public class ControlGainEffect extends SpellAbilityEffect { public void resolve() { if (bNoRegen) { - Singletons.getModel().getGame().getAction().destroyNoRegeneration(c); + Singletons.getModel().getGame().getAction().destroyNoRegeneration(c, null); } else { - Singletons.getModel().getGame().getAction().destroy(c); + Singletons.getModel().getGame().getAction().destroy(c, null); } } }; diff --git a/src/main/java/forge/card/ability/effects/CopyPermanentEffect.java b/src/main/java/forge/card/ability/effects/CopyPermanentEffect.java index 81520c3faab..f9cdecb4a34 100644 --- a/src/main/java/forge/card/ability/effects/CopyPermanentEffect.java +++ b/src/main/java/forge/card/ability/effects/CopyPermanentEffect.java @@ -6,10 +6,17 @@ import java.util.List; import org.apache.commons.lang3.StringUtils; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + import forge.Card; import forge.CardCharacteristicName; +import forge.CardLists; import forge.Command; import forge.Singletons; +import forge.card.CardRulesPredicates; import forge.card.ability.AbilityUtils; import forge.card.ability.SpellAbilityEffect; import forge.card.cardfactory.CardFactory; @@ -18,8 +25,15 @@ import forge.card.mana.ManaCost; import forge.card.spellability.Ability; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.game.ai.ComputerUtilCard; +import forge.game.player.HumanPlayer; import forge.game.player.Player; +import forge.game.zone.ZoneType; +import forge.gui.GuiChoose; import forge.item.CardDb; +import forge.item.CardPrinted; +import forge.util.Aggregates; +import forge.util.PredicateString.StringOp; public class CopyPermanentEffect extends SpellAbilityEffect { @@ -36,6 +50,9 @@ public class CopyPermanentEffect extends SpellAbilityEffect { return sb.toString(); } + /* (non-Javadoc) + * @see forge.card.ability.SpellAbilityEffect#resolve(forge.card.spellability.SpellAbility) + */ @Override public void resolve(final SpellAbility sa) { final Card hostCard = sa.getSourceCard(); @@ -46,9 +63,47 @@ public class CopyPermanentEffect extends SpellAbilityEffect { final int numCopies = sa.hasParam("NumCopies") ? AbilityUtils.calculateAmount(hostCard, sa.getParam("NumCopies"), sa) : 1; - final List tgtCards = getTargetCards(sa); + List tgtCards = getTargetCards(sa); final Target tgt = sa.getTarget(); + if (sa.hasParam("ValidSupportedCopy")) { + List cards = Lists.newArrayList(CardDb.instance().getUniqueCards()); + String valid = sa.getParam("ValidSupportedCopy"); + if (valid.contains("X")) { + valid = valid.replace("X", Integer.toString(AbilityUtils.calculateAmount(hostCard, "X", sa))); + } + if (StringUtils.containsIgnoreCase(valid, "creature")) { + Predicate cpp = Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, CardPrinted.FN_GET_RULES); + cards = Lists.newArrayList(Iterables.filter(cards, cpp)); + } + if (StringUtils.containsIgnoreCase(valid, "equipment")) { + Predicate cpp = Predicates.compose(CardRulesPredicates.Presets.IS_EQUIPMENT, CardPrinted.FN_GET_RULES); + cards = Lists.newArrayList(Iterables.filter(cards, cpp)); + } + if (sa.hasParam("RandomCopied")) { + List copysource = new ArrayList(cards); + List choice = new ArrayList(); + final String num = sa.hasParam("RandomNum") ? sa.getParam("RandomNum") : "1"; + int ncopied = AbilityUtils.calculateAmount(hostCard, num, sa); + while(ncopied > 0) { + final CardPrinted cp = Aggregates.random(copysource); + if (cp.getMatchingForgeCard().isValid(valid, hostCard.getController(), hostCard)) { + choice.add(cp.getMatchingForgeCard()); + copysource.remove(cp); + ncopied -= 1; + } + } + tgtCards = choice; + } else if (sa.hasParam("DefinedName")) { + final String name = sa.getParam("DefinedName"); + Predicate cpp = Predicates.compose(CardRulesPredicates.name(StringOp.EQUALS, name), CardPrinted.FN_GET_RULES); + cards = Lists.newArrayList(Iterables.filter(cards, cpp)); + Card c = cards.get(0).getMatchingForgeCard(); + tgtCards.clear(); + tgtCards.add(c); + } + } + Player controller = null; if (sa.hasParam("Controller")) { List defined = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("Controller"), sa); @@ -137,6 +192,38 @@ public class CopyPermanentEffect extends SpellAbilityEffect { if (c.isFaceDown()) { c.setState(CardCharacteristicName.FaceDown); } + + if (sa.hasParam("AttachedTo")) { + List list = AbilityUtils.getDefinedCards(hostCard, + sa.getParam("AttachedTo"), sa); + if (list.isEmpty()) { + list = copy.getController().getGame().getCardsIn(ZoneType.Battlefield); + list = CardLists.getValidCards(list, sa.getParam("AttachedTo"), copy.getController(), copy); + } + if (!list.isEmpty()) { + Card attachedTo = null; + if (sa.getActivatingPlayer() instanceof HumanPlayer) { + if (list.size() > 1) { + attachedTo = GuiChoose.one(copy + " - Select a card to attach to.", list); + } else { + attachedTo = list.get(0); + } + } else { // AI player + attachedTo = ComputerUtilCard.getBestAI(list); + } + if (copy.isAura()) { + if (attachedTo.canBeEnchantedBy(copy)) { + copy.enchantEntity(attachedTo); + } else {//can't enchant + continue; + } + } else { //Equipment + copy.equipCard(attachedTo); + } + } else { + continue; + } + } copy = Singletons.getModel().getGame().getAction().moveToPlay(copy); copy.setCloneOrigin(hostCard); @@ -147,6 +234,7 @@ public class CopyPermanentEffect extends SpellAbilityEffect { if (wasInAlt) { c.setState(stateName); } + // have to do this since getTargetCard() might change // if Kiki-Jiki somehow gets untapped again diff --git a/src/main/java/forge/card/ability/effects/CounterEffect.java b/src/main/java/forge/card/ability/effects/CounterEffect.java index 3300394624b..dffc1b272bc 100644 --- a/src/main/java/forge/card/ability/effects/CounterEffect.java +++ b/src/main/java/forge/card/ability/effects/CounterEffect.java @@ -1,15 +1,19 @@ package forge.card.ability.effects; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import forge.Card; import forge.Singletons; import forge.card.ability.SpellAbilityEffect; import forge.card.cardfactory.CardFactoryUtil; +import forge.game.player.AIPlayer; import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.SpellPermanent; +import forge.card.trigger.TriggerType; +import forge.gui.GuiChoose; public class CounterEffect extends SpellAbilityEffect { @Override @@ -102,11 +106,11 @@ public class CounterEffect extends SpellAbilityEffect { // Destroy Permanent may be able to be turned into a SubAbility if (tgtSA.isAbility() && sa.hasParam("DestroyPermanent")) { - Singletons.getModel().getGame().getAction().destroy(tgtSACard); + Singletons.getModel().getGame().getAction().destroy(tgtSACard, sa); } - if (sa.hasParam("RememberTargets")) { - if (sa.getParam("RememberTargets").equals("True")) { + if (sa.hasParam("RememberCountered")) { + if (sa.getParam("RememberCountered").equals("True")) { sa.getSourceCard().addRemembered(tgtSACard); } } @@ -131,7 +135,15 @@ public class CounterEffect extends SpellAbilityEffect { Singletons.getModel().getGame().getStack().remove(si); String destination = srcSA.hasParam("Destination") ? srcSA.getParam("Destination") : "Graveyard"; - + if (srcSA.hasParam("DestinationChoice")) {//Hinder + final String[] pos = srcSA.getParam("DestinationChoice").split(","); + if (srcSA.getActivatingPlayer() instanceof AIPlayer) { + destination = pos[0]; + } else { + final String prompt = "Select a destination to remove"; + destination = GuiChoose.one(prompt, pos); + } + } if (tgtSA.isAbility()) { // For Ability-targeted counterspells - do not move it anywhere, // even if Destination$ is specified. @@ -164,6 +176,13 @@ public class CounterEffect extends SpellAbilityEffect { throw new IllegalArgumentException("AbilityFactory_CounterMagic: Invalid Destination argument for card " + srcSA.getSourceCard().getName()); } + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Player", tgtSA.getActivatingPlayer()); + runParams.put("Card", tgtSA.getSourceCard()); + runParams.put("Cause", srcSA.getSourceCard()); + srcSA.getActivatingPlayer().getGame().getTriggerHandler().runTrigger(TriggerType.Countered, runParams, false); + if (!tgtSA.isAbility()) { System.out.println("Send countered spell to " + destination); diff --git a/src/main/java/forge/card/ability/effects/CountersMoveEffect.java b/src/main/java/forge/card/ability/effects/CountersMoveEffect.java index e2cba82325b..dd41e4f22cc 100644 --- a/src/main/java/forge/card/ability/effects/CountersMoveEffect.java +++ b/src/main/java/forge/card/ability/effects/CountersMoveEffect.java @@ -1,6 +1,8 @@ package forge.card.ability.effects; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import forge.Card; import forge.CounterType; @@ -8,6 +10,7 @@ import forge.card.ability.AbilityUtils; import forge.card.ability.SpellAbilityEffect; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.gui.GuiChoose; public class CountersMoveEffect extends SpellAbilityEffect { @@ -28,11 +31,19 @@ public class CountersMoveEffect extends SpellAbilityEffect { source = srcCards.get(0); } final List tgtCards = getTargetCards(sa); - - final CounterType cType = CounterType.valueOf(sa.getParam("CounterType")); + final String countername = sa.getParam("CounterType"); final int amount = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("CounterNum"), sa); - sb.append("Move ").append(amount).append(" ").append(cType.getName()).append(" counter"); + sb.append("Move "); + if ("Any".matches(countername)) { + if (amount == 1) { + sb.append("a counter"); + } else { + sb.append(amount).append(" ").append(" counter"); + } + } else { + sb.append(amount).append(" ").append(countername).append(" counter"); + } if (amount != 1) { sb.append("s"); } @@ -46,12 +57,21 @@ public class CountersMoveEffect extends SpellAbilityEffect { @Override public void resolve(SpellAbility sa) { final Card host = sa.getSourceCard(); - - final CounterType cType = CounterType.valueOf(sa.getParam("CounterType")); + final String counterName = sa.getParam("CounterType"); int amount = 0; if (!sa.getParam("CounterNum").equals("All")) { amount = AbilityUtils.calculateAmount(host, sa.getParam("CounterNum"), sa); } + + CounterType cType = null; + try { + cType = AbilityUtils.getCounterType(counterName, sa); + } catch (Exception e) { + if (!counterName.matches("Any")) { + System.out.println("Counter type doesn't match, nor does an SVar exist with the type name."); + return; + } + } Card source = null; List srcCards; @@ -76,16 +96,75 @@ public class CountersMoveEffect extends SpellAbilityEffect { for (final Card dest : tgtCards) { if ((null != source) && (null != dest)) { - if (source.getCounters(cType) >= amount) { - if (!dest.hasKeyword("CARDNAME can't have counters placed on it.") - && !(dest.hasKeyword("CARDNAME can't have -1/-1 counters placed on it.") && cType - .equals(CounterType.M1M1))) { + if (!"Any".matches(counterName)) { + if (dest.canHaveCountersPlacedOnIt(cType) + && source.getCounters(cType) >= amount) { dest.addCounter(cType, amount, true); source.subtractCounter(cType, amount); } + } else { + if (dest.hasKeyword("CARDNAME can't have counters placed on it.")) { + return; + } + boolean check = false; + for (final Card c : dest.getController().getCreaturesInPlay()) {//Melira, Sylvok Outcast + if (c.hasKeyword("Creatures you control can't have -1/-1 counters placed on them.")) { + check = true; + } + } + while (amount > 0 && source.hasCounters()) { + final Map tgtCounters = source.getCounters(); + CounterType chosenType = null; + int chosenAmount; + if (sa.getActivatingPlayer().isHuman()) { + final ArrayList typeChoices = new ArrayList(); + // get types of counters + for (CounterType key : tgtCounters.keySet()) { + if (tgtCounters.get(key) > 0 && !(key == CounterType.M1M1 && check)) { + typeChoices.add(key); + } + } + if (typeChoices.isEmpty()) { + return; + } + if (typeChoices.size() > 1) { + String prompt = "Select type counters to remove"; + chosenType = GuiChoose.one(prompt, typeChoices); + } else { + chosenType = typeChoices.get(0); + } + chosenAmount = tgtCounters.get(chosenType); + if (chosenAmount > amount) { + chosenAmount = amount; + } + // make list of amount choices + if (chosenAmount > 1) { + final List choices = new ArrayList(); + for (int i = 1; i <= chosenAmount; i++) { + choices.add(Integer.valueOf(i)); + } + String prompt = "Select the number of " + chosenType.getName() + " counters to remove"; + chosenAmount = GuiChoose.one(prompt, choices); + } + } else { + for (Object key : tgtCounters.keySet()) { + if (tgtCounters.get(key) > 0) { + chosenType = (CounterType) key; + break; + } + } + // subtract all of selected type + chosenAmount = tgtCounters.get(chosenType); + if (chosenAmount > amount) { + chosenAmount = amount; + } + } + dest.addCounter(chosenType, chosenAmount, true); + source.subtractCounter(chosenType, chosenAmount); + amount -= chosenAmount; + } } } } } // moveCounterResolve - } diff --git a/src/main/java/forge/card/ability/effects/DestroyAllEffect.java b/src/main/java/forge/card/ability/effects/DestroyAllEffect.java index a184464791d..6311a8993d3 100644 --- a/src/main/java/forge/card/ability/effects/DestroyAllEffect.java +++ b/src/main/java/forge/card/ability/effects/DestroyAllEffect.java @@ -21,7 +21,7 @@ public class DestroyAllEffect extends SpellAbilityEffect { final StringBuilder sb = new StringBuilder(); final boolean noRegen = sa.hasParam("NoRegen"); - ArrayList tgtCards; + List tgtCards; final Target tgt = sa.getTarget(); if (tgt != null) { @@ -87,13 +87,13 @@ public class DestroyAllEffect extends SpellAbilityEffect { if (noRegen) { for (int i = 0; i < list.size(); i++) { - if (Singletons.getModel().getGame().getAction().destroyNoRegeneration(list.get(i)) && remDestroyed) { + if (Singletons.getModel().getGame().getAction().destroyNoRegeneration(list.get(i), sa) && remDestroyed) { card.addRemembered(list.get(i)); } } } else { for (int i = 0; i < list.size(); i++) { - if (Singletons.getModel().getGame().getAction().destroy(list.get(i)) && remDestroyed) { + if (Singletons.getModel().getGame().getAction().destroy(list.get(i), sa) && remDestroyed) { card.addRemembered(list.get(i)); } } diff --git a/src/main/java/forge/card/ability/effects/DestroyEffect.java b/src/main/java/forge/card/ability/effects/DestroyEffect.java index f00c694abf0..51367b4bf37 100644 --- a/src/main/java/forge/card/ability/effects/DestroyEffect.java +++ b/src/main/java/forge/card/ability/effects/DestroyEffect.java @@ -96,9 +96,9 @@ public class DestroyEffect extends SpellAbilityEffect { if (sac) { destroyed = Singletons.getModel().getGame().getAction().sacrifice(tgtC, sa); } else if (noRegen) { - destroyed = Singletons.getModel().getGame().getAction().destroyNoRegeneration(tgtC); + destroyed = Singletons.getModel().getGame().getAction().destroyNoRegeneration(tgtC, sa); } else { - destroyed = Singletons.getModel().getGame().getAction().destroy(tgtC); + destroyed = Singletons.getModel().getGame().getAction().destroy(tgtC, sa); } if (destroyed && remDestroyed) { card.addRemembered(tgtC); } @@ -111,9 +111,9 @@ public class DestroyEffect extends SpellAbilityEffect { if (sac) { destroyed = Singletons.getModel().getGame().getAction().sacrifice(unTgtC, sa); } else if (noRegen) { - destroyed = Singletons.getModel().getGame().getAction().destroyNoRegeneration(unTgtC); + destroyed = Singletons.getModel().getGame().getAction().destroyNoRegeneration(unTgtC, sa); } else { - destroyed = Singletons.getModel().getGame().getAction().destroy(unTgtC); + destroyed = Singletons.getModel().getGame().getAction().destroy(unTgtC, sa); } if (destroyed && remDestroyed) { card.addRemembered(unTgtC); } diff --git a/src/main/java/forge/card/ability/effects/DiscardEffect.java b/src/main/java/forge/card/ability/effects/DiscardEffect.java index 2f1e216e781..b9453d61a93 100644 --- a/src/main/java/forge/card/ability/effects/DiscardEffect.java +++ b/src/main/java/forge/card/ability/effects/DiscardEffect.java @@ -201,12 +201,12 @@ public class DiscardEffect extends RevealEffectBase { } if (mode.startsWith("Reveal") && p != chooser) - chooser.getController().reveal("Revealed " + p + " hand", dPHand, ZoneType.Hand, p); + chooser.getGame().getAction().reveal(dPHand, p); - int minDiscardAmount = sa.hasParam("AnyNumber") || sa.hasParam("Optional") ? 0 : numCards; - int max = Math.min(validCards.size(), minDiscardAmount); + int min = sa.hasParam("AnyNumber") || sa.hasParam("Optional") ? 0 : numCards; + int max = Math.min(validCards.size(), numCards); - List toBeDiscarded = validCards.isEmpty() ? CardLists.emptyList : chooser.getController().chooseCardsToDiscardFrom(p, sa, validCards, max); + List toBeDiscarded = validCards.isEmpty() ? CardLists.emptyList : chooser.getController().chooseCardsToDiscardFrom(p, sa, validCards, min, max); if (mode.startsWith("Reveal") ) { p.getController().reveal(chooser + " has chosen", toBeDiscarded, ZoneType.Hand, p); diff --git a/src/main/java/forge/card/ability/effects/EncodeEffect.java b/src/main/java/forge/card/ability/effects/EncodeEffect.java index f8f57be27fb..39f72e203a4 100644 --- a/src/main/java/forge/card/ability/effects/EncodeEffect.java +++ b/src/main/java/forge/card/ability/effects/EncodeEffect.java @@ -35,8 +35,8 @@ public class EncodeEffect extends SpellAbilityEffect { choices = CardLists.getValidCards(choices, "Creature.YouCtrl", host.getController(), host); // if no creatures on battlefield, cannot encoded - if (choices.size() == 0) { - + if (choices.isEmpty()) { + return; } // Handle choice of whether or not to encoded @@ -46,8 +46,6 @@ public class EncodeEffect extends SpellAbilityEffect { if (!player.getController().confirmAction(sa, null, sb.toString())) { return; } - // Note: AI will always choose to encode - // TODO add better AI choice here // move host card to exile Card movedCard = Singletons.getModel().getGame().getAction().moveTo(ZoneType.Exile, host); diff --git a/src/main/java/forge/card/ability/effects/ManaEffect.java b/src/main/java/forge/card/ability/effects/ManaEffect.java index 6af32728246..669217c6e61 100644 --- a/src/main/java/forge/card/ability/effects/ManaEffect.java +++ b/src/main/java/forge/card/ability/effects/ManaEffect.java @@ -18,6 +18,7 @@ import forge.game.ai.ComputerUtilCard; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; +import forge.gui.GuiDialog; public class ManaEffect extends SpellAbilityEffect { @@ -37,7 +38,13 @@ public class ManaEffect extends SpellAbilityEffect { final List tgtPlayers = getTargetPlayers(sa); final Target tgt = sa.getTarget(); + final boolean optional = sa.hasParam("Optional"); + if (optional) { + if (!GuiDialog.confirm(sa.getSourceCard(), "Do you want to add mana to your mana pool?")) { + return; + } + } if (abMana.isComboMana()) { for (Player p : tgtPlayers) { int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa) : 1; diff --git a/src/main/java/forge/card/ability/effects/PlayEffect.java b/src/main/java/forge/card/ability/effects/PlayEffect.java index 9c392278a10..5c401427f06 100644 --- a/src/main/java/forge/card/ability/effects/PlayEffect.java +++ b/src/main/java/forge/card/ability/effects/PlayEffect.java @@ -6,11 +6,15 @@ import java.util.List; import org.apache.commons.lang3.StringUtils; import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import forge.Card; import forge.CardCharacteristicName; import forge.CardLists; import forge.Singletons; +import forge.card.CardRulesPredicates; import forge.card.ability.AbilityUtils; import forge.card.ability.SpellAbilityEffect; import forge.card.spellability.Spell; @@ -20,11 +24,14 @@ import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCard; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; import forge.gui.GuiDialog; import forge.item.CardDb; +import forge.item.CardPrinted; +import forge.util.Aggregates; public class PlayEffect extends SpellAbilityEffect { @Override @@ -81,6 +88,43 @@ public class PlayEffect extends SpellAbilityEffect { tgtCards.add(encodedCards.get(encodedIndex)); useEncoded = true; } + else if (sa.hasParam("AnySupportedCard")) { + List cards = Lists.newArrayList(CardDb.instance().getUniqueCards()); + String valid = sa.getParam("AnySupportedCard"); + if (StringUtils.containsIgnoreCase(valid, "sorcery")) { + Predicate cpp = Predicates.compose(CardRulesPredicates.Presets.IS_SORCERY, CardPrinted.FN_GET_RULES); + cards = Lists.newArrayList(Iterables.filter(cards, cpp)); + } + if (StringUtils.containsIgnoreCase(valid, "instant")) { + Predicate cpp = Predicates.compose(CardRulesPredicates.Presets.IS_INSTANT, CardPrinted.FN_GET_RULES); + cards = Lists.newArrayList(Iterables.filter(cards, cpp)); + } + if (sa.hasParam("RandomCopied")) { + List copysource = new ArrayList(cards); + List choice = new ArrayList(); + final String num = sa.hasParam("RandomNum") ? sa.getParam("RandomNum") : "1"; + int ncopied = AbilityUtils.calculateAmount(source, num, sa); + while(ncopied > 0) { + final CardPrinted cp = Aggregates.random(copysource); + if (cp.getMatchingForgeCard().isValid(valid, source.getController(), source)) { + choice.add(cp.getMatchingForgeCard()); + copysource.remove(cp); + ncopied -= 1; + } + } + if (sa.hasParam("ChoiceNum")) { + final int choicenum = AbilityUtils.calculateAmount(source, sa.getParam("ChoiceNum"), sa); + List afterchoice = new ArrayList(); + for (int i = 0; i < choicenum; i++) { + afterchoice.add(GuiChoose.oneOrNone(source + "- Choose a Card", choice)); + } + tgtCards = afterchoice; + } else { + tgtCards = choice; + } + + } + } else { tgtCards = getTargetCards(sa); } @@ -202,7 +246,7 @@ public class PlayEffect extends SpellAbilityEffect { boolean noManaCost = sa.hasParam("WithoutManaCost"); if (controller.isHuman()) { SpellAbility newSA = noManaCost ? tgtSA.copyWithNoManaCost() : tgtSA; - game.getActionPlay().playSpellAbility(newSA, activator); + ((HumanPlayer)activator).playSpellAbility(newSA); } else { if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell? Spell spell = (Spell) tgtSA; diff --git a/src/main/java/forge/card/ability/effects/PumpEffect.java b/src/main/java/forge/card/ability/effects/PumpEffect.java index 7691505f010..66c5b0756b0 100644 --- a/src/main/java/forge/card/ability/effects/PumpEffect.java +++ b/src/main/java/forge/card/ability/effects/PumpEffect.java @@ -16,6 +16,7 @@ import forge.card.spellability.Target; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiDialog; +import forge.util.Aggregates; public class PumpEffect extends SpellAbilityEffect { @@ -179,7 +180,7 @@ public class PumpEffect extends SpellAbilityEffect { String pumpForget = null; String pumpImprint = null; - final List keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList(); + List keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList(); final int a = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumAtt"), sa); final int d = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumDef"), sa); @@ -194,7 +195,29 @@ public class PumpEffect extends SpellAbilityEffect { tgtCards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa); } } - + + if (sa.hasParam("RandomKeyword")) { + final String num = sa.hasParam("RandomKWNum") ? sa.getParam("RandomKWNum") : "1"; + final int numkw = AbilityUtils.calculateAmount(sa.getSourceCard(), num, sa); + List choice = new ArrayList(); + List total = new ArrayList(keywords); + if (sa.hasParam("NoRepetition")) { + final List tgtCardskws = tgtCards.get(0).getKeyword(); + for (String kws : tgtCardskws) { + if (total.contains(kws)) { + total.remove(kws); + } + } + } + final int min = Math.min(total.size(), numkw); + for (int i = 0; i < min; i++) { + final String random = Aggregates.random(total); + choice.add(random); + total.remove(random); + } + keywords = choice; + } + if (sa.hasParam("Optional")) { if (sa.getActivatingPlayer().isHuman()) { final StringBuilder targets = new StringBuilder(); diff --git a/src/main/java/forge/card/ability/effects/SacrificeEffect.java b/src/main/java/forge/card/ability/effects/SacrificeEffect.java index a875e5f701d..94e063c5e2d 100644 --- a/src/main/java/forge/card/ability/effects/SacrificeEffect.java +++ b/src/main/java/forge/card/ability/effects/SacrificeEffect.java @@ -61,12 +61,12 @@ public class SacrificeEffect extends SpellAbilityEffect { choosenToSacrifice = Aggregates.random(validTargets, Math.min(amount, validTargets.size())); } else { boolean isOptional = sa.hasParam("Optional"); - choosenToSacrifice = p.getController().choosePermanentsToSacrifice(validTargets, amount, sa, destroy, isOptional); + choosenToSacrifice = p.getController().choosePermanentsToSacrifice(validTargets, valid, amount, sa, destroy, isOptional); } for(Card sac : choosenToSacrifice) { boolean wasSacrificed = !destroy && game.getAction().sacrifice(sac, sa); - boolean wasDestroyed = destroy && game.getAction().destroy(sac); + boolean wasDestroyed = destroy && game.getAction().destroy(sac, sa); if ( remSacrificed && (wasDestroyed || wasSacrificed) ) { card.addRemembered(sac); diff --git a/src/main/java/forge/card/ability/effects/StoreSVarEffect.java b/src/main/java/forge/card/ability/effects/StoreSVarEffect.java index 8a4f832bf3d..bedc3520e03 100644 --- a/src/main/java/forge/card/ability/effects/StoreSVarEffect.java +++ b/src/main/java/forge/card/ability/effects/StoreSVarEffect.java @@ -1,6 +1,7 @@ package forge.card.ability.effects; import forge.Card; +import forge.card.ability.AbilityUtils; import forge.card.ability.SpellAbilityEffect; import forge.card.cardfactory.CardFactoryUtil; import forge.card.spellability.SpellAbility; @@ -42,6 +43,11 @@ public class StoreSVarEffect extends SpellAbilityEffect { value = Integer.valueOf(expr); } else if (type.equals("CountSVar")) { + if (expr.contains("/")) { + final String exprMathVar = expr.split("\\/")[1].split("\\.")[1]; + int exprMath = AbilityUtils.calculateAmount(source, exprMathVar, sa); + expr = expr.replace(exprMathVar, Integer.toString(exprMath)); + } value = CardFactoryUtil.xCount(source, "SVar$" + expr); } else if (type.equals("Targeted")) { diff --git a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java index d395dda03d4..99a31805572 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java @@ -34,7 +34,6 @@ import forge.CardPredicates.Presets; import forge.Command; import forge.CounterType; import forge.Singletons; -import forge.card.ability.AbilityFactory; import forge.card.cost.Cost; import forge.card.mana.ManaCost; import forge.card.spellability.Ability; @@ -317,7 +316,6 @@ public class CardFactoryCreatures { c.addCounter(CounterType.P1P1, xCounters, true); } }; - spell.setIsXCost(true); // Do not remove SpellAbilities created by AbilityFactory or // Keywords. card.clearFirstSpell(); @@ -530,140 +528,7 @@ public class CardFactoryCreatures { card.addTrigger(myTrigger); } - private static void getCard_Nebuchadnezzar(final Card card, final String cardName) { - /* - * X, T: Name a card. Target opponent reveals X cards at random from - * his or her hand. Then that player discards all cards with that - * name revealed this way. Activate this ability only during your - * turn. - */ - final Cost abCost = new Cost(card, "X T", true); - final Target target = new Target(card, "Select target opponent", "Opponent".split(",")); - class NebuchadnezzarAbility extends AbilityActivated { - public NebuchadnezzarAbility(final Card ca, final Cost co, final Target t) { - super(ca, co, t); - } - @Override - public AbilityActivated getCopy() { - AbilityActivated discard = new NebuchadnezzarAbility(getSourceCard(), - getPayCosts(), new Target(getTarget())); - discard.getRestrictions().setPlayerTurn(true); - return discard; - } - - private static final long serialVersionUID = 4839778470534392198L; - - @Override - public void resolve() { - // name a card - final String choice = JOptionPane.showInputDialog(null, "Name a card", cardName, - JOptionPane.QUESTION_MESSAGE); - final List hand = new ArrayList(this.getTargetPlayer().getCardsIn(ZoneType.Hand)); - int numCards = card.getXManaCostPaid(); - numCards = Math.min(hand.size(), numCards); - - final List revealed = new ArrayList(); - for (int i = 0; i < numCards; i++) { - final Card random = Aggregates.random(hand); - revealed.add(random); - hand.remove(random); - } - if (!revealed.isEmpty()) { - GuiChoose.one("Revealed at random", revealed); - } else { - GuiChoose.one("Revealed at random", new String[] { "Nothing to reveal" }); - } - - for (final Card c : revealed) { - if (c.getName().equals(choice)) { - c.getController().discard(c, this); - } - } - } - - @Override - public boolean canPlayAI() { - return false; - } - - @Override - public String getDescription() { - final StringBuilder sbDesc = new StringBuilder(); - sbDesc.append(abCost).append("Name a card. "); - sbDesc.append("Target opponent reveals X cards at random from his or her hand. "); - sbDesc.append("Then that player discards all cards with that name revealed this way. "); - sbDesc.append("Activate this ability only during your turn."); - return sbDesc.toString(); - } - } - final AbilityActivated discard = new NebuchadnezzarAbility(card, abCost, target); - - discard.getRestrictions().setPlayerTurn(true); - - final StringBuilder sbStack = new StringBuilder(); - sbStack.append(cardName).append(" - name a card."); - discard.setStackDescription(sbStack.toString()); - - card.addSpellAbility(discard); - } - - private static void getCard_DuctCrawler(final Card card, final String cardName) { - final String theCost; - if (cardName.equals("Duct Crawler")) { - theCost = "1 R"; - } else if (cardName.equals("Shrewd Hatchling")) { - theCost = "UR"; - } else { // if (cardName.equals("Spin Engine") || - // cardName.equals("Screeching Griffin")) { - theCost = "R"; - } - - class DuctCrawlerAbility extends AbilityActivated { - private static final long serialVersionUID = 7914250202245863157L; - - public DuctCrawlerAbility(final Card ca, final Cost co, Target t) { - super(ca, co, t); - } - - @Override - public AbilityActivated getCopy() { - return new DuctCrawlerAbility(getSourceCard(), - getPayCosts(), new Target(getTarget())); - } - - @Override - public void resolve() { - final StringBuilder keywordBuilder = new StringBuilder("HIDDEN CARDNAME can't block "); - keywordBuilder.append(this.getSourceCard().toString()); - - final StringBuilder abilityBuilder = new StringBuilder("AB$Pump | Cost$ "); - abilityBuilder.append(theCost); - abilityBuilder.append(" | ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True | KW$ "); - abilityBuilder.append(keywordBuilder.toString()); - abilityBuilder.append(" | SpellDescription$ Target creature can't block CARDNAME this turn."); - final SpellAbility myAb = AbilityFactory.getAbility(abilityBuilder.toString(), card); - - myAb.getTarget().setTargetChoices(this.getChosenTarget().getTargetChoices()); - myAb.resolve(); - } - - @Override - public String getStackDescription() { - return this.getSourceCard().toString() + " - Target creature can't block " - + this.getSourceCard().getName() + " this turn."; - } - - @Override - public String getDescription() { - return theCost + ": Target creature can't block CARDNAME this turn."; - } - } - final SpellAbility finalAb = new DuctCrawlerAbility(card, new Cost(card, theCost, true), new Target(card, - "Select target creature.", "Creature")); - - card.addSpellAbility(finalAb); - } // // This is a hardcoded card template // @@ -686,11 +551,6 @@ public class CardFactoryCreatures { getCard_SurturedGhoul(card); } else if (cardName.equals("Phyrexian Dreadnought")) { getCard_PhyrexianDreadnought(card, cardName); - } else if (cardName.equals("Nebuchadnezzar")) { - getCard_Nebuchadnezzar(card, cardName); - } else if (cardName.equals("Duct Crawler") || cardName.equals("Shrewd Hatchling") - || cardName.equals("Spin Engine") || cardName.equals("Screeching Griffin")) { - getCard_DuctCrawler(card, cardName); } // *************************************************** diff --git a/src/main/java/forge/card/cardfactory/CardFactorySorceries.java b/src/main/java/forge/card/cardfactory/CardFactorySorceries.java index 47fbdd47bf9..e50a7545de9 100644 --- a/src/main/java/forge/card/cardfactory/CardFactorySorceries.java +++ b/src/main/java/forge/card/cardfactory/CardFactorySorceries.java @@ -45,6 +45,7 @@ import forge.control.input.InputSelectCardsFromList; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; @@ -60,190 +61,6 @@ import forge.util.Aggregates; */ public class CardFactorySorceries { - private static final SpellAbility getBrilliantUltimatum(final Card card) { - return new Spell(card) { - private static final long serialVersionUID = 1481112451519L; - - @Override - public void resolve() { - - Card choice = null; - - // check for no cards in hand on resolve - final List lib = card.getController().getCardsIn(ZoneType.Library); - final List cards = new ArrayList(); - final List exiled = new ArrayList(); - if (lib.size() == 0) { - JOptionPane.showMessageDialog(null, "No more cards in library.", "", - JOptionPane.INFORMATION_MESSAGE); - return; - } - int count = 5; - if (lib.size() < 5) { - count = lib.size(); - } - for (int i = 0; i < count; i++) { - cards.add(lib.get(i)); - } - for (int i = 0; i < count; i++) { - exiled.add(lib.get(i)); - Singletons.getModel().getGame().getAction().exile(lib.get(i)); - } - final List pile1 = new ArrayList(); - final List pile2 = new ArrayList(); - boolean stop = false; - int pile1CMC = 0; - int pile2CMC = 0; - - final StringBuilder msg = new StringBuilder(); - msg.append("Revealing top ").append(count).append(" cards of library: "); - GuiChoose.one(msg.toString(), cards); - // Human chooses - if (card.getController().isComputer()) { - for (int i = 0; i < count; i++) { - if (!stop) { - choice = GuiChoose.oneOrNone("Choose cards to put into the first pile: ", - cards); - if (choice != null) { - pile1.add(choice); - cards.remove(choice); - pile1CMC = pile1CMC + choice.getCMC(); - } else { - stop = true; - } - } - } - for (int i = 0; i < count; i++) { - if (!pile1.contains(exiled.get(i))) { - pile2.add(exiled.get(i)); - pile2CMC = pile2CMC + exiled.get(i).getCMC(); - } - } - final StringBuilder sb = new StringBuilder(); - sb.append("You have spilt the cards into the following piles"); - sb.append("\r\n").append("\r\n"); - sb.append("Pile 1: ").append("\r\n"); - for (int i = 0; i < pile1.size(); i++) { - sb.append(pile1.get(i).getName()).append("\r\n"); - } - sb.append("\r\n").append("Pile 2: ").append("\r\n"); - for (int i = 0; i < pile2.size(); i++) { - sb.append(pile2.get(i).getName()).append("\r\n"); - } - JOptionPane.showMessageDialog(null, sb, "", JOptionPane.INFORMATION_MESSAGE); - if (pile1CMC >= pile2CMC) { - JOptionPane.showMessageDialog(null, "Computer chooses the Pile 1", "", - JOptionPane.INFORMATION_MESSAGE); - for (int i = 0; i < pile1.size(); i++) { - final List choices = pile1.get(i).getBasicSpells(); - - for (final SpellAbility sa : choices) { - if (sa.canPlayAI()) { - ComputerUtil.playStackFree(sa.getActivatingPlayer(), sa); - if (pile1.get(i).isPermanent()) { - exiled.remove(pile1.get(i)); - } - break; - } - } - } - } else { - JOptionPane.showMessageDialog(null, "Computer chooses the Pile 2", "", - JOptionPane.INFORMATION_MESSAGE); - for (int i = 0; i < pile2.size(); i++) { - final List choices = pile2.get(i).getBasicSpells(); - - for (final SpellAbility sa : choices) { - if (sa.canPlayAI()) { - ComputerUtil.playStackFree(sa.getActivatingPlayer(), sa); - if (pile2.get(i).isPermanent()) { - exiled.remove(pile2.get(i)); - } - break; - } - } - } - } - - } else { // Computer chooses (It picks the highest converted - // mana cost card and 1 random card.) - Card biggest = exiled.get(0); - - for (final Card c : exiled) { - if (biggest.getManaCost().getCMC() < c.getManaCost().getCMC()) { - biggest = c; - } - } - - pile1.add(biggest); - cards.remove(biggest); - if (cards.size() > 2) { - final Card random = Aggregates.random(cards); - pile1.add(random); - } - for (int i = 0; i < count; i++) { - if (!pile1.contains(exiled.get(i))) { - pile2.add(exiled.get(i)); - } - } - final StringBuilder sb = new StringBuilder(); - sb.append("Choose a pile to add to your hand: "); - sb.append("\r\n").append("\r\n"); - sb.append("Pile 1: ").append("\r\n"); - for (int i = 0; i < pile1.size(); i++) { - sb.append(pile1.get(i).getName()).append("\r\n"); - } - sb.append("\r\n").append("Pile 2: ").append("\r\n"); - for (int i = 0; i < pile2.size(); i++) { - sb.append(pile2.get(i).getName()).append("\r\n"); - } - final Object[] possibleValues = { "Pile 1", "Pile 2" }; - final Object q = JOptionPane.showOptionDialog(null, sb, "Brilliant Ultimatum", - JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, possibleValues, - possibleValues[0]); - - List chosen; - if (q.equals(0)) { - chosen = pile1; - } else { - chosen = pile2; - } - - final int numChosen = chosen.size(); - for (int i = 0; i < numChosen; i++) { - final Card check = GuiChoose.oneOrNone("Select spells to play in reverse order: ", chosen); - if (check == null) { - break; - } - - final Card playing = check; - if (playing.isLand()) { - if (card.getController().canPlayLand(playing)) { - card.getController().playLand(playing); - } else { - JOptionPane.showMessageDialog(null, "You can't play any more lands this turn.", "", - JOptionPane.INFORMATION_MESSAGE); - } - } else { - Singletons.getModel().getGame().getActionPlay().playCardWithoutManaCost(playing, card.getController()); - } - chosen.remove(playing); - } - - } - pile1.clear(); - pile2.clear(); - } // resolve() - - @Override - public boolean canPlayAI() { - final List cards = getActivatingPlayer().getCardsIn(ZoneType.Library); - return cards.size() >= 8; - } - }; // SpellAbility - - } - private static final void balanceLands(Spell card) { List> lands = new ArrayList>(); @@ -505,8 +322,7 @@ public class CardFactorySorceries { public static void buildCard(final Card card, final String cardName) { - if (cardName.equals("Brilliant Ultimatum")) { card.addSpellAbility(getBrilliantUltimatum(card)); - } else if (cardName.equals("Balance")) { card.addSpellAbility(getBalance(card)); + if (cardName.equals("Balance")) { card.addSpellAbility(getBalance(card)); } else if (cardName.equals("Patriarch's Bidding")) { card.addSpellAbility(getPatriarchsBidding(card)); } else if (cardName.equals("Transmute Artifact")) { card.addSpellAbility(getTransmuteArtifact(card)); } diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index 71078439ed6..8dccd2b289f 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -47,7 +47,6 @@ import forge.card.ability.ApiType; import forge.card.cost.Cost; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostParser; -import forge.card.mana.ManaCostShard; import forge.card.replacement.ReplacementEffect; import forge.card.replacement.ReplacementHandler; import forge.card.replacement.ReplacementLayer; @@ -63,7 +62,6 @@ import forge.card.spellability.Target; import forge.card.trigger.Trigger; import forge.card.trigger.TriggerHandler; import forge.card.trigger.TriggerType; -import forge.control.input.InputBase; import forge.control.input.InputSelectCards; import forge.control.input.InputSelectCardsFromList; import forge.game.GameState; @@ -73,14 +71,13 @@ import forge.game.ai.ComputerUtilCost; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.PlayerZone; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; -import forge.gui.match.CMatchUI; import forge.util.Aggregates; -import forge.view.ButtonUtil; /** *

@@ -868,13 +865,9 @@ public class CardFactoryUtil { * an array of {@link java.lang.String} objects. * @return an array of {@link java.lang.String} objects. */ - public static String[] parseMath(final String[] l) { - final String[] m = { "none" }; - if (l.length > 1) { - m[0] = l[1]; - } - - return m; + public static String extractOperators(final String expression) { + String[] l = expression.split("/"); + return l.length > 1 ? l[1] : null; } /** @@ -891,20 +884,12 @@ public class CardFactoryUtil { * @return a int. */ public static int objectXCount(final ArrayList objects, final String s, final Card source) { - if (objects.size() == 0) { + if (objects.isEmpty()) { return 0; } - final String[] l = s.split("/"); - final String[] m = CardFactoryUtil.parseMath(l); - - int n = 0; - - if (s.startsWith("Amount")) { - n = objects.size(); - } - - return CardFactoryUtil.doXMath(n, m, source); + int n = s.startsWith("Amount") ? objects.size() : 0; + return CardFactoryUtil.doXMath(n, CardFactoryUtil.extractOperators(s), source); } /** @@ -926,7 +911,7 @@ public class CardFactoryUtil { } final String[] l = s.split("/"); - final String[] m = CardFactoryUtil.parseMath(l); + final String m = CardFactoryUtil.extractOperators(s); int n = 0; @@ -1117,19 +1102,21 @@ public class CardFactoryUtil { * * @param c * a {@link forge.Card} object. - * @param s + * @param expression * a {@link java.lang.String} object. * @return a int. */ - public static int xCount(final Card c, final String s) { + public static int xCount(final Card c, final String expression) { int n = 0; + if (StringUtils.isBlank(expression)) return 0; + if (StringUtils.isNumeric(expression)) return Integer.parseInt(expression); final Player cardController = c.getController(); final Player oppController = cardController.getOpponent(); final Player activePlayer = Singletons.getModel().getGame().getPhaseHandler().getPlayerTurn(); - final String[] l = s.split("/"); - final String[] m = CardFactoryUtil.parseMath(l); + final String[] l = expression.split("/"); + final String m = CardFactoryUtil.extractOperators(expression); // accept straight numbers if (l[0].startsWith("Number$")) { @@ -1250,22 +1237,18 @@ public class CardFactoryUtil { return highest; } - if (l[0].startsWith("DifferentCardNamesRemembered")) { - final List list = new ArrayList(); + if (l[0].startsWith("DifferentCardNames_")) { final List crdname = new ArrayList(); - if (c.getRemembered().size() > 0) { - for (final Object o : c.getRemembered()) { - if (o instanceof Card) { - list.add(Singletons.getModel().getGame().getCardState((Card) o)); - } - } - } + final String restriction = l[0].substring(19); + final String[] rest = restriction.split(","); + List list = cardController.getGame().getCardsInGame(); + list = CardLists.getValidCards(list, rest, cardController, c); for (final Card card : list) { if (!crdname.contains(card.getName())) { crdname.add(card.getName()); } } - return crdname.size(); + return CardFactoryUtil.doXMath(crdname.size(), m, c); } if (l[0].startsWith("RememberedSize")) { @@ -2034,12 +2017,12 @@ public class CardFactoryUtil { return CardFactoryUtil.doXMath(n, m, c); } - private static int doXMath(final int num, final String m, final Card c) { - if (m.equals("none")) { + public static int doXMath(final int num, final String operators, final Card c) { + if (operators == null || operators.equals("none")) { return num; } - final String[] s = m.split("\\."); + final String[] s = operators.split("\\."); int secondaryNum = 0; try { @@ -2098,27 +2081,6 @@ public class CardFactoryUtil { } } - /** - *

- * doXMath. - *

- * - * @param num - * a int. - * @param m - * an array of {@link java.lang.String} objects. - * @param c - * a {@link forge.Card} object. - * @return a int. - */ - public static int doXMath(final int num, final String[] m, final Card c) { - if (m.length == 0) { - return num; - } - - return CardFactoryUtil.doXMath(num, m[0], c); - } - /** *

* handlePaid. @@ -2151,17 +2113,9 @@ public class CardFactoryUtil { } if (string.startsWith("Valid")) { - final String[] m = { "none" }; - String valid = string.substring(6); - final String[] l; - l = valid.split("/"); // separate the specification from any math - valid = l[0]; - if (l.length > 1) { - m[0] = l[1]; - } final List list = CardLists.getValidCards(paidList, valid, source.getController(), source); - return CardFactoryUtil.doXMath(list.size(), m, source); + return CardFactoryUtil.doXMath(list.size(), CardFactoryUtil.extractOperators(valid), source); } int tot = 0; @@ -2397,7 +2351,6 @@ public class CardFactoryUtil { final String[] k = parse.split("kicker "); final SpellAbility sa = card.getSpellAbility()[0]; - sa.setIsMultiKicker(true); sa.setMultiKickerManaCost(new ManaCost(new ManaCostParser(k[1]))); } } @@ -2557,13 +2510,6 @@ public class CardFactoryUtil { } } // Suspend - int xCount = card.getManaCost().getShardCount(ManaCostShard.X); - if (xCount > 0) { - final SpellAbility sa = card.getSpellAbility()[0]; - sa.setIsXCost(true); - sa.setXManaCost(xCount); - } // X - if (CardFactoryUtil.hasKeyword(card, "Fading") != -1) { final int n = CardFactoryUtil.hasKeyword(card, "Fading"); if (n != -1) { @@ -2922,7 +2868,7 @@ public class CardFactoryUtil { } if (card.getController().isHuman()) { - game.getActionPlay().playSpellAbilityNoStack(card.getController(), origSA, false); + ((HumanPlayer)card.getController()).playSpellAbilityNoStack(origSA); } else { ComputerUtil.playNoStack((AIPlayer) card.getController(), origSA, game); } @@ -3459,6 +3405,10 @@ public class CardFactoryUtil { Card dinner = (Card) o; card.addDevoured(dinner); Singletons.getModel().getGame().getAction().sacrifice(dinner, null); + final HashMap runParams = new HashMap(); + runParams.put("Devoured", dinner); + card.getController().getGame().getTriggerHandler() + .runTrigger(TriggerType.Devoured, runParams, false); } } } // human @@ -3469,6 +3419,10 @@ public class CardFactoryUtil { if ((c.getNetAttack() <= 1) && ((c.getNetAttack() + c.getNetDefense()) <= 3)) { card.addDevoured(c); Singletons.getModel().getGame().getAction().sacrifice(c, null); + final HashMap runParams = new HashMap(); + runParams.put("Devoured", c); + card.getController().getGame().getTriggerHandler() + .runTrigger(TriggerType.Devoured, runParams, false); count++; } } diff --git a/src/main/java/forge/card/cost/Cost.java b/src/main/java/forge/card/cost/Cost.java index b9ed4412aa5..66ce7a4caa9 100644 --- a/src/main/java/forge/card/cost/Cost.java +++ b/src/main/java/forge/card/cost/Cost.java @@ -23,7 +23,6 @@ import java.util.regex.Pattern; import forge.Card; import forge.CounterType; -import forge.Singletons; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostBeingPaid; import forge.card.mana.ManaCostParser; @@ -141,8 +140,7 @@ public class Cost { this.name = card != null ? card.getName() : ""; boolean xCantBe0 = false; - int amountX = 0; - + StringBuilder manaParts = new StringBuilder(); String[] parts = TextUtil.splitWithParenthesis(parse, ' ', '<', '>'); @@ -157,8 +155,6 @@ public class Cost { for(String part : parts) { if( "XCantBe0".equals(part) ) xCantBe0 = true; - else if ( "X".equals(part) ) - amountX++; else { CostPart cp = parseCostPart(part, tapCost, untapCost); if ( null != cp ) @@ -169,11 +165,10 @@ public class Cost { } - if ((amountX > 0) || manaParts.length() > 0) { - this.costParts.add(0, new CostPartMana(manaParts.toString(), amountX, xCantBe0)); + if (manaParts.length() > 0) { + this.costParts.add(0, new CostPartMana(new ManaCost(new ManaCostParser(manaParts.toString())), xCantBe0)); } - - + // inspect parts to set Sac, {T} and {Q} flags for (int iCp = 0; iCp < costParts.size(); iCp++) { CostPart cp = costParts.get(iCp); @@ -366,16 +361,18 @@ public class Cost { for (final CostPart part : this.costParts) { if (part instanceof CostPartMana) { final ManaCost mana = new ManaCost(new ManaCostParser(part.toString())); - final ManaCostBeingPaid changedCost = Singletons.getModel().getGame().getActionPlay().getSpellCostChange(sa, new ManaCostBeingPaid(mana)); + final ManaCostBeingPaid changedCost = new ManaCostBeingPaid(mana); + changedCost.applySpellCostChange(sa); - ((CostPartMana)part).setAdjustedMana(changedCost.toString(false)); + ((CostPartMana)part).setAdjustedMana(changedCost.toManaCost()); costChanged = true; } } if (!costChanged) { // Spells with a cost of 0 should be affected too - final ManaCostBeingPaid changedCost = Singletons.getModel().getGame().getActionPlay().getSpellCostChange(sa, new ManaCostBeingPaid("0")); - this.costParts.add(new CostPartMana(changedCost.toString(), 0, false)); + final ManaCostBeingPaid changedCost = new ManaCostBeingPaid("0"); + changedCost.applySpellCostChange(sa); + this.costParts.add(new CostPartMana(changedCost.toManaCost(), false)); } } diff --git a/src/main/java/forge/card/cost/CostDamage.java b/src/main/java/forge/card/cost/CostDamage.java index f26324df966..1a82c960d12 100644 --- a/src/main/java/forge/card/cost/CostDamage.java +++ b/src/main/java/forge/card/cost/CostDamage.java @@ -54,7 +54,7 @@ public class CostDamage extends CostPart { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { return true; } diff --git a/src/main/java/forge/card/cost/CostDiscard.java b/src/main/java/forge/card/cost/CostDiscard.java index 5d07264d3ac..4c800273418 100644 --- a/src/main/java/forge/card/cost/CostDiscard.java +++ b/src/main/java/forge/card/cost/CostDiscard.java @@ -103,7 +103,10 @@ public class CostDiscard extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + List handList = new ArrayList(activator.getCardsIn(ZoneType.Hand)); String type = this.getType(); final Integer amount = this.convertAmount(); diff --git a/src/main/java/forge/card/cost/CostExile.java b/src/main/java/forge/card/cost/CostExile.java index 4ca0c1d47b4..5694002aee9 100644 --- a/src/main/java/forge/card/cost/CostExile.java +++ b/src/main/java/forge/card/cost/CostExile.java @@ -461,7 +461,11 @@ public class CostExile extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + final GameState game = activator.getGame(); + List typeList = new ArrayList(); if (this.getType().equals("All")) { return true; // this will always work diff --git a/src/main/java/forge/card/cost/CostGainLife.java b/src/main/java/forge/card/cost/CostGainLife.java index dd4350e7c75..dbbd9958ac8 100644 --- a/src/main/java/forge/card/cost/CostGainLife.java +++ b/src/main/java/forge/card/cost/CostGainLife.java @@ -57,10 +57,10 @@ public class CostGainLife extends CostPart { return sb.toString(); } - private List getPotentialTargets(final GameState game, final Player payer, final Card source) + private List getPotentialTargets(final Player payer, final Card source) { List res = new ArrayList(); - for(Player p : game.getPlayers()) + for(Player p : payer.getGame().getPlayers()) { if(p.isValid(getType(), payer, source)) res.add(p); @@ -76,12 +76,12 @@ public class CostGainLife extends CostPart { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { final Integer amount = this.convertAmount(); if ( amount == null ) return false; int cntAbleToGainLife = 0; - List possibleTargets = getPotentialTargets(game, activator, source); + List possibleTargets = getPotentialTargets(ability.getActivatingPlayer(), ability.getSourceCard()); for (final Player opp : possibleTargets) { if (opp.canGainLife()) { @@ -101,7 +101,7 @@ public class CostGainLife extends CostPart { @Override public final void payAI(final PaymentDecision decision, final AIPlayer ai, SpellAbility ability, Card source) { int playersLeft = cntPlayers; - for (final Player opp : getPotentialTargets(ai.getGame(), ai, source)) { + for (final Player opp : getPotentialTargets(ai, source)) { if (opp.canGainLife() && playersLeft > 0) { playersLeft--; opp.gainLife(decision.c, null); @@ -135,7 +135,7 @@ public class CostGainLife extends CostPart { } final List oppsThatCanGainLife = new ArrayList(); - for (final Player opp : getPotentialTargets(game, activator, source)) { + for (final Player opp : getPotentialTargets(activator, source)) { if (opp.canGainLife()) { oppsThatCanGainLife.add(opp); } diff --git a/src/main/java/forge/card/cost/CostMill.java b/src/main/java/forge/card/cost/CostMill.java index 68b55dd44f9..40eefd99084 100644 --- a/src/main/java/forge/card/cost/CostMill.java +++ b/src/main/java/forge/card/cost/CostMill.java @@ -62,7 +62,9 @@ public class CostMill extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); final PlayerZone zone = activator.getZone(ZoneType.Library); Integer i = this.convertAmount(); diff --git a/src/main/java/forge/card/cost/CostPart.java b/src/main/java/forge/card/cost/CostPart.java index 9e4bada7f7a..a8c0db3ff8b 100644 --- a/src/main/java/forge/card/cost/CostPart.java +++ b/src/main/java/forge/card/cost/CostPart.java @@ -18,11 +18,12 @@ package forge.card.cost; +import org.apache.commons.lang3.StringUtils; + import forge.Card; import forge.card.spellability.SpellAbility; import forge.game.GameState; import forge.game.player.AIPlayer; -import forge.game.player.Player; /** * The Class CostPart. @@ -92,13 +93,9 @@ public abstract class CostPart { return this.typeDescription; } - /** - * Gets the descriptive type. - * - * @return the descriptive type - */ public final String getDescriptiveType() { - return this.getTypeDescription() == null ? this.getType() : this.getTypeDescription(); + String typeDesc = this.getTypeDescription(); + return typeDesc == null ? this.getType() : typeDesc; } /** @@ -125,12 +122,7 @@ public abstract class CostPart { * @return the integer */ public final Integer convertAmount() { - Integer i = null; - try { - i = Integer.parseInt(this.getAmount()); - } catch (final NumberFormatException e) { - } - return i; + return StringUtils.isNumeric(amount) ? Integer.parseInt(amount) : null; } /** @@ -147,7 +139,7 @@ public abstract class CostPart { * @param game * @return true, if successful */ - public abstract boolean canPay(SpellAbility ability, Card source, Player activator, Cost cost, GameState game); + public abstract boolean canPay(SpellAbility ability); /** * Decide ai payment. diff --git a/src/main/java/forge/card/cost/CostPartMana.java b/src/main/java/forge/card/cost/CostPartMana.java index 7385fbe8c57..05327d51dd9 100644 --- a/src/main/java/forge/card/cost/CostPartMana.java +++ b/src/main/java/forge/card/cost/CostPartMana.java @@ -17,11 +17,13 @@ */ package forge.card.cost; -import com.google.common.base.Strings; - import forge.Card; import forge.FThreads; +import forge.card.MagicColor; import forge.card.ability.AbilityUtils; +import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostBeingPaid; +import forge.card.mana.ManaCostShard; import forge.card.spellability.SpellAbility; import forge.control.input.InputPayManaOfCostPayment; import forge.control.input.InputPayManaX; @@ -29,83 +31,42 @@ import forge.control.input.InputPayment; import forge.game.GameState; import forge.game.ai.ComputerUtilMana; import forge.game.player.AIPlayer; -import forge.game.player.Player; /** * The mana component of any spell or ability cost */ public class CostPartMana extends CostPart { // "Leftover" - private String mana = ""; - private int amountX = 0; - private String adjustedMana = ""; + private final ManaCost cost; + private ManaCost adjustedCost; private boolean xCantBe0 = false; + /** + * Instantiates a new cost mana. + * + * @param mana + * the mana + * @param amount + * the amount + * @param xCantBe0 TODO + */ + public CostPartMana(final ManaCost cost, boolean xCantBe0) { + this.cost = cost; + this.xCantBe0 = xCantBe0; // TODO: Add 0 to parameter's name. + } + /** * Gets the mana. * * @return the mana */ - public final String getMana() { + public final ManaCost getMana() { // Only used for Human to pay for non-X cost first - return this.mana; + return this.cost; } - /** - * Sets the mana. - * - * @param sCost - * the new mana - */ - public final void setMana(final String sCost) { - this.mana = sCost; - } - - /** - * Checks for no x mana cost. - * - * @return true, if successful - */ - public final boolean hasNoXManaCost() { - return this.amountX == 0; - } - - /** - * Gets the x mana. - * - * @return the x mana - */ public final int getAmountOfX() { - return this.amountX; - } - - /** - * Sets the x mana. - * - * @param xCost - * the new x mana - */ - public final void setAmountOfX(final int xCost) { - this.amountX = xCost; - } - - /** - * Gets the adjusted mana. - * - * @return the adjusted mana - */ - public final String getAdjustedMana() { - return this.adjustedMana; - } - - /** - * Sets the adjusted mana. - * - * @param adjustedMana - * the new adjusted mana - */ - public final void setAdjustedMana(final String adjustedMana) { - this.adjustedMana = adjustedMana; + return this.cost.getShardCount(ManaCostShard.X); } /** @@ -116,10 +77,11 @@ public class CostPartMana extends CostPart { } /** - * @param xCantBe00 the xCantBe0 to set + * Used to set mana cost after applying static effects that change costs. */ - public void setxCantBe0(boolean xCantBe0) { - this.xCantBe0 = xCantBe0; // TODO: Add 0 to parameter's name. + public void setAdjustedMana(ManaCost manaCost) { + // this is set when static effects of LodeStone Golems or Thalias are applied + adjustedCost = manaCost; } /** @@ -127,13 +89,8 @@ public class CostPartMana extends CostPart { * * @return the mana to pay */ - public final String getManaToPay() { - // Only used for Human to pay for non-X cost first - if (!this.adjustedMana.equals("")) { - return this.adjustedMana; - } - - return this.mana; + public final ManaCost getManaToPay() { + return adjustedCost == null ? cost : adjustedCost; } @Override @@ -142,51 +99,61 @@ public class CostPartMana extends CostPart { @Override public boolean isUndoable() { return true; } - /** - * Instantiates a new cost mana. - * - * @param mana - * the mana - * @param amount - * the amount - * @param xCantBe0 TODO - */ - public CostPartMana(final String mana, final int amount, boolean xCantBe0) { - this.mana = mana.trim(); - this.amountX = amount; - this.setxCantBe0(xCantBe0); - } - /* - * (non-Javadoc) - * - * @see forge.card.cost.CostPart#toString() - */ @Override public final String toString() { - final StringBuilder sb = new StringBuilder(); - - sb.append(Strings.repeat("X ", this.amountX)); - if ( sb.length() == 0 || mana != "0" ) - sb.append(this.mana); - - return sb.toString().trim(); + return cost.toString(); } - /* - * (non-Javadoc) - * - * @see - * forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility, - * forge.Card, forge.Player, forge.card.cost.Cost) - */ + @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { // For now, this will always return true. But this should probably be // checked at some point return true; } + @Override + public final boolean payHuman(final SpellAbility ability, final GameState game) { + final Card source = ability.getSourceCard(); + ManaCostBeingPaid toPay = new ManaCostBeingPaid(getManaToPay()); + + if (this.getAmountOfX() > 0 && !ability.getSVar("X").equals("Count$xPaid")) { // announce X will overwrite whatever was in card script + // this currently only works for things about Targeted object + int xCost = AbilityUtils.calculateAmount(source, "X", ability) * this.getAmountOfX(); + byte xColor = MagicColor.fromName(ability.hasParam("XColor") ? ability.getParam("XColor") : "1"); + toPay.increaseShard(ManaCostShard.valueOf(xColor), xCost); + } + int timesMultikicked = ability.getSourceCard().getMultiKickerMagnitude(); + if ( timesMultikicked > 0 && ability.isAnnouncing("Multikicker")) { + ManaCost mkCost = ability.getMultiKickerManaCost(); + for(int i = 0; i < timesMultikicked; i++) + toPay.combineManaCost(mkCost); + } + + + if (!toPay.isPaid()) { + InputPayment inpPayment = new InputPayManaOfCostPayment(game, toPay, ability); + FThreads.setInputAndWait(inpPayment); + if(!inpPayment.isPaid()) + return false; + } + if (this.getAmountOfX() > 0) { + if( !ability.isAnnouncing("X") ) { + source.setXManaCostPaid(0); + InputPayment inpPayment = new InputPayManaX(ability, this.getAmountOfX(), this.canXbe0()); + FThreads.setInputAndWait(inpPayment); + if(!inpPayment.isPaid()) + return false; + } else { + int x = AbilityUtils.calculateAmount(source, "X", ability); + source.setXManaCostPaid(x); + } + } + return true; + + } + /* (non-Javadoc) * @see forge.card.cost.CostPart#payAI(forge.card.cost.PaymentDecision, forge.game.player.AIPlayer, forge.card.spellability.SpellAbility, forge.Card) */ @@ -195,42 +162,6 @@ public class CostPartMana extends CostPart { ComputerUtilMana.payManaCost(ai, ability); } - /* - * (non-Javadoc) - * - * @see - * forge.card.cost.CostPart#payHuman(forge.card.spellability.SpellAbility, - * forge.Card, forge.card.cost.Cost_Payment) - */ - @Override - public final boolean payHuman(final SpellAbility ability, final GameState game) { - final Card source = ability.getSourceCard(); - int manaToAdd = 0; - if (!this.hasNoXManaCost()) { - // if X cost is a defined value, other than xPaid - if (!ability.getSVar("X").equals("Count$xPaid")) { - // this currently only works for things about Targeted object - manaToAdd = AbilityUtils.calculateAmount(source, "X", ability) * this.getAmountOfX(); - } - } - - - if (!"0".equals(this.getManaToPay()) || manaToAdd > 0) { - InputPayment inpPayment = new InputPayManaOfCostPayment(game, this, ability, manaToAdd); - FThreads.setInputAndWait(inpPayment); - if(!inpPayment.isPaid()) - return false; - } - if (this.getAmountOfX() > 0) { - source.setXManaCostPaid(0); - InputPayment inpPayment = new InputPayManaX(game, ability, this.getAmountOfX(), this.canXbe0()); - FThreads.setInputAndWait(inpPayment); - if(!inpPayment.isPaid()) - return false; - } - return true; - - } /* (non-Javadoc) * @see forge.card.cost.CostPart#decideAIPayment(forge.game.player.AIPlayer, forge.card.spellability.SpellAbility, forge.Card) @@ -239,7 +170,4 @@ public class CostPartMana extends CostPart { public PaymentDecision decideAIPayment(AIPlayer ai, SpellAbility ability, Card source) { return new PaymentDecision(0); } - - // Inputs - } diff --git a/src/main/java/forge/card/cost/CostPartWithList.java b/src/main/java/forge/card/cost/CostPartWithList.java index 866590345cc..5326433eddd 100644 --- a/src/main/java/forge/card/cost/CostPartWithList.java +++ b/src/main/java/forge/card/cost/CostPartWithList.java @@ -69,6 +69,8 @@ public abstract class CostPartWithList extends CostPart { sa.addCostToHashList(CardUtil.getLKICopy(card), paymentMethod); } } + + // public abstract List getValidCards(); /** * Instantiates a new cost part with list. diff --git a/src/main/java/forge/card/cost/CostPayLife.java b/src/main/java/forge/card/cost/CostPayLife.java index 6f1c38036c7..5afe507d3f0 100644 --- a/src/main/java/forge/card/cost/CostPayLife.java +++ b/src/main/java/forge/card/cost/CostPayLife.java @@ -72,8 +72,9 @@ public class CostPayLife extends CostPart { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { final Integer amount = this.convertAmount(); + Player activator = ability.getActivatingPlayer(); if ((amount != null) && !activator.canPayLife(amount)) { return false; } diff --git a/src/main/java/forge/card/cost/CostPayment.java b/src/main/java/forge/card/cost/CostPayment.java index 1c08e5d61ac..706440f03e4 100644 --- a/src/main/java/forge/card/cost/CostPayment.java +++ b/src/main/java/forge/card/cost/CostPayment.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import forge.Card; +import forge.card.mana.ManaCost; import forge.card.spellability.SpellAbility; import forge.game.GameState; import forge.game.player.AIPlayer; @@ -77,7 +78,7 @@ public class CostPayment { * a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ - public static boolean canPayAdditionalCosts(final GameState game, final Cost cost, final SpellAbility ability) { + public static boolean canPayAdditionalCosts(final Cost cost, final SpellAbility ability) { if (cost == null) { return true; } @@ -90,7 +91,7 @@ public class CostPayment { } for (final CostPart part : cost.getCostParts()) { - if (!part.canPay(ability, card, activator, cost, game)) { + if (!part.canPay(ability)) { return false; } } @@ -179,7 +180,7 @@ public class CostPayment { final List parts = this.cost.getCostParts(); if (this.getCost().getCostMana() == null) { - parts.add(new CostPartMana("0", 0, false)); + parts.add(new CostPartMana(ManaCost.ZERO, false)); } Map, PaymentDecision> decisions = new HashMap, PaymentDecision>(); diff --git a/src/main/java/forge/card/cost/CostPutCounter.java b/src/main/java/forge/card/cost/CostPutCounter.java index a20dbc02452..252fdfa87a5 100644 --- a/src/main/java/forge/card/cost/CostPutCounter.java +++ b/src/main/java/forge/card/cost/CostPutCounter.java @@ -189,13 +189,11 @@ public class CostPutCounter extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); if (this.payCostFromSource()) { - if (source.hasKeyword("CARDNAME can't have counters placed on it.")) { - return false; - } - if (source.hasKeyword("CARDNAME can't have -1/-1 counters placed on it.") - && this.counter.equals(CounterType.M1M1)) { + if (!source.canHaveCountersPlacedOnIt(this.counter)) { return false; } } else { diff --git a/src/main/java/forge/card/cost/CostRemoveCounter.java b/src/main/java/forge/card/cost/CostRemoveCounter.java index 592e53dd1d9..780796c4969 100644 --- a/src/main/java/forge/card/cost/CostRemoveCounter.java +++ b/src/main/java/forge/card/cost/CostRemoveCounter.java @@ -297,8 +297,10 @@ public class CostRemoveCounter extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { final CounterType cntrs = this.getCounter(); + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); final Integer amount = this.convertAmount(); if (this.payCostFromSource()) { diff --git a/src/main/java/forge/card/cost/CostReturn.java b/src/main/java/forge/card/cost/CostReturn.java index 0aea94af567..701072a7e52 100644 --- a/src/main/java/forge/card/cost/CostReturn.java +++ b/src/main/java/forge/card/cost/CostReturn.java @@ -93,7 +93,9 @@ public class CostReturn extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); if (!this.payCostFromSource()) { boolean needsAnnoucement = ability.hasParam("Announce") && this.getType().contains(ability.getParam("Announce")); diff --git a/src/main/java/forge/card/cost/CostReveal.java b/src/main/java/forge/card/cost/CostReveal.java index 870a6516ea3..47c64509965 100644 --- a/src/main/java/forge/card/cost/CostReveal.java +++ b/src/main/java/forge/card/cost/CostReveal.java @@ -66,7 +66,10 @@ public class CostReveal extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + List handList = new ArrayList(activator.getCardsIn(ZoneType.Hand)); final String type = this.getType(); final Integer amount = this.convertAmount(); diff --git a/src/main/java/forge/card/cost/CostSacrifice.java b/src/main/java/forge/card/cost/CostSacrifice.java index 6994e4c7aca..42375798133 100644 --- a/src/main/java/forge/card/cost/CostSacrifice.java +++ b/src/main/java/forge/card/cost/CostSacrifice.java @@ -24,85 +24,20 @@ import forge.CardLists; import forge.FThreads; import forge.card.ability.AbilityUtils; import forge.card.spellability.SpellAbility; -import forge.control.input.InputPayment; +import forge.control.input.InputSelectCards; +import forge.control.input.InputSelectCardsFromList; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiDialog; -import forge.gui.match.CMatchUI; -import forge.view.ButtonUtil; /** * The Class CostSacrifice. */ public class CostSacrifice extends CostPartWithList { - /** - * TODO: Write javadoc for this type. - * - */ - public static final class InputPayCostSacrificeFromList extends InputPayCostBase { - private final CostSacrifice part; - private final SpellAbility sa; - private final int nNeeded; - private final List typeList; - private static final long serialVersionUID = 2685832214519141903L; - private int nSacrifices = 0; - - /** - * TODO: Write javadoc for Constructor. - * @param part - * @param sa - * @param nNeeded - * @param payment - * @param typeList - */ - public InputPayCostSacrificeFromList(CostSacrifice part, SpellAbility sa, int nNeeded, List typeList) { - this.part = part; - this.sa = sa; - this.nNeeded = nNeeded; - this.typeList = typeList; - } - - @Override - public void showMessage() { - if (nNeeded == 0) { - this.done(); - } - - final StringBuilder msg = new StringBuilder("Sacrifice "); - final int nLeft = nNeeded - this.nSacrifices; - msg.append(nLeft).append(" "); - msg.append(part.getDescriptiveType()); - if (nLeft > 1) { - msg.append("s"); - } - - CMatchUI.SINGLETON_INSTANCE.showMessage(msg.toString()); - ButtonUtil.enableOnlyCancel(); - } - - @Override - public void selectCard(final Card card) { - if (typeList.contains(card)) { - this.nSacrifices++; - part.executePayment(sa, card); - typeList.remove(card); - // in case nothing else to sacrifice - if (this.nSacrifices == nNeeded) { - this.done(); - } else if (typeList.isEmpty()) { - // happen - this.cancel(); - } else { - this.showMessage(); - } - } - } - } - /** * Instantiates a new cost sacrifice. * @@ -150,7 +85,10 @@ public class CostSacrifice extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + // You can always sac all if (!this.payCostFromSource()) { // If the sacrificed type is dependant on an annoucement, can't necesarily rule out the CanPlay call @@ -223,9 +161,14 @@ public class CostSacrifice extends CostPartWithList { if (0 == c.intValue()) { return true; } - InputPayment inp = new InputPayCostSacrificeFromList(this, ability, c, list); + + InputSelectCards inp = new InputSelectCardsFromList(c, c, list); + inp.setMessage("Select a " + this.getDescriptiveType() + " to sacrifice (%d left)"); FThreads.setInputAndWait(inp); - return inp.isPaid(); + if ( inp.hasCancelled() ) + return false; + + return executePayment(ability, inp.getSelected()); } return false; } diff --git a/src/main/java/forge/card/cost/CostTap.java b/src/main/java/forge/card/cost/CostTap.java index 9081fba4086..b3d743647e8 100644 --- a/src/main/java/forge/card/cost/CostTap.java +++ b/src/main/java/forge/card/cost/CostTap.java @@ -21,7 +21,6 @@ import forge.Card; import forge.card.spellability.SpellAbility; import forge.game.GameState; import forge.game.player.AIPlayer; -import forge.game.player.Player; /** * The Class CostTap. @@ -70,7 +69,8 @@ public class CostTap extends CostPart { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Card source = ability.getSourceCard(); return source.isUntapped() && (!source.isSick() || source.hasKeyword("CARDNAME may activate abilities as though it has haste.")); } diff --git a/src/main/java/forge/card/cost/CostTapType.java b/src/main/java/forge/card/cost/CostTapType.java index 26969477c28..ac4c6b9c2ac 100644 --- a/src/main/java/forge/card/cost/CostTapType.java +++ b/src/main/java/forge/card/cost/CostTapType.java @@ -19,87 +19,28 @@ package forge.card.cost; import java.util.ArrayList; import java.util.List; + +import com.google.common.base.Predicate; + import forge.Card; import forge.CardLists; import forge.CardPredicates.Presets; import forge.FThreads; -import forge.Singletons; import forge.card.ability.AbilityUtils; import forge.card.spellability.SpellAbility; -import forge.control.input.InputPayment; +import forge.control.input.InputSelectCards; +import forge.control.input.InputSelectCardsFromList; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; import forge.game.player.Player; -import forge.game.zone.Zone; import forge.game.zone.ZoneType; -import forge.view.ButtonUtil; /** * The Class CostTapType. */ public class CostTapType extends CostPartWithList { - /** - * TODO: Write javadoc for this type. - * - */ - public static final class InputPayCostTapType extends InputPayCostBase { - private final CostTapType tapType; - private final int nCards; - private final List cardList; - private static final long serialVersionUID = 6438988130447851042L; - private int nTapped = 0; - - /** - * TODO: Write javadoc for Constructor. - * @param sa - * @param tapType - * @param nCards - * @param cardList - * @param payment - */ - public InputPayCostTapType(CostTapType tapType, int nCards, List cardList) { - this.tapType = tapType; - this.nCards = nCards; - this.cardList = cardList; - } - - @Override - public void showMessage() { - - final int left = nCards - this.nTapped; - showMessage("Select a " + tapType.getDescription() + " to tap (" + left + " left)"); - ButtonUtil.enableOnlyCancel(); - if (nCards == 0) { - this.done(); - } - } - - - - @Override - public void selectCard(final Card card) { - Zone zone = Singletons.getModel().getGame().getZoneOf(card); - if (zone.is(ZoneType.Battlefield) && cardList.contains(card) && card.isUntapped()) { - // send in List for Typing - tapType.executePayment(null, card); - cardList.remove(card); - - this.nTapped++; - - if (this.nTapped == nCards) { - this.done(); - } else if (cardList.size() == 0) { - // happen - this.cancel(); - } else { - this.showMessage(); - } - } - } - } - private final boolean canTapSource; /** @@ -121,15 +62,6 @@ public class CostTapType extends CostPartWithList { public boolean isReusable() { return true; } - /** - * Gets the description. - * - * @return the description - */ - public final String getDescription() { - return this.getTypeDescription() == null ? this.getType() : this.getTypeDescription(); - } - /* * (non-Javadoc) * @@ -141,12 +73,15 @@ public class CostTapType extends CostPartWithList { sb.append("Tap "); final Integer i = this.convertAmount(); - final String desc = this.getDescription(); - - sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), "untapped " + desc)); - - sb.append(" you control"); - + final String desc = this.getDescriptiveType(); + final String type = this.getType(); + + if (type.contains("sharesCreatureTypeWith")) { + sb.append("two untapped creatures you control that share a creature type"); + } else { + sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), "untapped " + desc)); + sb.append(" you control"); + } return sb.toString(); } @@ -172,15 +107,39 @@ public class CostTapType extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + List typeList = new ArrayList(activator.getCardsIn(ZoneType.Battlefield)); + String type = this.getType(); + boolean sameType = false; + + if (type.contains("sharesCreatureTypeWith")) { + sameType = true; + type = type.replace("sharesCreatureTypeWith", ""); + } + + typeList = CardLists.getValidCards(typeList, type.split(";"), activator, source); - typeList = CardLists.getValidCards(typeList, this.getType().split(";"), activator, source); - - if (cost.hasTapCost()) { + if (!canTapSource) { typeList.remove(source); } typeList = CardLists.filter(typeList, Presets.UNTAPPED); + + if (sameType) { + for (final Card card : typeList) { + if (CardLists.filter(typeList, new Predicate() { + @Override + public boolean apply(final Card c) { + return c.sharesCreatureTypeWith(card); + } + }).size() > 1) { + return true; + } + } + return false; + } final Integer amount = this.convertAmount(); if ((typeList.size() == 0) || ((amount != null) && (typeList.size() < amount))) { @@ -200,8 +159,29 @@ public class CostTapType extends CostPartWithList { @Override public final boolean payHuman(final SpellAbility ability, final GameState game) { List typeList = new ArrayList(ability.getActivatingPlayer().getCardsIn(ZoneType.Battlefield)); - typeList = CardLists.getValidCards(typeList, this.getType().split(";"), ability.getActivatingPlayer(), ability.getSourceCard()); + String type = this.getType(); + boolean sameType = false; + if (type.contains("sharesCreatureTypeWith")) { + sameType = true; + type = type.replace("sharesCreatureTypeWith", ""); + } + typeList = CardLists.getValidCards(typeList, type.split(";"), ability.getActivatingPlayer(), ability.getSourceCard()); typeList = CardLists.filter(typeList, Presets.UNTAPPED); + if (sameType) { + final List List2 = typeList; + typeList = CardLists.filter(typeList, new Predicate() { + @Override + public boolean apply(final Card c) { + for (Card card : List2) { + if (!card.equals(c) && card.sharesCreatureTypeWith(c)) { + return true; + } + } + return false; + } + }); + } + final String amount = this.getAmount(); final Card source = ability.getSourceCard(); Integer c = this.convertAmount(); @@ -214,9 +194,15 @@ public class CostTapType extends CostPartWithList { c = AbilityUtils.calculateAmount(source, amount, ability); } } - InputPayment inp = new InputPayCostTapType(this, c, typeList); + + + InputSelectCards inp = new InputSelectCardsFromList(c, c, typeList); + inp.setMessage("Select a " + getDescriptiveType() + " to tap (%d left)"); FThreads.setInputAndWait(inp); - return inp.isPaid(); + if ( inp.hasCancelled() ) + return false; + + return executePayment(ability, inp.getSelected()); } /* (non-Javadoc) @@ -238,6 +224,9 @@ public class CostTapType extends CostPartWithList { c = AbilityUtils.calculateAmount(source, amount, ability); } } + if (this.getType().contains("sharesCreatureTypeWith")) { + return null; + } List totap = ComputerUtil.chooseTapType(ai, this.getType(), source, !canTapSource, c); diff --git a/src/main/java/forge/card/cost/CostUnattach.java b/src/main/java/forge/card/cost/CostUnattach.java index 5e9e6fe0f49..8e2777861db 100644 --- a/src/main/java/forge/card/cost/CostUnattach.java +++ b/src/main/java/forge/card/cost/CostUnattach.java @@ -65,7 +65,10 @@ public class CostUnattach extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + final String type = this.getType(); if (type.equals("CARDNAME")) { if (source.isEquipping()) { diff --git a/src/main/java/forge/card/cost/CostUntap.java b/src/main/java/forge/card/cost/CostUntap.java index 2d8eda4bebb..c37e56f39e5 100644 --- a/src/main/java/forge/card/cost/CostUntap.java +++ b/src/main/java/forge/card/cost/CostUntap.java @@ -21,7 +21,6 @@ import forge.Card; import forge.card.spellability.SpellAbility; import forge.game.GameState; import forge.game.player.AIPlayer; -import forge.game.player.Player; /** * The Class CostUntap. @@ -69,7 +68,8 @@ public class CostUntap extends CostPart { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Card source = ability.getSourceCard(); return source.isTapped() && (!source.isSick() || source.hasKeyword("CARDNAME may activate abilities as though it has haste.")); } diff --git a/src/main/java/forge/card/cost/CostUntapType.java b/src/main/java/forge/card/cost/CostUntapType.java index 550f3282a16..004d5cc5362 100644 --- a/src/main/java/forge/card/cost/CostUntapType.java +++ b/src/main/java/forge/card/cost/CostUntapType.java @@ -59,15 +59,6 @@ public class CostUntapType extends CostPartWithList { public boolean isReusable() { return true; } - /** - * Gets the description. - * - * @return the description - */ - public final String getDescription() { - return this.getTypeDescription() == null ? this.getType() : this.getTypeDescription(); - } - /* * (non-Javadoc) * @@ -79,11 +70,11 @@ public class CostUntapType extends CostPartWithList { sb.append("Untap "); final Integer i = this.convertAmount(); - final String desc = this.getDescription(); + final String desc = this.getDescriptiveType(); sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), " tapped " + desc)); - if (this.getType().contains("YouDontCtrl")) { + if (this.getType().contains("OppCtrl")) { sb.append(" an opponent controls"); } else if (this.getType().contains("YouCtrl")) { sb.append(" you control"); @@ -113,8 +104,10 @@ public class CostUntapType extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { - List typeList = Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield); + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + List typeList = activator.getGame().getCardsIn(ZoneType.Battlefield); typeList = CardLists.getValidCards(typeList, this.getType().split(";"), activator, source); @@ -159,7 +152,7 @@ public class CostUntapType extends CostPartWithList { } } InputSelectCards inp = new InputSelectCardsFromList(c, c, typeList); - inp.setMessage("Select a " + getDescription() + " to untap (%d left)"); + inp.setMessage("Select a " + getDescriptiveType() + " to untap (%d left)"); FThreads.setInputAndWait(inp); if( inp.hasCancelled() || inp.getSelected().size() != c ) return false; diff --git a/src/main/java/forge/card/cost/CostUtil.java b/src/main/java/forge/card/cost/CostUtil.java index e71ed8799ac..c261d0def7a 100644 --- a/src/main/java/forge/card/cost/CostUtil.java +++ b/src/main/java/forge/card/cost/CostUtil.java @@ -126,31 +126,20 @@ public class CostUtil { } public static Cost combineCosts(Cost cost1, Cost cost2) { - if (cost1 == null) { - if (cost2 == null) { - return null; - } else { - return cost2; - } - } - - if (cost2 == null) { - return cost1; - } + if (cost1 == null) return cost2; + if (cost2 == null) return cost1; + CostPartMana costPart2 = cost2.getCostMana(); for (final CostPart part : cost1.getCostParts()) { - if (!(part instanceof CostPartMana)) { + if (part instanceof CostPartMana && costPart2 != null) { + ManaCostBeingPaid oldManaCost = new ManaCostBeingPaid(((CostPartMana) part).getMana()); + boolean xCanBe0 = ((CostPartMana) part).canXbe0() && costPart2.canXbe0(); + oldManaCost.combineManaCost(costPart2.getMana()); + + cost2.getCostParts().remove(costPart2); + cost2.getCostParts().add(0, new CostPartMana(oldManaCost.toManaCost(), !xCanBe0)); + } else { cost2.getCostParts().add(part); - } else { - CostPartMana newCostMana = cost2.getCostMana(); - if (newCostMana != null) { - ManaCostBeingPaid oldManaCost = new ManaCostBeingPaid(part.toString()); - newCostMana.setAmountOfX(oldManaCost.getXcounter() + newCostMana.getAmountOfX()); - oldManaCost.combineManaCost(newCostMana.toString()); - newCostMana.setMana(oldManaCost.toString(false)); - } else { - cost2.getCostParts().add(0, part); - } } } return cost2; diff --git a/src/main/java/forge/card/cost/InputPayCostBase.java b/src/main/java/forge/card/cost/InputPayCostBase.java index 00847b3c488..33c65bff7b3 100644 --- a/src/main/java/forge/card/cost/InputPayCostBase.java +++ b/src/main/java/forge/card/cost/InputPayCostBase.java @@ -1,5 +1,6 @@ package forge.card.cost; +import forge.Singletons; import forge.control.input.InputPayment; import forge.control.input.InputSyncronizedBase; @@ -8,6 +9,14 @@ import forge.control.input.InputSyncronizedBase; * */ abstract class InputPayCostBase extends InputSyncronizedBase implements InputPayment { + /** + * TODO: Write javadoc for Constructor. + * @param player + */ + public InputPayCostBase() { + super(Singletons.getControl().getPlayer()); + } + private static final long serialVersionUID = -2967434867139585579L; boolean bPaid = false; diff --git a/src/main/java/forge/card/mana/ManaCost.java b/src/main/java/forge/card/mana/ManaCost.java index cf3f7d7e695..c4bf7c422b2 100644 --- a/src/main/java/forge/card/mana/ManaCost.java +++ b/src/main/java/forge/card/mana/ManaCost.java @@ -79,19 +79,15 @@ public final class ManaCost implements Comparable { * the parser */ public ManaCost(final IParserManaCost parser) { - if (!parser.hasNext()) { - throw new RuntimeException("Empty manacost passed to parser (this should have been handled before)"); - } final List shardsTemp = new ArrayList(); this.hasNoCost = false; while (parser.hasNext()) { final ManaCostShard shard = parser.next(); - if (shard != null) { + if (shard != null && shard != ManaCostShard.COLORLESS) { shardsTemp.add(shard); } // null is OK - that was generic mana } - this.genericCost = parser.getTotalColorlessCost(); // collect generic - // mana + this.genericCost = parser.getTotalColorlessCost(); // collect generic mana // here sealClass(shardsTemp); } diff --git a/src/main/java/forge/card/mana/ManaCostBeingPaid.java b/src/main/java/forge/card/mana/ManaCostBeingPaid.java index 224309c1312..2c809203e5d 100644 --- a/src/main/java/forge/card/mana/ManaCostBeingPaid.java +++ b/src/main/java/forge/card/mana/ManaCostBeingPaid.java @@ -19,11 +19,25 @@ package forge.card.mana; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map.Entry; +import com.google.common.collect.Lists; + +import forge.Card; +import forge.CardColor; +import forge.CardLists; +import forge.CardPredicates; import forge.Constant; import forge.card.MagicColor; +import forge.card.spellability.SpellAbility; +import forge.card.staticability.StaticAbility; +import forge.game.GameState; +import forge.game.player.Player; +import forge.game.zone.ZoneType; +import forge.gui.GuiChoose; +import forge.util.MyRandom; /** *

@@ -34,6 +48,48 @@ import forge.card.MagicColor; * @version $Id$ */ public class ManaCostBeingPaid { + private class ManaCostBeingPaidIterator implements IParserManaCost { + private Iterator mch; + private ManaCostShard nextShard; + private int remainingShards = 0; + + 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 ( !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 HashMap unpaidShards = new HashMap(); @@ -42,8 +98,6 @@ public class ManaCostBeingPaid { private final ArrayList manaNeededToAvoidNegativeEffect = new ArrayList(); private final ArrayList manaPaidToAvoidNegativeEffect = new ArrayList(); - private final ManaCost originalCost; - // 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 @@ -61,7 +115,6 @@ public class ManaCostBeingPaid { } public ManaCostBeingPaid(ManaCost manaCost) { - originalCost = manaCost; if ( null == manaCost ) return; @@ -515,24 +568,19 @@ public class ManaCostBeingPaid { return true; } - /** - *

- * combineManaCost. - *

- * - * @param extra - * a {@link java.lang.String} object. - */ - public final void combineManaCost(final String extra) { - final ManaCost manaCost = new ManaCost(new ManaCostParser(extra)); - for (ManaCostShard shard : manaCost.getShards()) { + public final void combineManaCost(final ManaCost extra) { + for (ManaCostShard shard : extra.getShards()) { if (shard == ManaCostShard.X) { cntX++; } else { increaseShard(shard, 1); } } - increaseColorlessMana(manaCost.getGenericCost()); + increaseColorlessMana(extra.getGenericCost()); + } + + public final void combineManaCost(final String extra) { + combineManaCost(new ManaCost(new ManaCostParser(extra))); } /** @@ -596,6 +644,10 @@ public class ManaCostBeingPaid { } return cmc; } + + public ManaCost toManaCost() { + return new ManaCost(new ManaCostBeingPaidIterator()); + } /** *

@@ -649,7 +701,164 @@ public class ManaCostBeingPaid { return this.manaPaidToAvoidNegativeEffect; } - public ManaCost getStartingCost() { - return originalCost; + public final void applySpellCostChange(final SpellAbility sa) { + final GameState 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 = Lists.newArrayList(pc.getZone(ZoneType.Graveyard).getCards()); + final List toExile = pc.getController().chooseCardsToDelve(this.getColorlessManaAmount(), mutableGrave); + for (final Card c : toExile) { + pc.getGame().getAction().exile(c); + decreaseColorlessMana(1); + } + } else if (spell.getSourceCard().hasKeyword("Convoke")) { + adjustCostByConvoke(sa, spell); + } + } // 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); + } + + // 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, final SpellAbility spell) { + + List untappedCreats = CardLists.filter(spell.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); + untappedCreats = CardLists.filter(untappedCreats, CardPredicates.Presets.UNTAPPED); + + while (!untappedCreats.isEmpty() && getConvertedManaCost() > 0) { + Card workingCard = null; + String chosenColor = null; + if (sa.getActivatingPlayer().isHuman()) { + workingCard = GuiChoose.oneOrNone("Tap for Convoke? " + toString(), untappedCreats); + if( null == workingCard ) + break; // that means "I'm done" + + List usableColors = getConvokableColors(workingCard); + if ( !usableColors.isEmpty() ) { + chosenColor = usableColors.size() == 1 ? usableColors.get(0) : GuiChoose.one("Convoke for which color?", usableColors); + } + } else { + // TODO: AI to choose a creature to tap would go here + // Probably along with deciding how many creatures to + // tap + + if ( MyRandom.getRandom().nextInt(3) == 0 ) // 66% chance to chose first creature, 33% to cancel + workingCard = untappedCreats.get(0); + + if( null == workingCard ) + break; // that means "I'm done" + + List usableColors = getConvokableColors(workingCard); + if ( !usableColors.isEmpty() ) { + // TODO: AI for choosing which color to convoke goes here. + chosenColor = usableColors.get(0); + } + + } + untappedCreats.remove(workingCard); + + + if ( null == chosenColor ) + continue; + else if (chosenColor.equals("colorless")) { + decreaseColorlessMana(1); + } else { + decreaseShard(ManaCostShard.valueOf(MagicColor.fromName(chosenColor)), 1); + } + + sa.addTappedForConvoke(workingCard); + } + + // 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 Card c : sa.getTappedForConvoke()) { + c.setTapped(true); + } + } + + /** + * Gets the convokable colors. + * + * @param cardToConvoke + * the card to convoke + * @param cost + * the cost + * @return the convokable colors + */ + private List getConvokableColors(final Card cardToConvoke) { + final ArrayList usableColors = new ArrayList(); + + if (getColorlessManaAmount() > 0) { + usableColors.add("colorless"); + } + for (final CardColor col : cardToConvoke.getColor()) { + for (final String strCol : col.toStringList()) { + if (strCol.equals("colorless")) { + continue; + } + if (toString().contains(MagicColor.toShortString(strCol))) { + usableColors.add(strCol.toString()); + } + } + } + + return usableColors; + } } diff --git a/src/main/java/forge/card/mana/ManaCostShard.java b/src/main/java/forge/card/mana/ManaCostShard.java index acccd8915d4..e12aa9ca0bb 100644 --- a/src/main/java/forge/card/mana/ManaCostShard.java +++ b/src/main/java/forge/card/mana/ManaCostShard.java @@ -282,6 +282,7 @@ public class ManaCostShard { * @return the card mana cost shard */ public static ManaCostShard valueOf(final int atoms) { + if ( atoms == 0 ) return ManaCostShard.COLORLESS; for (final ManaCostShard element : ManaCostShard.ALL_POSSIBLE) { if (element.shard == atoms) { return element; diff --git a/src/main/java/forge/card/replacement/ReplacementHandler.java b/src/main/java/forge/card/replacement/ReplacementHandler.java index 3fc979f6080..1bad49e95c3 100644 --- a/src/main/java/forge/card/replacement/ReplacementHandler.java +++ b/src/main/java/forge/card/replacement/ReplacementHandler.java @@ -31,6 +31,7 @@ import forge.card.spellability.SpellAbility; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; @@ -239,7 +240,7 @@ public class ReplacementHandler { Player player = replacementEffect.getHostCard().getController(); //player.getController().playNoStack() if (player.isHuman()) { - game.getActionPlay().playSpellAbilityNoStack(player, effectSA, false); + ((HumanPlayer)player).playSpellAbilityNoStack(effectSA); } else { ComputerUtil.playNoStack((AIPlayer) player, effectSA, game); } diff --git a/src/main/java/forge/card/spellability/AbilityActivated.java b/src/main/java/forge/card/spellability/AbilityActivated.java index c3ef6e83912..b196661574d 100644 --- a/src/main/java/forge/card/spellability/AbilityActivated.java +++ b/src/main/java/forge/card/spellability/AbilityActivated.java @@ -20,7 +20,6 @@ package forge.card.spellability; import java.util.ArrayList; import forge.Card; -import forge.Singletons; import forge.card.cost.Cost; import forge.card.cost.CostPayment; import forge.card.staticability.StaticAbility; @@ -82,7 +81,7 @@ public abstract class AbilityActivated extends SpellAbility implements java.io.S /** {@inheritDoc} */ @Override public boolean canPlay() { - final GameState game = Singletons.getModel().getGame(); + final GameState game = getActivatingPlayer().getGame(); if (game.getStack().isSplitSecondOnStack() && !this.isManaAbility()) { return false; } @@ -90,7 +89,7 @@ public abstract class AbilityActivated extends SpellAbility implements java.io.S final Card c = this.getSourceCard(); // CantBeActivated static abilities - for (final Card ca : Singletons.getModel().getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { + 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)) { @@ -104,7 +103,7 @@ public abstract class AbilityActivated extends SpellAbility implements java.io.S } if (this.isCycling() - && Singletons.getModel().getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCycling)) { + && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCycling)) { return false; } @@ -112,7 +111,7 @@ public abstract class AbilityActivated extends SpellAbility implements java.io.S return false; } - return CostPayment.canPayAdditionalCosts(game, this.getPayCosts(), this); + return CostPayment.canPayAdditionalCosts(this.getPayCosts(), this); } /* (non-Javadoc) diff --git a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java b/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java similarity index 55% rename from src/main/java/forge/card/spellability/SpellAbilityRequirements.java rename to src/main/java/forge/card/spellability/HumanPlaySpellAbility.java index 3513267975a..52d775b435f 100644 --- a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java +++ b/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java @@ -23,7 +23,6 @@ import org.apache.commons.lang3.StringUtils; import forge.Card; import forge.CardCharacteristicName; -import forge.Singletons; import forge.card.ability.AbilityUtils; import forge.card.cost.CostPayment; import forge.game.GameState; @@ -37,49 +36,28 @@ import forge.game.zone.Zone; * @author Forge * @version $Id$ */ -public class SpellAbilityRequirements { - private SpellAbility ability = null; - private TargetSelection select = null; - private CostPayment payment = null; - private boolean isFree = false; - private boolean skipStack = false; - private boolean bCasting = false; - private Zone fromZone = null; - private Integer zonePosition = null; +public class HumanPlaySpellAbility { + private final SpellAbility ability; + private final CostPayment payment; - - public final void setSkipStack(final boolean bSkip) { - this.skipStack = bSkip; - } - - public final void setFree(final boolean bFree) { - this.isFree = bFree; - } - - - public SpellAbilityRequirements(final SpellAbility sa, final TargetSelection ts, final CostPayment cp) { + public HumanPlaySpellAbility(final SpellAbility sa, final CostPayment cp) { this.ability = sa; - this.select = ts; this.payment = cp; } - public final void fillRequirements() { - this.fillRequirements(false); - } - public final void fillRequirements(final boolean skipTargeting) { - final GameState game = Singletons.getModel().getGame(); - - if ((this.ability instanceof Spell) && !this.bCasting) { - // remove from hand - this.bCasting = true; - if (!this.ability.getSourceCard().isCopiedSpell()) { - final Card c = this.ability.getSourceCard(); + public final void fillRequirements(boolean isAlreadyTargeted, boolean isFree, boolean skipStack) { + final GameState game = ability.getActivatingPlayer().getGame(); - this.fromZone = game.getZoneOf(c); - this.zonePosition = this.fromZone.getPosition(c); - this.ability.setSourceCard(game.getAction().moveToStack(c)); - } + // used to rollback + Zone fromZone = null; + int zonePosition = 0; + + final Card c = this.ability.getSourceCard(); + if (this.ability instanceof Spell && !c.isCopiedSpell()) { + fromZone = game.getZoneOf(c); + zonePosition = fromZone.getPosition(c); + this.ability.setSourceCard(game.getAction().moveToStack(c)); } // freeze Stack. No abilities should go onto the stack while I'm filling requirements. @@ -87,26 +65,32 @@ public class SpellAbilityRequirements { // Announce things like how many times you want to Multikick or the value of X if (!this.announceRequirements()) { - this.select.setCancel(true); - rollbackAbility(); + rollbackAbility(fromZone, zonePosition); return; } // Skip to paying if parent ability doesn't target and has no // subAbilities. // (or trigger case where its already targeted) - if (!skipTargeting && (this.select.doesTarget() || (this.ability.getSubAbility() != null))) { - this.select.setRequirements(this); - this.select.clearTargets(); - this.select.chooseTargets(); - if (this.select.isCanceled()) { - rollbackAbility(); - return; - } + if (!isAlreadyTargeted) { + + SpellAbility beingTargeted = ability; + do { + Target tgt = beingTargeted.getTarget(); + if( tgt != null && tgt.doesTarget()) { + clearTargets(beingTargeted); + final TargetSelection select = new TargetSelection(beingTargeted); + if (!select.chooseTargets() ) { + rollbackAbility(fromZone, zonePosition); + return; + } + } + beingTargeted = beingTargeted.getSubAbility(); + } while (beingTargeted != null); } // Payment - boolean paymentMade = this.isFree; + boolean paymentMade = isFree; if (!paymentMade) { this.payment.changeCost(); @@ -114,66 +98,77 @@ public class SpellAbilityRequirements { } if (!paymentMade) { - rollbackAbility(); + rollbackAbility(fromZone, zonePosition); return; } - else if (this.isFree || this.payment.isFullyPaid()) { - if (this.skipStack) { + else if (isFree || this.payment.isFullyPaid()) { + if (skipStack) { AbilityUtils.resolve(this.ability, false); } else { - this.enusureAbilityHasDescription(this.ability); this.ability.getActivatingPlayer().getManaPool().clearManaPaid(this.ability, false); game.getStack().addAndUnfreeze(this.ability); } - // Warning about this - resolution may come in another thread, and it would still need its targets - this.select.clearTargets(); + // no worries here. The same thread must resolve, and by this moment ability will have been resolved already + clearTargets(ability); game.getAction().checkStateEffects(); } } - private void rollbackAbility() { + public final void clearTargets(SpellAbility ability) { + Target tg = ability.getTarget(); + if (tg != null) { + tg.resetTargets(); + tg.calculateStillToDivide(ability.getParam("DividedAsYouChoose"), ability.getSourceCard(), ability); + } + } + + private void rollbackAbility(Zone fromZone, int zonePosition) { // cancel ability during target choosing - final Card c = this.ability.getSourceCard(); + final GameState game = ability.getActivatingPlayer().getGame(); + final Card c = ability.getSourceCard(); // split cards transform back to full form if targeting is canceled if (c.isSplitCard()) { c.setState(CardCharacteristicName.Original); } - if (this.bCasting && !c.isCopiedSpell()) { // and not a copy + if (fromZone != null) { // and not a copy // add back to where it came from - Singletons.getModel().getGame().getAction().moveTo(this.fromZone, c, this.zonePosition); + game.getAction().moveTo(fromZone, c, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); } - if (this.select != null) { - this.select.clearTargets(); - } + clearTargets(ability); this.ability.resetOnceResolved(); this.payment.refundPayment(); - Singletons.getModel().getGame().getStack().clearFrozen(); + game.getStack().clearFrozen(); // Singletons.getModel().getGame().getStack().removeFromFrozenStack(this.ability); } - public boolean announceRequirements() { + private boolean announceRequirements() { // Announcing Requirements like Choosing X or Multikicker // SA Params as comma delimited list String announce = ability.getParam("Announce"); if (announce != null) { for(String aVar : announce.split(",")) { - String value = ability.getActivatingPlayer().getController().announceRequirements(ability, aVar); - if (value == null || !StringUtils.isNumeric(value)) { - return false; - } else if (ability.getPayCosts().getCostMana() != null && !ability.getPayCosts().getCostMana().canXbe0() - && Integer.parseInt(value) == 0) { - return false; - } - ability.setSVar(aVar, "Number$" + value); - ability.getSourceCard().setSVar(aVar, "Number$" + value); + String varName = aVar.trim(); + + boolean allowZero = !("X".equalsIgnoreCase(varName)) || ability.getPayCosts().getCostMana().canXbe0(); + + Integer value = ability.getActivatingPlayer().getController().announceRequirements(ability, varName, allowZero); + if ( null == value ) + return false; + + ability.setSVar(varName, value.toString()); + if( "Multikicker".equals(varName) ) { + ability.getSourceCard().setMultiKickerMagnitude(value); + } else { + ability.getSourceCard().setSVar(varName, value.toString()); + } } } return true; diff --git a/src/main/java/forge/card/spellability/Spell.java b/src/main/java/forge/card/spellability/Spell.java index 2a4fbc7a990..f11a88ac418 100644 --- a/src/main/java/forge/card/spellability/Spell.java +++ b/src/main/java/forge/card/spellability/Spell.java @@ -114,7 +114,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable } if (this.getPayCosts() != null) { - if (!CostPayment.canPayAdditionalCosts(game, this.getPayCosts(), this)) { + if (!CostPayment.canPayAdditionalCosts(this.getPayCosts(), this)) { return false; } } diff --git a/src/main/java/forge/card/spellability/SpellAbility.java b/src/main/java/forge/card/spellability/SpellAbility.java index 9988f964f09..6e653839a50 100644 --- a/src/main/java/forge/card/spellability/SpellAbility.java +++ b/src/main/java/forge/card/spellability/SpellAbility.java @@ -23,6 +23,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.commons.lang3.StringUtils; + import forge.Card; import forge.GameEntity; import forge.Singletons; @@ -35,6 +37,7 @@ import forge.card.mana.Mana; import forge.card.mana.ManaCost; import forge.game.player.AIPlayer; import forge.game.player.Player; +import forge.util.TextUtil; //only SpellAbility can go on the stack //override any methods as needed @@ -54,7 +57,6 @@ public abstract class SpellAbility implements ISpellAbility { private ManaCost manaCost = null; private ManaCost multiKickerManaCost = null; private ManaCost replicateManaCost = null; - private int xManaCost = 0; private Player activatingPlayer = null; private String type = "Intrinsic"; // set to Intrinsic by default @@ -74,9 +76,7 @@ public abstract class SpellAbility implements ISpellAbility { private boolean temporarilySuppressed = false; private boolean flashBackAbility = false; - private boolean multiKicker = false; private boolean replicate = false; - private boolean xCost = false; private boolean cycling = false; private boolean delve = false; @@ -289,29 +289,6 @@ public abstract class SpellAbility implements ISpellAbility { this.replicateManaCost = spellManaCost; } - /** - *

- * Getter for the field xManaCost. - *

- * - * @return a {@link java.lang.String} object. - */ - public int getXManaCost() { - return this.xManaCost; - } - - /** - *

- * Setter for the field xManaCost. - *

- * - * @param cost - * a {@link java.lang.String} object. - */ - public final void setXManaCost(final int cost) { - this.xManaCost = cost; - } - /** *

* Getter for the field activatingPlayer. @@ -387,19 +364,7 @@ public abstract class SpellAbility implements ISpellAbility { return false; } - /** - *

- * setIsMultiKicker. - *

- * - * @param b - * a boolean. - */ - public final void setIsMultiKicker(final boolean b) { - this.multiKicker = b; - } - - /** + /** *

* isMultiKicker. *

@@ -407,7 +372,7 @@ public abstract class SpellAbility implements ISpellAbility { * @return a boolean. */ public boolean isMultiKicker() { - return this.multiKicker; + return this.multiKickerManaCost != null && !this.isAnnouncing("Multikicker"); } /** @@ -433,28 +398,6 @@ public abstract class SpellAbility implements ISpellAbility { return this.replicate; } - /** - *

- * setIsXCost. - *

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

- * isXCost. - *

- * - * @return a boolean. - */ - public boolean isXCost() { - return this.xCost; - } /** *

@@ -1053,7 +996,7 @@ public abstract class SpellAbility implements ISpellAbility { if (this.targetCard == null) { final Target tgt = this.getTarget(); if (tgt != null) { - final ArrayList list = tgt.getTargetCards(); + final List list = tgt.getTargetCards(); if (!list.isEmpty()) { return list.get(0); @@ -1106,7 +1049,7 @@ public abstract class SpellAbility implements ISpellAbility { public Player getTargetPlayer() { final Target tgt = this.getTarget(); if (tgt != null) { - final ArrayList list = tgt.getTargetPlayers(); + final List list = tgt.getTargetPlayers(); if (!list.isEmpty()) { return list.get(0); @@ -1313,7 +1256,7 @@ public abstract class SpellAbility implements ISpellAbility { return false; } if (entity.isValid(this.getTarget().getValidTgts(), this.getActivatingPlayer(), this.getSourceCard()) - && (!this.getTarget().isUniqueTargets() || !TargetSelection.getUniqueTargets(this).contains(entity)) + && (!this.getTarget().isUniqueTargets() || !this.getUniqueTargets().contains(entity)) && entity.canBeTargetedBy(this)) { return true; } @@ -1494,23 +1437,22 @@ public abstract class SpellAbility implements ISpellAbility { * * @return a {@link forge.card.spellability.SpellAbility} object. */ - public ArrayList findTargetedCards() { - - ArrayList list = new ArrayList(); + public List findTargetedCards() { Target tgt = this.getTarget(); // First search for targeted cards associated with current ability if (tgt != null && tgt.getTargetCards() != null && !tgt.getTargetCards().isEmpty()) { return tgt.getTargetCards(); } + List list = new ArrayList(); // Next search for source cards of targeted SAs associated with current ability - else if (tgt != null && tgt.getTargetSAs() != null && !tgt.getTargetSAs().isEmpty()) { + if (tgt != null && tgt.getTargetSAs() != null && !tgt.getTargetSAs().isEmpty()) { for (final SpellAbility ability : tgt.getTargetSAs()) { list.add(ability.getSourceCard()); } return list; } // Lastly Search parent SAs for target cards - else { + // Check for a parent that targets a card SpellAbility parent = this.getParentTargetingCard(); if (null != parent) { @@ -1523,7 +1465,7 @@ public abstract class SpellAbility implements ISpellAbility { list.add(ability.getSourceCard()); } } - } + return list; } @@ -1680,4 +1622,110 @@ public abstract class SpellAbility implements ISpellAbility { public void setCopied(boolean isCopied0) { this.isCopied = isCopied0; } + + /** + * 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.getTarget() != null) { + targets.addAll(child.getTarget().getTargets()); + } + child = child.getParent(); + } + return targets; + } + + public boolean canTargetSpellAbility(final SpellAbility topSA) { + final Target tgt = this.getTarget(); + final String saType = tgt.getTargetSpellAbilityType(); + + if (null == saType) { + // just take this to mean no restrictions - carry on. + } else if (topSA instanceof Spell) { + if (!saType.contains("Spell")) { + return false; + } + } else if (topSA.isTrigger()) { + if (!saType.contains("Triggered")) { + return false; + } + } else if (topSA instanceof AbilityActivated) { + if (!saType.contains("Activated")) { + return false; + } + } else { + return false; //Static ability? Whatever. + } + + final String splitTargetRestrictions = tgt.getSAValidTargeting(); + if (splitTargetRestrictions != null) { + // TODO What about spells with SubAbilities with Targets? + + final Target matchTgt = topSA.getTarget(); + + if (matchTgt == null) { + return false; + } + + boolean result = false; + + for (final Object o : matchTgt.getTargets()) { + if (matchesValid(o, splitTargetRestrictions.split(","))) { + result = true; + break; + } + } + + if (!result) { + return false; + } + } + + return topSA.getSourceCard().isValid(tgt.getValidTgts(), this.getActivatingPlayer(), this.getSourceCard()); + } + + + private boolean matchesValid(final Object o, final String[] valids) { + final Card srcCard = this.getSourceCard(); + final Player activatingPlayer = this.getActivatingPlayer(); + if (o instanceof Card) { + final Card c = (Card) o; + return c.isValid(valids, activatingPlayer, srcCard); + } + + if (o instanceof Player) { + Player p = (Player) o; + if (p.isValid(valids, activatingPlayer, srcCard)) { + return true; + } + } + + return false; + } + + /** + * 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; + } } diff --git a/src/main/java/forge/card/spellability/Target.java b/src/main/java/forge/card/spellability/Target.java index c0b835643cd..87c6cdd80d7 100644 --- a/src/main/java/forge/card/spellability/Target.java +++ b/src/main/java/forge/card/spellability/Target.java @@ -272,7 +272,7 @@ public class Target { * * @return the min targets */ - public final String getMinTargets() { + private final String getMinTargets() { return this.minTargets; } @@ -281,7 +281,7 @@ public class Target { * * @return the max targets */ - public final String getMaxTargets() { + private final String getMaxTargets() { return this.maxTargets; } @@ -464,7 +464,7 @@ public class Target { * * @return a {@link java.util.ArrayList} object. */ - public final ArrayList getTargetCards() { + public final List getTargetCards() { if (this.choice == null) { return new ArrayList(); } @@ -479,7 +479,7 @@ public class Target { * * @return a {@link java.util.ArrayList} object. */ - public final ArrayList getTargetPlayers() { + public final List getTargetPlayers() { if (this.choice == null) { return new ArrayList(); } @@ -494,7 +494,7 @@ public class Target { * * @return a {@link java.util.ArrayList} object. */ - public final ArrayList getTargetSAs() { + public final List getTargetSAs() { if (this.choice == null) { return new ArrayList(); } diff --git a/src/main/java/forge/card/spellability/TargetChoices.java b/src/main/java/forge/card/spellability/TargetChoices.java index 1167673b280..62e5f7ba9a3 100644 --- a/src/main/java/forge/card/spellability/TargetChoices.java +++ b/src/main/java/forge/card/spellability/TargetChoices.java @@ -18,6 +18,7 @@ package forge.card.spellability; import java.util.ArrayList; +import java.util.List; import forge.Card; import forge.game.player.Player; @@ -45,9 +46,9 @@ public class TargetChoices { } // Card or Player are legal targets. - private final ArrayList targetCards = new ArrayList(); - private final ArrayList targetPlayers = new ArrayList(); - private final ArrayList targetSAs = new ArrayList(); + private final List targetCards = new ArrayList(); + private final List targetPlayers = new ArrayList(); + private final List targetSAs = new ArrayList(); /** *

@@ -199,7 +200,7 @@ public class TargetChoices { * * @return a {@link java.util.ArrayList} object. */ - public final ArrayList getTargetCards() { + public final List getTargetCards() { return this.targetCards; } @@ -210,7 +211,7 @@ public class TargetChoices { * * @return a {@link java.util.ArrayList} object. */ - public final ArrayList getTargetPlayers() { + public final List getTargetPlayers() { return this.targetPlayers; } @@ -221,7 +222,7 @@ public class TargetChoices { * * @return a {@link java.util.ArrayList} object. */ - public final ArrayList getTargetSAs() { + public final List getTargetSAs() { return this.targetSAs; } diff --git a/src/main/java/forge/card/spellability/TargetSelection.java b/src/main/java/forge/card/spellability/TargetSelection.java index 4f8185c312e..d9363abf0c4 100644 --- a/src/main/java/forge/card/spellability/TargetSelection.java +++ b/src/main/java/forge/card/spellability/TargetSelection.java @@ -18,23 +18,20 @@ package forge.card.spellability; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; + import com.google.common.base.Predicate; import forge.Card; import forge.CardLists; import forge.FThreads; -import forge.Singletons; import forge.card.ability.AbilityUtils; -import forge.card.ability.ApiType; -import forge.control.input.InputSynchronized; -import forge.control.input.InputSyncronizedBase; +import forge.control.input.InputSelectTargets; +import forge.game.GameState; import forge.game.player.Player; +import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; -import forge.gui.match.CMatchUI; -import forge.view.ButtonUtil; /** *

@@ -45,368 +42,78 @@ import forge.view.ButtonUtil; * @version $Id$ */ public class TargetSelection { - /** - * TODO: Write javadoc for this type. - * - */ - public final class InputSelectTargets extends InputSyncronizedBase { - private final TargetSelection select; - private final List choices; - private final ArrayList alreadyTargeted; - private final boolean targeted; - private final Target tgt; - private final SpellAbility sa; - private final boolean mandatory; - private static final long serialVersionUID = -1091595663541356356L; + private final SpellAbility ability; - /** - * TODO: Write javadoc for Constructor. - * @param select - * @param choices - * @param req - * @param alreadyTargeted - * @param targeted - * @param tgt - * @param sa - * @param mandatory - */ - public InputSelectTargets(TargetSelection select, List choices, ArrayList alreadyTargeted, boolean targeted, Target tgt, SpellAbility sa, boolean mandatory) { - this.select = select; - this.choices = choices; - this.alreadyTargeted = alreadyTargeted; - this.targeted = targeted; - this.tgt = tgt; - this.sa = sa; - this.mandatory = mandatory; - } - @Override - public void showMessage() { - final StringBuilder sb = new StringBuilder(); - sb.append("Targeted: "); - for (final Object o : alreadyTargeted) { - sb.append(o).append(" "); - } - sb.append(tgt.getTargetedString()); - sb.append("\n"); - sb.append(tgt.getVTSelection()); - - showMessage(sb.toString()); - - // If reached Minimum targets, enable OK button - if (!tgt.isMinTargetsChosen(sa.getSourceCard(), sa) || tgt.isDividedAsYouChoose()) { - if (mandatory && tgt.hasCandidates(sa, true)) { - // Player has to click on a target - ButtonUtil.disableAll(); - } else { - ButtonUtil.enableOnlyCancel(); - } - } else { - if (mandatory && tgt.hasCandidates(sa, true)) { - // Player has to click on a target or ok - ButtonUtil.enableOnlyOk(); - } else { - ButtonUtil.enableAllFocusOk(); - } - } - } - - @Override - public void selectButtonCancel() { - select.setCancel(true); - this.done(); - } - - @Override - public void selectButtonOK() { - this.done(); - } - - @Override - public void selectCard(final Card card) { - // leave this in temporarily, there some seriously wrong things - // going on here - if (targeted && !card.canBeTargetedBy(sa)) { - CMatchUI.SINGLETON_INSTANCE.showMessage("Cannot target this card (Shroud? Protection? Restrictions?)."); - } else if (choices.contains(card)) { - if (tgt.isDividedAsYouChoose()) { - final int stillToDivide = tgt.getStillToDivide(); - int allocatedPortion = 0; - // allow allocation only if the max targets isn't reached and there are more candidates - if ((tgt.getNumTargeted() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa)) - && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { - final Integer[] choices = new Integer[stillToDivide]; - for (int i = 1; i <= stillToDivide; i++) { - choices[i - 1] = i; - } - String apiBasedMessage = "Distribute how much to "; - if (sa.getApi() == ApiType.DealDamage) { - apiBasedMessage = "Select how much damage to deal to "; - } else if (sa.getApi() == ApiType.PreventDamage) { - apiBasedMessage = "Select how much damage to prevent to "; - } else if (sa.getApi() == ApiType.PutCounter) { - apiBasedMessage = "Select how many counters to distribute to "; - } - final StringBuilder sb = new StringBuilder(); - sb.append(apiBasedMessage); - sb.append(card.toString()); - Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices); - if (null == chosen) { - return; - } - allocatedPortion = chosen; - } else { // otherwise assign the rest of the damage/protection - allocatedPortion = stillToDivide; - } - tgt.setStillToDivide(stillToDivide - allocatedPortion); - tgt.addDividedAllocation(card, allocatedPortion); - } - tgt.addTarget(card); - this.done(); - } - } // selectCard() - - @Override - public void selectPlayer(final Player player) { - if (alreadyTargeted.contains(player)) { - return; - } - - if (sa.canTarget(player)) { - if (tgt.isDividedAsYouChoose()) { - final int stillToDivide = tgt.getStillToDivide(); - int allocatedPortion = 0; - // allow allocation only if the max targets isn't reached and there are more candidates - if ((alreadyTargeted.size() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa)) - && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { - final Integer[] choices = new Integer[stillToDivide]; - for (int i = 1; i <= stillToDivide; i++) { - choices[i - 1] = i; - } - String apiBasedMessage = "Distribute how much to "; - if (sa.getApi() == ApiType.DealDamage) { - apiBasedMessage = "Select how much damage to deal to "; - } else if (sa.getApi() == ApiType.PreventDamage) { - apiBasedMessage = "Select how much damage to prevent to "; - } - final StringBuilder sb = new StringBuilder(); - sb.append(apiBasedMessage); - sb.append(player.getName()); - Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices); - if (null == chosen) { - return; - } - allocatedPortion = chosen; - } else { // otherwise assign the rest of the damage/protection - allocatedPortion = stillToDivide; - } - tgt.setStillToDivide(stillToDivide - allocatedPortion); - tgt.addDividedAllocation(player, allocatedPortion); - } - tgt.addTarget(player); - this.done(); - } - } - - void done() { - this.stop(); - } + public TargetSelection(final SpellAbility sa) { + this.ability = sa; } - private Target target = null; - private SpellAbility ability = null; - private Card card = null; - private TargetSelection subSelection = null; - - /** - *

- * getTgt. - *

- * - * @return a {@link forge.card.spellability.Target} object. - */ - public final Target getTgt() { - return this.target; + private final Target getTgt() { + return this.ability.getTarget(); } - /** - *

- * Getter for the field ability. - *

- * - * @return a {@link forge.card.spellability.SpellAbility} object. - */ - public final SpellAbility getAbility() { - return this.ability; - } - - /** - *

- * Getter for the field card. - *

- * - * @return a {@link forge.Card} object. - */ - public final Card getCard() { - return this.card; - } - - private SpellAbilityRequirements req = null; - - /** - *

- * setRequirements. - *

- * - * @param reqs - * a {@link forge.card.spellability.SpellAbilityRequirements} - * object. - */ - public final void setRequirements(final SpellAbilityRequirements reqs) { - this.req = reqs; - } - - private boolean bCancel = false; private boolean bTargetingDone = false; - /** - *

- * setCancel. - *

- * - * @param done - * a boolean. - */ - public final void setCancel(final boolean done) { - this.bCancel = done; - } - - /** - *

- * isCanceled. - *

- * - * @return a boolean. - */ - public final boolean isCanceled() { - if (this.bCancel) { - return this.bCancel; - } - - if (this.subSelection == null) { - return false; - } - - return this.subSelection.isCanceled(); - } - - /** - *

- * Constructor for Target_Selection. - *

- * - * @param tgt - * a {@link forge.card.spellability.Target} object. - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - */ - public TargetSelection(final Target tgt, final SpellAbility sa) { - this.target = tgt; - this.ability = sa; - this.card = sa.getSourceCard(); - } - - /** - *

- * doesTarget. - *

- * - * @return a boolean. - */ - public final boolean doesTarget() { - if (this.target == null) { - return false; - } - return this.target.doesTarget(); - } /** *

* resetTargets. *

*/ - public final void clearTargets() { - if (this.target != null) { - this.target.resetTargets(); - this.target.calculateStillToDivide(this.ability.getParam("DividedAsYouChoose"), this.getCard(), this.ability); - } - } - /** - *

- * chooseTargets. - *

- * - * @return a boolean. - */ public final boolean chooseTargets() { - // if not enough targets chosen, reset and cancel Ability - if (this.bCancel || (this.bTargetingDone && !this.target.isMinTargetsChosen(this.card, this.ability))) { - this.bCancel = true; + Target tgt = getTgt(); + final boolean canTarget = tgt != null && tgt.doesTarget(); + if( !canTarget ) + throw new RuntimeException("TargetSelection.chooseTargets called for ability that does not target - " + ability); + + final int minTargets = tgt.getMinTargets(ability.getSourceCard(), ability); + final int maxTargets = tgt.getMaxTargets(ability.getSourceCard(), ability); + final int numTargeted = tgt.getNumTargeted(); + + boolean hasEnoughTargets = minTargets == 0 || numTargeted >= minTargets; + boolean hasAllTargets = numTargeted == maxTargets && maxTargets > 0; + + // if not enough targets chosen, cancel Ability + if (this.bTargetingDone && !hasEnoughTargets) return false; - } + - if (!this.doesTarget() - || this.bTargetingDone && this.target.isMinTargetsChosen(this.card, this.ability) - || this.target.isMaxTargetsChosen(this.card, this.ability) - || this.target.isDividedAsYouChoose() && this.target.getStillToDivide() == 0) { - final AbilitySub abSub = this.ability.getSubAbility(); - - if (abSub == null) { - // if no more SubAbilities finish targeting - return true; - } else { - // Has Sub Ability - this.subSelection = new TargetSelection(abSub.getTarget(), abSub); - this.subSelection.setRequirements(this.req); - this.subSelection.clearTargets(); - return this.subSelection.chooseTargets(); - } + if (this.bTargetingDone && hasEnoughTargets || hasAllTargets || tgt.isDividedAsYouChoose() && tgt.getStillToDivide() == 0) { + return true; } - if (!this.target.hasCandidates(this.ability, true) && !this.target.isMinTargetsChosen(this.card, this.ability)) { + if (!tgt.hasCandidates(this.ability, true) && !hasEnoughTargets) { // Cancel ability if there aren't any valid Candidates - this.bCancel = true; return false; } - - this.chooseValidInput(); - if ( !bCancel ) - return chooseTargets(); - - return false; - } - - /** - * Gets the unique targets. - * - * @param ability - * the ability - * @return the unique targets - */ - public static final ArrayList getUniqueTargets(final SpellAbility ability) { - final ArrayList targets = new ArrayList(); - SpellAbility child = ability; - while (child instanceof AbilitySub) { - child = ((AbilitySub) child).getParent(); - if (child != null && child.getTarget() != null) { - targets.addAll(child.getTarget().getTargets()); + + final List zone = tgt.getZone(); + final boolean mandatory = tgt.getMandatory() && tgt.hasCandidates(this.ability, true); + + final boolean choiceResult; + if (zone.size() == 1 && zone.get(0) == ZoneType.Stack) { + // If Zone is Stack, the choices are handled slightly differently + choiceResult = this.chooseCardFromStack(mandatory); + } else { + List validTargets = this.getValidCardsToTarget(); + if (zone.size() == 1 && zone.get(0) == ZoneType.Battlefield) { + InputSelectTargets inp = new InputSelectTargets(validTargets, ability, mandatory); + FThreads.setInputAndWait(inp); + choiceResult = !inp.hasCancelled(); + bTargetingDone = inp.hasPressedOk(); + } else { + // for every other case an all-purpose GuiChoose + choiceResult = this.chooseCardFromList(validTargets, true, mandatory); } } - - return targets; + // some inputs choose cards one-by-one and need to be called again + return choiceResult && chooseTargets(); } + + // these have been copied over from CardFactoryUtil as they need two extra // parameters for target selection. // however, due to the changes necessary for SA_Requirements this is much @@ -416,21 +123,16 @@ public class TargetSelection { *

* chooseValidInput. *

+ * @return */ - public final void chooseValidInput() { + private final List getValidCardsToTarget() { final Target tgt = this.getTgt(); + final GameState game = ability.getActivatingPlayer().getGame(); final List zone = tgt.getZone(); - final boolean mandatory = this.target.getMandatory() ? this.target.hasCandidates(this.ability, true) : false; final boolean canTgtStack = zone.contains(ZoneType.Stack); - - if (canTgtStack && (zone.size() == 1)) { - // If Zone is Stack, the choices are handled slightly differently - this.chooseCardFromStack(mandatory); - return; - } - - List choices = CardLists.getTargetableCards(CardLists.getValidCards(Singletons.getModel().getGame().getCardsIn(zone), this.target.getValidTgts(), this.ability.getActivatingPlayer(), this.ability.getSourceCard()), this.ability); + List validCards = CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), this.ability.getActivatingPlayer(), this.ability.getSourceCard()); + List choices = CardLists.getTargetableCards(validCards, this.ability); if (canTgtStack) { // Since getTargetableCards doesn't have additional checks if one of the Zones is stack // Remove the activating card from targeting itself if its on the Stack @@ -439,18 +141,18 @@ public class TargetSelection { choices.remove(tgt.getSourceCard()); } } - ArrayList objects = getUniqueTargets(this.ability); + List targetedObjects = this.ability.getUniqueTargets(); if (tgt.isUniqueTargets()) { - for (final Object o : objects) { - if ((o instanceof Card) && objects.contains(o)) { + for (final Object o : targetedObjects) { + if ((o instanceof Card) && targetedObjects.contains(o)) { choices.remove(o); } } } // Remove cards already targeted - final ArrayList targeted = tgt.getTargetCards(); + final List targeted = tgt.getTargetCards(); for (final Card c : targeted) { if (choices.contains(c)) { choices.remove(c); @@ -458,9 +160,9 @@ public class TargetSelection { } // If all cards (including subability targets) must have the same controller - if (tgt.isSameController() && !objects.isEmpty()) { + if (tgt.isSameController() && !targetedObjects.isEmpty()) { final List list = new ArrayList(); - for (final Object o : objects) { + for (final Object o : targetedObjects) { if (o instanceof Card) { list.add((Card) o); } @@ -486,7 +188,7 @@ public class TargetSelection { } // If all cards must have different controllers if (tgt.isDifferentControllers() && !targeted.isEmpty()) { - final List availableControllers = new ArrayList(Singletons.getModel().getGame().getPlayers()); + final List availableControllers = new ArrayList(game.getPlayers()); for (int i = 0; i < targeted.size(); i++) { availableControllers.remove(targeted.get(i).getController()); } @@ -504,7 +206,7 @@ public class TargetSelection { } // If the cards must have a specific controller if (tgt.getDefinedController() != null) { - List pl = AbilityUtils.getDefinedPlayers(card, tgt.getDefinedController(), this.ability); + List pl = AbilityUtils.getDefinedPlayers(ability.getSourceCard(), tgt.getDefinedController(), this.ability); if (pl != null && !pl.isEmpty()) { Player controller = pl.get(0); choices = CardLists.filterControlledBy(choices, controller); @@ -512,21 +214,8 @@ public class TargetSelection { choices.clear(); } } - - if (!tgt.isUniqueTargets()) { - // Previously targeted objects needed to be used for same controller above, but causes problems - // if passed through with certain card functionality to inputTargetSpecific so resetting now - objects = new ArrayList(); - } - - if (zone.contains(ZoneType.Battlefield) && zone.size() == 1) { - InputSynchronized inp = new InputSelectTargets(this, choices, objects, true, this.target, this.ability, mandatory); - FThreads.setInputAndWait(inp); - bTargetingDone = !bCancel; - } else { - this.chooseCardFromList(choices, true, mandatory); - } - } // input_targetValid + return choices; + } /** *

@@ -540,92 +229,63 @@ public class TargetSelection { * @param mandatory * a boolean. */ - public final void chooseCardFromList(final List choices, final boolean targeted, final boolean mandatory) { + private final boolean chooseCardFromList(final List choices, final boolean targeted, final boolean mandatory) { // Send in a list of valid cards, and popup a choice box to target - final Card dummy = new Card(); - dummy.setName("[FINISH TARGETING]"); - final SpellAbility sa = this.ability; - final String message = this.target.getVTSelection(); + final GameState game = ability.getActivatingPlayer().getGame(); - final Card divBattlefield = new Card(); - divBattlefield.setName("--CARDS ON BATTLEFIELD:--"); - final Card divExile = new Card(); - divExile.setName("--CARDS IN EXILE:--"); - final Card divGrave = new Card(); - divGrave.setName("--CARDS IN GRAVEYARD:--"); - final Card divLibrary = new Card(); - divLibrary.setName("--CARDS IN LIBRARY:--"); - final Card divStack = new Card(); - divStack.setName("--CARDS IN STACK:--"); - - List choicesZoneUnfiltered = choices; final List crdsBattle = new ArrayList(); final List crdsExile = new ArrayList(); final List crdsGrave = new ArrayList(); final List crdsLibrary = new ArrayList(); final List crdsStack = new ArrayList(); - for (final Card inZone : choicesZoneUnfiltered) { - if (Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield).contains(inZone)) { - crdsBattle.add(inZone); - } else if (Singletons.getModel().getGame().getCardsIn(ZoneType.Exile).contains(inZone)) { - crdsExile.add(inZone); - } else if (Singletons.getModel().getGame().getCardsIn(ZoneType.Graveyard).contains(inZone)) { - crdsGrave.add(inZone); - } else if (Singletons.getModel().getGame().getCardsIn(ZoneType.Library).contains(inZone)) { - crdsLibrary.add(inZone); - } else if (Singletons.getModel().getGame().getCardsIn(ZoneType.Stack).contains(inZone)) { - crdsStack.add(inZone); - } + for (final Card inZone : choices) { + Zone zz = game.getZoneOf(inZone); + if (zz.is(ZoneType.Battlefield)) crdsBattle.add(inZone); + else if (zz.is(ZoneType.Exile)) crdsExile.add(inZone); + else if (zz.is(ZoneType.Graveyard)) crdsGrave.add(inZone); + else if (zz.is(ZoneType.Library)) crdsLibrary.add(inZone); + else if (zz.is(ZoneType.Stack)) crdsStack.add(inZone); } - List choicesFiltered = new ArrayList(); - if (crdsBattle.size() >= 1) { - choicesFiltered.add(divBattlefield); + List choicesFiltered = new ArrayList(); + if (!crdsBattle.isEmpty()) { + choicesFiltered.add("--CARDS ON BATTLEFIELD:--"); choicesFiltered.addAll(crdsBattle); - crdsBattle.clear(); } - if (crdsExile.size() >= 1) { - choicesFiltered.add(divExile); + if (!crdsExile.isEmpty()) { + choicesFiltered.add("--CARDS IN EXILE:--"); choicesFiltered.addAll(crdsExile); - crdsExile.clear(); } - if (crdsGrave.size() >= 1) { - choicesFiltered.add(divGrave); + if (!crdsGrave.isEmpty()) { + choicesFiltered.add("--CARDS IN GRAVEYARD:--"); choicesFiltered.addAll(crdsGrave); - crdsGrave.clear(); } - if (crdsLibrary.size() >= 1) { - choicesFiltered.add(divLibrary); + if (!crdsLibrary.isEmpty()) { + choicesFiltered.add("--CARDS IN LIBRARY:--"); choicesFiltered.addAll(crdsLibrary); - crdsLibrary.clear(); } - if (crdsStack.size() >= 1) { - choicesFiltered.add(divStack); + if (!crdsStack.isEmpty()) { + choicesFiltered.add("--CARDS IN STACK:--"); choicesFiltered.addAll(crdsStack); - crdsStack.clear(); } - final Target tgt = this.getTgt(); - - final List choicesWithDone = choicesFiltered; - if (tgt.isMinTargetsChosen(sa.getSourceCard(), sa)) { + final String msgDone = "[FINISH TARGETING]"; + if (this.getTgt().isMinTargetsChosen(this.ability.getSourceCard(), this.ability)) { // is there a more elegant way of doing this? - choicesWithDone.add(dummy); + choicesFiltered.add(msgDone); } - - final Card check = GuiChoose.oneOrNone(message, choicesWithDone); - if (check != null) { - final Card c = check; - if (!c.equals(divBattlefield) && !c.equals(divExile) && !c.equals(divGrave) - && !c.equals(divLibrary) && !c.equals(divStack)) { - if (c.equals(dummy)) { - bTargetingDone = true; - } else { - tgt.addTarget(c); - } - } - } else { - this.setCancel(true); + + final Object chosen = GuiChoose.oneOrNone(getTgt().getVTSelection(), choicesFiltered); + if (chosen == null) { + return false; } + if (msgDone.equals(chosen)) { + bTargetingDone = true; + return true; + } + + if (chosen instanceof Card ) + this.getTgt().addTarget(chosen); + return true; } /** @@ -636,74 +296,36 @@ public class TargetSelection { * @param mandatory * a boolean. */ - public final void chooseCardFromStack(final boolean mandatory) { - final Target tgt = this.target; + private final boolean chooseCardFromStack(final boolean mandatory) { + final Target tgt = this.getTgt(); final String message = tgt.getVTSelection(); - final TargetSelection select = this; - final String doneDummy = "[FINISH TARGETING]"; - // Find what's targetable, then allow human to choose - final ArrayList choosables = TargetSelection.getTargetableOnStack(this.ability, select.getTgt()); + final List selectOptions = new ArrayList(); - final HashMap map = new HashMap(); - - for (final SpellAbility sa : choosables) { - if (!tgt.getTargetSAs().contains(sa)) { - map.put(choosables.indexOf(sa) + ". " + sa.getStackDescription(), sa); - } + final GameState game = ability.getActivatingPlayer().getGame(); + for (int i = 0; i < game.getStack().size(); i++) { + SpellAbility stackItem = game.getStack().peekAbility(i); + if( ability.canTargetSpellAbility(stackItem)) + selectOptions.add(stackItem); } - + if (tgt.isMinTargetsChosen(this.ability.getSourceCard(), this.ability)) { - map.put(doneDummy, null); + selectOptions.add("[FINISH TARGETING]"); } - String[] choices = new String[map.keySet().size()]; - choices = map.keySet().toArray(choices); - - if (choices.length == 0) { - select.setCancel(true); + if (selectOptions.isEmpty()) { + return false; } else { - final String madeChoice = GuiChoose.oneOrNone(message, choices); - - if (madeChoice != null) { - if (madeChoice.equals(doneDummy)) { - bTargetingDone = true; - } else { - tgt.addTarget(map.get(madeChoice)); - } - } else { - select.setCancel(true); + final Object madeChoice = GuiChoose.oneOrNone(message, selectOptions); + if (madeChoice == null) { + return false; } + if (madeChoice instanceof SpellAbility) { + tgt.addTarget(madeChoice); + } else // only 'FINISH TARGETING' remains + bTargetingDone = true; } - } - - // TODO The following three functions are Utility functions for - // TargetOnStack, probably should be moved - // The following should be select.getTargetableOnStack() - /** - *

- * getTargetableOnStack. - *

- * - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - * @param tgt - * a {@link forge.card.spellability.Target} object. - * @return a {@link java.util.ArrayList} object. - */ - public static ArrayList getTargetableOnStack(final SpellAbility sa, final Target tgt) { - final ArrayList choosables = new ArrayList(); - - for (int i = 0; i < Singletons.getModel().getGame().getStack().size(); i++) { - choosables.add(Singletons.getModel().getGame().getStack().peekAbility(i)); - } - - for (int i = 0; i < choosables.size(); i++) { - if (!TargetSelection.matchSpellAbility(sa, choosables.get(i), tgt)) { - choosables.remove(i); - } - } - return choosables; + return true; } /** @@ -719,106 +341,4 @@ public class TargetSelection { * a {@link forge.card.spellability.Target} object. * @return a boolean. */ - public static boolean matchSpellAbility(final SpellAbility sa, final SpellAbility topSA, final Target tgt) { - final String saType = tgt.getTargetSpellAbilityType(); - - if (null == saType) { - // just take this to mean no restrictions - carry on. - } else if (topSA instanceof Spell) { - if (!saType.contains("Spell")) { - return false; - } - } else if (topSA.isTrigger()) { - if (!saType.contains("Triggered")) { - return false; - } - } else if (topSA instanceof AbilityActivated) { - if (!saType.contains("Activated")) { - return false; - } - } else { - return false; //Static ability? Whatever. - } - - final String splitTargetRestrictions = tgt.getSAValidTargeting(); - if (splitTargetRestrictions != null) { - // TODO What about spells with SubAbilities with Targets? - - final Target matchTgt = topSA.getTarget(); - - if (matchTgt == null) { - return false; - } - - boolean result = false; - - for (final Object o : matchTgt.getTargets()) { - if (TargetSelection.matchesValid(o, splitTargetRestrictions.split(","), sa)) { - result = true; - break; - } - } - - if (!result) { - return false; - } - } - - if (!TargetSelection.matchesValidSA(topSA, tgt.getValidTgts(), sa)) { - return false; - } - - return true; - } - - /** - *

- * matchesValid. - *

- * - * @param o - * a {@link java.lang.Object} object. - * @param valids - * an array of {@link java.lang.String} objects. - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - * @return a boolean. - */ - private static boolean matchesValid(final Object o, final String[] valids, final SpellAbility sa) { - final Card srcCard = sa.getSourceCard(); - final Player activatingPlayer = sa.getActivatingPlayer(); - if (o instanceof Card) { - final Card c = (Card) o; - return c.isValid(valids, activatingPlayer, srcCard); - } - - if (o instanceof Player) { - Player p = (Player) o; - if (p.isValid(valids, sa.getActivatingPlayer(), sa.getSourceCard())) { - return true; - } - } - - return false; - } - - /** - *

- * matchesValidSA. - *

- * - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - * @param valids - * an array of {@link java.lang.String} objects. - * @param source - * a {@link forge.card.spellability.SpellAbility} object. - * @return a boolean. - */ - private static boolean matchesValidSA(final SpellAbility sa, final String[] valids, final SpellAbility source) { - final Card srcCard = source.getSourceCard(); - final Player activatingPlayer = source.getActivatingPlayer(); - final Card c = sa.getSourceCard(); - return c.isValid(valids, activatingPlayer, srcCard); - } } diff --git a/src/main/java/forge/card/staticability/StaticAbility.java b/src/main/java/forge/card/staticability/StaticAbility.java index 3ca3ff50eba..709fd184631 100644 --- a/src/main/java/forge/card/staticability/StaticAbility.java +++ b/src/main/java/forge/card/staticability/StaticAbility.java @@ -358,28 +358,26 @@ public class StaticAbility { * the originalCost * @return the modified ManaCost */ - public final ManaCostBeingPaid applyAbility(final String mode, final SpellAbility sa, final ManaCostBeingPaid originalCost) { + 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 originalCost; + return; } if (this.isSuppressed() || !this.checkConditions()) { - return originalCost; + return; } if (mode.equals("RaiseCost")) { - return StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost); + StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost); } if (mode.equals("ReduceCost")) { - return StaticAbilityCostChange.applyReduceCostAbility(this, sa, originalCost); + StaticAbilityCostChange.applyReduceCostAbility(this, sa, originalCost); } if (mode.equals("SetCost")) { //Set cost is only used by Trinisphere - return StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost); + StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost); } - - return originalCost; } /** diff --git a/src/main/java/forge/card/staticability/StaticAbilityContinuous.java b/src/main/java/forge/card/staticability/StaticAbilityContinuous.java index 9fd6329501b..99107790b03 100644 --- a/src/main/java/forge/card/staticability/StaticAbilityContinuous.java +++ b/src/main/java/forge/card/staticability/StaticAbilityContinuous.java @@ -154,6 +154,12 @@ public class StaticAbilityContinuous { 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("AddHiddenKeyword")) { diff --git a/src/main/java/forge/card/staticability/StaticAbilityCostChange.java b/src/main/java/forge/card/staticability/StaticAbilityCostChange.java index c989725b489..468ff7ab41f 100644 --- a/src/main/java/forge/card/staticability/StaticAbilityCostChange.java +++ b/src/main/java/forge/card/staticability/StaticAbilityCostChange.java @@ -45,51 +45,50 @@ public class StaticAbilityCostChange { * @param originalCost * a ManaCost */ - public static ManaCostBeingPaid applyRaiseCostAbility(final StaticAbility staticAbility, final SpellAbility sa - , final ManaCostBeingPaid originalCost) { + public static void applyRaiseCostAbility(final StaticAbility staticAbility, final SpellAbility sa, final ManaCostBeingPaid manaCost) { final HashMap params = staticAbility.getMapParams(); final Card hostCard = staticAbility.getHostCard(); final Player activator = sa.getActivatingPlayer(); final Card card = sa.getSourceCard(); final String amount = params.get("Amount"); - final ManaCostBeingPaid manaCost = new ManaCostBeingPaid(originalCost.toString()); + if (params.containsKey("ValidCard") && !card.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard)) { - return originalCost; + return; } if (params.containsKey("Activator") && ((activator == null) || !activator.isValid(params.get("Activator"), hostCard.getController(), hostCard))) { - return originalCost; + return; } if (params.containsKey("Type")) { if (params.get("Type").equals("Spell")) { if (!sa.isSpell()) { - return originalCost; + return; } } else if (params.get("Type").equals("Ability")) { if (!(sa instanceof AbilityActivated)) { - return originalCost; + return; } } else if (params.get("Type").equals("NonManaAbility")) { if (!(sa instanceof AbilityActivated) || sa.isManaAbility()) { - return originalCost; + return; } } else if (params.get("Type").equals("Flashback")) { if (!sa.isFlashBackAbility()) { - return originalCost; + return; } } } if (params.containsKey("AffectedZone") && !card.isInZone(ZoneType.smartValueOf(params.get("AffectedZone")))) { - return originalCost; + return; } if (params.containsKey("ValidTarget")) { Target tgt = sa.getTarget(); if (tgt == null) { - return originalCost; + return; } boolean targetValid = false; for (Object target : tgt.getTargets()) { @@ -101,13 +100,13 @@ public class StaticAbilityCostChange { } } if (!targetValid) { - return originalCost; + return; } } if (params.containsKey("ValidSpellTarget")) { Target tgt = sa.getTarget(); if (tgt == null) { - return originalCost; + return; } boolean targetValid = false; for (Object target : tgt.getTargets()) { @@ -119,7 +118,7 @@ public class StaticAbilityCostChange { } } if (!targetValid) { - return originalCost; + return; } } int value = 0; @@ -155,8 +154,6 @@ public class StaticAbilityCostChange { manaCost.increaseShard(ManaCostShard.GREEN, value); } } - - return manaCost; } /** @@ -169,58 +166,57 @@ public class StaticAbilityCostChange { * @param originalCost * a ManaCost */ - public static ManaCostBeingPaid applyReduceCostAbility(final StaticAbility staticAbility, final SpellAbility sa - , final ManaCostBeingPaid originalCost) { + public static void applyReduceCostAbility(final StaticAbility staticAbility, final SpellAbility sa, final ManaCostBeingPaid manaCost) { //Can't reduce zero cost - if (originalCost.toString().equals("0")) { - return originalCost; + if (manaCost.toString().equals("0")) { + return; } final HashMap params = staticAbility.getMapParams(); final Card hostCard = staticAbility.getHostCard(); final Player activator = sa.getActivatingPlayer(); final Card card = sa.getSourceCard(); final String amount = params.get("Amount"); - final ManaCostBeingPaid manaCost = new ManaCostBeingPaid(originalCost.toString()); + if (params.containsKey("ValidCard") && !card.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard)) { - return originalCost; + return; } if (params.containsKey("Activator") && ((activator == null) || !activator.isValid(params.get("Activator"), hostCard.getController(), hostCard))) { - return originalCost; + return; } if (params.containsKey("Type")) { if (params.get("Type").equals("Spell")) { if (!sa.isSpell()) { - return originalCost; + return; } } else if (params.get("Type").equals("Ability")) { if (!(sa instanceof AbilityActivated)) { - return originalCost; + return; } } else if (params.get("Type").equals("Buyback")) { if (!sa.isBuyBackAbility()) { - return originalCost; + return; } } else if (params.get("Type").equals("Cycling")) { if (!sa.isCycling()) { - return originalCost; + return; } } else if (params.get("Type").equals("Equip")) { if (!(sa instanceof AbilityActivated) || !sa.hasParam("Equip")) { - return originalCost; + return; } } else if (params.get("Type").equals("Flashback")) { if (!sa.isFlashBackAbility()) { - return originalCost; + return; } } } if (params.containsKey("ValidTarget")) { Target tgt = sa.getTarget(); if (tgt == null) { - return originalCost; + return; } boolean targetValid = false; for (Object target : tgt.getTargets()) { @@ -232,11 +228,11 @@ public class StaticAbilityCostChange { } } if (!targetValid) { - return originalCost; + return; } } if (params.containsKey("AffectedZone") && !card.isInZone(ZoneType.smartValueOf(params.get("AffectedZone")))) { - return originalCost; + return; } int value = 0; if ("X".equals(amount)) { @@ -259,8 +255,5 @@ public class StaticAbilityCostChange { manaCost.decreaseShard(ManaCostShard.GREEN, value); } } - - - return manaCost; } } diff --git a/src/main/java/forge/card/trigger/TriggerCountered.java b/src/main/java/forge/card/trigger/TriggerCountered.java new file mode 100644 index 00000000000..9e8cafc4ca2 --- /dev/null +++ b/src/main/java/forge/card/trigger/TriggerCountered.java @@ -0,0 +1,97 @@ +/* + * 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.card.trigger; + +import forge.Card; +import forge.card.spellability.SpellAbility; + +/** + *

+ * Trigger_Countered class. + *

+ * + * @author Forge + * @version $Id: TriggerCountered.java 17802 2012-10-31 08:05:14Z Max mtg $ + */ +public class TriggerCountered extends Trigger { + + /** + *

+ * Constructor for Trigger_Countered. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerCountered(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.getMapParams().containsKey("ValidCard")) { + if (!matchesValid(runParams2.get("Card"), this.getMapParams().get("ValidCard").split(","), + this.getHostCard())) { + return false; + } + } + + if (this.getMapParams().containsKey("ValidPlayer")) { + if (!matchesValid(runParams2.get("Player"), this.getMapParams().get("ValidPlayer").split(","), + this.getHostCard())) { + return false; + } + } + + if (this.getMapParams().containsKey("ValidCause")) { + if (runParams2.get("Cause") == null) { + return false; + } + if (!matchesValid(runParams2.get("Cause"), this.getMapParams().get("ValidCause").split(","), + this.getHostCard())) { + return false; + } + } + return true; + } + + /** {@inheritDoc} */ + @Override + public final Trigger getCopy() { + final Trigger copy = new TriggerCountered(this.getMapParams(), this.getHostCard(), this.isIntrinsic()); + if (this.getOverridingAbility() != null) { + copy.setOverridingAbility(this.getOverridingAbility()); + } + + copyFieldsTo(copy); + return copy; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + sa.setTriggeringObject("Cause", this.getRunParams().get("Cause")); + sa.setTriggeringObject("Player", this.getRunParams().get("Player")); + } +} diff --git a/src/main/java/forge/card/trigger/TriggerDestroyed.java b/src/main/java/forge/card/trigger/TriggerDestroyed.java new file mode 100644 index 00000000000..9ce42966148 --- /dev/null +++ b/src/main/java/forge/card/trigger/TriggerDestroyed.java @@ -0,0 +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.card.trigger; + +import forge.Card; +import forge.card.spellability.SpellAbility; + +/** + *

+ * Trigger_Destroyed class. + *

+ * + * @author Forge + * @version $Id: TriggerDestroyed.java 17802 2012-10-31 08:05:14Z Max mtg $ + */ +public class TriggerDestroyed extends Trigger { + + /** + *

+ * Constructor for Trigger_Destroyed. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerDestroyed(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.getMapParams().containsKey("ValidCauser")) { + if (!matchesValid(runParams2.get("Causer"), this.getMapParams().get("ValidCauser").split(","), + this.getHostCard())) { + return false; + } + } + if (this.getMapParams().containsKey("ValidCard")) { + if (!matchesValid(runParams2.get("Card"), this.getMapParams().get("ValidCard").split(","), + this.getHostCard())) { + return false; + } + } + return true; + } + + /** {@inheritDoc} */ + @Override + public final Trigger getCopy() { + final Trigger copy = new TriggerDestroyed(this.getMapParams(), this.getHostCard(), this.isIntrinsic()); + if (this.getOverridingAbility() != null) { + copy.setOverridingAbility(this.getOverridingAbility()); + } + + copyFieldsTo(copy); + return copy; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + sa.setTriggeringObject("Causer", this.getRunParams().get("Causer")); + } +} diff --git a/src/main/java/forge/card/trigger/TriggerDevoured.java b/src/main/java/forge/card/trigger/TriggerDevoured.java new file mode 100644 index 00000000000..4b83937cf2c --- /dev/null +++ b/src/main/java/forge/card/trigger/TriggerDevoured.java @@ -0,0 +1,79 @@ +/* + * 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.card.trigger; + +import forge.Card; +import forge.card.spellability.SpellAbility; + +/** + *

+ * Trigger_Devoured class. + *

+ * + * @author Forge + * @version $Id: TriggerSacrificed.java 17802 2012-10-31 08:05:14Z Max mtg $ + */ +public class TriggerDevoured extends Trigger { + + /** + *

+ * Constructor for Trigger_Devoured. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerDevoured(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("Devoured"); + if (this.getMapParams().containsKey("ValidDevoured")) { + if (!sac.isValid(this.getMapParams().get("ValidDevoured").split(","), this.getHostCard().getController(), + this.getHostCard())) { + return false; + } + } + return true; + } + + /** {@inheritDoc} */ + @Override + public final Trigger getCopy() { + final Trigger copy = new TriggerDevoured(this.getMapParams(), this.getHostCard(), this.isIntrinsic()); + if (this.getOverridingAbility() != null) { + copy.setOverridingAbility(this.getOverridingAbility()); + } + + copyFieldsTo(copy); + return copy; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Devoured", this.getRunParams().get("Devoured")); + } +} diff --git a/src/main/java/forge/card/trigger/TriggerHandler.java b/src/main/java/forge/card/trigger/TriggerHandler.java index 1fc800f7e29..bf37f7b5691 100644 --- a/src/main/java/forge/card/trigger/TriggerHandler.java +++ b/src/main/java/forge/card/trigger/TriggerHandler.java @@ -39,6 +39,7 @@ import forge.game.GlobalRuleChange; import forge.game.ai.ComputerUtil; import forge.game.phase.PhaseType; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -168,27 +169,29 @@ public class TriggerHandler { if (game.getStack().isFrozen() || holdTrigger) { waitingTriggers.add(new TriggerWaiting(mode, runParams)); } else { - runWaitingTrigger(new TriggerWaiting(mode, runParams), true); + runWaitingTrigger(new TriggerWaiting(mode, runParams)); } // Tell auto stop to stop } - public final boolean runWaitingTriggers(boolean runStaticEffects) { + public final boolean runWaitingTriggers() { ArrayList waiting = new ArrayList(waitingTriggers); waitingTriggers.clear(); if (waiting.isEmpty()) { return false; } + + Singletons.getModel().getGame().getAction().checkStaticAbilities(); boolean haveWaiting = false; for (TriggerWaiting wt : waiting) { - haveWaiting |= runWaitingTrigger(wt, runStaticEffects); + haveWaiting |= runWaitingTrigger(wt); } return haveWaiting; } - public final boolean runWaitingTrigger(TriggerWaiting wt, boolean runStaticEffects) { + public final boolean runWaitingTrigger(TriggerWaiting wt) { final TriggerType mode = wt.getMode(); final Map runParams = wt.getParams(); final GameState game = Singletons.getModel().getGame(); @@ -419,7 +422,7 @@ public class TriggerHandler { if (regtrig.isStatic()) { if (wrapperAbility.getActivatingPlayer().isHuman()) { - game.getActionPlay().playSpellAbilityNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility, false); + ((HumanPlayer)wrapperAbility.getActivatingPlayer()).playSpellAbilityNoStack(wrapperAbility); } else { wrapperAbility.doTrigger(isMandatory, (AIPlayer)wrapperAbility.getActivatingPlayer()); ComputerUtil.playNoStack((AIPlayer)wrapperAbility.getActivatingPlayer(), wrapperAbility, game); diff --git a/src/main/java/forge/card/trigger/TriggerType.java b/src/main/java/forge/card/trigger/TriggerType.java index 3de64f9b95c..622a7b980a5 100644 --- a/src/main/java/forge/card/trigger/TriggerType.java +++ b/src/main/java/forge/card/trigger/TriggerType.java @@ -20,6 +20,9 @@ public enum TriggerType { ChangesZone(TriggerChangesZone.class), Clashed(TriggerClashed.class), + Destroyed(TriggerDestroyed.class), + Devoured(TriggerDevoured.class), + Countered(TriggerCountered.class), TapsForMana(TriggerTapsForMana.class), CounterAdded(TriggerCounterAdded.class), CounterRemoved(TriggerCounterRemoved.class), diff --git a/src/main/java/forge/card/trigger/WrappedAbility.java b/src/main/java/forge/card/trigger/WrappedAbility.java index f679f1cdf3f..0722ab7054f 100644 --- a/src/main/java/forge/card/trigger/WrappedAbility.java +++ b/src/main/java/forge/card/trigger/WrappedAbility.java @@ -19,6 +19,7 @@ import forge.card.spellability.Target; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.gui.GuiDialog; @@ -218,11 +219,6 @@ public class WrappedAbility extends Ability implements ISpellAbility { return sa.getTargetPlayer(); } - @Override - public int getXManaCost() { - return sa.getXManaCost(); - } - @Override public boolean isAbility() { return sa.isAbility(); @@ -284,11 +280,6 @@ public class WrappedAbility extends Ability implements ISpellAbility { sa.setFlashBackAbility(flashBackAbility); } - @Override - public void setIsXCost(final boolean b) { - sa.setIsXCost(b); - } - @Override public void setMultiKickerManaCost(final ManaCost cost) { sa.setMultiKickerManaCost(cost); @@ -419,7 +410,7 @@ public class WrappedAbility extends Ability implements ISpellAbility { } if (getActivatingPlayer().isHuman()) { - game.getActionPlay().playSpellAbilityNoStack(getActivatingPlayer(), sa, true); + ((HumanPlayer)getActivatingPlayer()).playSpellAbilityNoStack(sa, true); } else { // commented out because i don't think this should be called // again here diff --git a/src/main/java/forge/control/FControl.java b/src/main/java/forge/control/FControl.java index 21d56e514c5..8589c833d54 100644 --- a/src/main/java/forge/control/FControl.java +++ b/src/main/java/forge/control/FControl.java @@ -34,7 +34,7 @@ import javax.swing.WindowConstants; import forge.Singletons; import forge.control.KeyboardShortcuts.Shortcut; import forge.game.ai.AiProfileUtil; -import forge.game.player.Player; +import forge.game.player.HumanPlayer; import forge.gui.SOverlayUtils; import forge.gui.deckeditor.CDeckEditorUI; import forge.gui.deckeditor.VDeckEditorUI; @@ -284,8 +284,8 @@ public enum FControl { } /** @return {@link forge.game.player.Player} */ - private Player localPlayer; - public Player getPlayer() { + private HumanPlayer localPlayer; + public HumanPlayer getPlayer() { return localPlayer; } @@ -305,7 +305,7 @@ public enum FControl { * TODO: Write javadoc for this method. * @param localHuman */ - public void setPlayer(Player localHuman) { + public void setPlayer(HumanPlayer localHuman) { localPlayer = localHuman; } diff --git a/src/main/java/forge/control/input/InputAttack.java b/src/main/java/forge/control/input/InputAttack.java index 5a6d05d1631..55367ce8f11 100644 --- a/src/main/java/forge/control/input/InputAttack.java +++ b/src/main/java/forge/control/input/InputAttack.java @@ -47,8 +47,9 @@ public class InputAttack extends InputBase { private static final long serialVersionUID = 7849903731842214245L; private final GameState game; - public InputAttack(GameState game0) { - game = game0; + public InputAttack(Player human) { + super(human); + game = human.getGame(); } @@ -119,6 +120,7 @@ public class InputAttack extends InputBase { // just to make sure the attack symbol is marked human.getZone(ZoneType.Battlefield).updateObservers(); CombatUtil.showCombat(); + ButtonUtil.enableOnlyOk(); } else { SDisplayUtil.remind(VMessage.SINGLETON_INSTANCE); diff --git a/src/main/java/forge/control/input/InputAutoPassPriority.java b/src/main/java/forge/control/input/InputAutoPassPriority.java new file mode 100644 index 00000000000..d3244abc907 --- /dev/null +++ b/src/main/java/forge/control/input/InputAutoPassPriority.java @@ -0,0 +1,21 @@ +package forge.control.input; + +import forge.game.player.Player; + +/** + * TODO: Write javadoc for this type. + * + */ +public class InputAutoPassPriority extends InputBase { + private static final long serialVersionUID = -7520803307255234647L; + + public InputAutoPassPriority(Player player) { + super(player); + } + + @Override + public void showMessage() { + passPriority(); + } + +} diff --git a/src/main/java/forge/control/input/InputBase.java b/src/main/java/forge/control/input/InputBase.java index f835e028ab3..d3da28bee70 100644 --- a/src/main/java/forge/control/input/InputBase.java +++ b/src/main/java/forge/control/input/InputBase.java @@ -18,6 +18,7 @@ package forge.control.input; import forge.Card; +import forge.FThreads; import forge.Singletons; import forge.game.player.Player; import forge.gui.match.CMatchUI; @@ -33,7 +34,11 @@ import forge.gui.match.CMatchUI; public abstract class InputBase implements java.io.Serializable, Input { /** Constant serialVersionUID=-6539552513871194081L. */ private static final long serialVersionUID = -6539552513871194081L; - + protected final Player player; + public InputBase(Player player) { + this.player = player; + } + // showMessage() is always the first method called @Override public abstract void showMessage(); @@ -64,4 +69,18 @@ public abstract class InputBase implements java.io.Serializable, Input { } protected void afterStop() { } + + + + protected void passPriority() { + final Runnable pass = new Runnable() { + @Override public void run() { + player.getController().passPriority(); + } + }; + if( FThreads.isEDT() ) + FThreads.invokeInNewThread(pass, true); + else + pass.run(); + } } \ No newline at end of file diff --git a/src/main/java/forge/control/input/InputBlock.java b/src/main/java/forge/control/input/InputBlock.java index e2f172403bf..29ad874a3c0 100644 --- a/src/main/java/forge/control/input/InputBlock.java +++ b/src/main/java/forge/control/input/InputBlock.java @@ -23,7 +23,6 @@ import java.util.List; import forge.Card; import forge.Singletons; -import forge.control.FControl; import forge.game.GameState; import forge.game.phase.CombatUtil; import forge.game.player.Player; @@ -48,14 +47,13 @@ public class InputBlock extends InputBase { private Card currentAttacker = null; private final HashMap> allBlocking = new HashMap>(); - private final Player defender; /** * TODO: Write javadoc for Constructor. * @param priority */ - public InputBlock(Player priority) { - defender = priority; + public InputBlock(Player human) { + super(human); } /** @@ -98,14 +96,14 @@ public class InputBlock extends InputBase { @Override public final void selectButtonOK() { final GameState game = Singletons.getModel().getGame(); - if (CombatUtil.finishedMandatoryBlocks(game.getCombat(), defender)) { + if (CombatUtil.finishedMandatoryBlocks(game.getCombat(), player)) { // Done blocking ButtonUtil.reset(); CombatUtil.orderMultipleCombatants(game.getCombat()); currentAttacker = null; allBlocking.clear(); - FControl.SINGLETON_INSTANCE.getPlayer().getController().passPriority(); + passPriority(); } } @@ -121,8 +119,8 @@ public class InputBlock extends InputBase { } else { Zone zone = Singletons.getModel().getGame().getZoneOf(card); // Make sure this card is valid to even be a blocker - if (this.currentAttacker != null && card.isCreature() && card.getController().equals(defender) - && zone.is(ZoneType.Battlefield, defender)) { + if (this.currentAttacker != null && card.isCreature() && card.getController().equals(player) + && zone.is(ZoneType.Battlefield, player)) { // Create a new blockedBy list if it doesn't exist if (!this.allBlocking.containsKey(card)) { this.allBlocking.put(card, new ArrayList()); diff --git a/src/main/java/forge/control/input/InputCleanup.java b/src/main/java/forge/control/input/InputCleanup.java index 5e8f98fddbb..0f0f21c4ef5 100644 --- a/src/main/java/forge/control/input/InputCleanup.java +++ b/src/main/java/forge/control/input/InputCleanup.java @@ -24,7 +24,6 @@ import forge.game.GameState; import forge.game.player.Player; import forge.game.zone.Zone; import forge.game.zone.ZoneType; -import forge.gui.match.CMatchUI; import forge.view.ButtonUtil; /** @@ -44,9 +43,9 @@ public class InputCleanup extends InputBase { * TODO: Write javadoc for Constructor. * @param game */ - public InputCleanup(GameState game) { - super(); - this.game = game; + public InputCleanup(Player player) { + super(player); + this.game = player.getGame(); } /** {@inheritDoc} */ @@ -59,7 +58,7 @@ public class InputCleanup extends InputBase { final int max = active.getMaxHandSize(); // goes to the next phase if (active.isUnlimitedHandSize() || n <= max || n <= 0 || active != turnOwner) { - active.getController().passPriority(); + passPriority(); return; } ButtonUtil.disableAll(); @@ -69,7 +68,7 @@ public class InputCleanup extends InputBase { final StringBuffer sb = new StringBuffer(); sb.append("Cleanup Phase: You can only have a maximum of ").append(max); sb.append(" cards, you currently have ").append(n).append(" cards in your hand - select a card to discard"); - CMatchUI.SINGLETON_INSTANCE.showMessage(sb.toString()); + showMessage(sb.toString()); } /** {@inheritDoc} */ diff --git a/src/main/java/forge/control/input/InputControl.java b/src/main/java/forge/control/input/InputControl.java index a2be674d7e9..b4b48c19020 100644 --- a/src/main/java/forge/control/input/InputControl.java +++ b/src/main/java/forge/control/input/InputControl.java @@ -19,9 +19,14 @@ package forge.control.input; import java.util.Stack; +import forge.Singletons; +import forge.game.GameAge; import forge.game.GameState; +import forge.game.GameType; +import forge.game.MatchController; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.player.PlayerController; import forge.game.zone.MagicStack; @@ -41,6 +46,11 @@ public class InputControl extends MyObservable implements java.io.Serializable { private final Stack inputStack = new Stack(); + private final MatchController match; + public InputControl(MatchController matchController) { + match = matchController; + } + /** *

* Setter for the field input. @@ -114,39 +124,41 @@ public class InputControl extends MyObservable implements java.io.Serializable { * @return a {@link forge.control.input.InputBase} object. */ public final Input getActualInput(GameState game) { - if ( !game.hasMulliganned() ) - return new InputMulligan(); + GameAge age = game.getAge(); + if ( age == GameAge.Mulligan ) { + HumanPlayer human = Singletons.getControl().getPlayer(); + return game.getType() == GameType.Commander ? new InputPartialParisMulligan(match, human) : new InputMulligan(match, human); + } + if ( age != GameAge.Play ) + return inputLock; + + if (!this.inputStack.isEmpty()) { // incoming input to Control + return this.inputStack.peek(); + } + final PhaseHandler handler = game.getPhaseHandler(); final PhaseType phase = handler.getPhase(); final Player playerTurn = handler.getPlayerTurn(); final Player priority = handler.getPriorityPlayer(); - final MagicStack stack = game.getStack(); - - - // TODO this resolving portion needs more work, but fixes Death Cloud - // issues - if (!this.inputStack.isEmpty()) { // incoming input to Control - return this.inputStack.peek(); - } - - // If the Phase we're in doesn't allow for Priority, return null to move - // to next phase - if (!handler.isPlayerPriorityAllowed()) { - return null; - } - - if (priority == null) - return null; + if (priority == null) + throw new RuntimeException("No player has priority!"); PlayerController pc = priority.getController(); + + // If the Phase we're in doesn't allow for Priority, move to next phase + if (!handler.isPlayerPriorityAllowed()) { + return pc.getAutoPassPriorityInput(); + } + // Special Inputs needed for the following phases: + final MagicStack stack = game.getStack(); switch (phase) { case COMBAT_DECLARE_ATTACKERS: stack.freezeStack(); if (playerTurn.isHuman() && !playerTurn.getController().mayAutoPass(phase)) { game.getCombat().initiatePossibleDefenders(playerTurn.getOpponents()); - return new InputAttack(game); + return new InputAttack(playerTurn); } break; @@ -158,8 +170,7 @@ public class InputControl extends MyObservable implements java.io.Serializable { } // noone attacks you - pc.passPriority(); - return null; + return pc.getAutoPassPriorityInput(); case CLEANUP: // discard @@ -176,19 +187,16 @@ public class InputControl extends MyObservable implements java.io.Serializable { // Special phases handled above, everything else is handled simply by // priority - boolean prioritySkip = pc.mayAutoPass(phase) || pc.isUiSetToSkipPhase(playerTurn, phase); - if (game.getStack().isEmpty() && prioritySkip) { - pc.passPriority(); - return null; + boolean maySkipPriority = pc.mayAutoPass(phase) || pc.isUiSetToSkipPhase(playerTurn, phase); + if (game.getStack().isEmpty() && maySkipPriority) { + return pc.getAutoPassPriorityInput(); } else pc.autoPassCancel(); // probably cancel, since something has happened return pc.getDefaultInput(); } // getInput() - /** - * TODO: Write javadoc for this method. - */ + private final static InputLockUI inputLock = new InputLockUI(); public void lock() { setInput(inputLock); diff --git a/src/main/java/forge/control/input/InputLockUI.java b/src/main/java/forge/control/input/InputLockUI.java index 2fb4c2eba7e..5beace5ed26 100644 --- a/src/main/java/forge/control/input/InputLockUI.java +++ b/src/main/java/forge/control/input/InputLockUI.java @@ -2,16 +2,18 @@ package forge.control.input; import java.util.concurrent.atomic.AtomicInteger; +import forge.Card; import forge.FThreads; +import forge.Singletons; +import forge.game.player.Player; +import forge.gui.match.CMatchUI; import forge.view.ButtonUtil; /** * TODO: Write javadoc for this type. * */ -public class InputLockUI extends InputBase { - private static final long serialVersionUID = 5777143577098597374L; - +public class InputLockUI implements Input { private final AtomicInteger iCall = new AtomicInteger(); public void showMessage() { @@ -47,5 +49,18 @@ public class InputLockUI extends InputBase { showMessage("Waiting for actions..."); } }; + + protected final boolean isActive() { + return Singletons.getModel().getMatch().getInput().getInput() == this; + } + + protected void showMessage(String message) { + CMatchUI.SINGLETON_INSTANCE.showMessage(message); + } + + @Override public void selectCard(Card c) {} + @Override public void selectPlayer(Player player) {} + @Override public void selectButtonOK() {} + @Override public void selectButtonCancel() {} } diff --git a/src/main/java/forge/control/input/InputMulligan.java b/src/main/java/forge/control/input/InputMulligan.java index ed7b64ffab9..f6283df6c3e 100644 --- a/src/main/java/forge/control/input/InputMulligan.java +++ b/src/main/java/forge/control/input/InputMulligan.java @@ -20,16 +20,11 @@ package forge.control.input; import java.util.ArrayList; import java.util.List; -import com.google.common.collect.Iterables; - import forge.Card; -import forge.CardPredicates; -import forge.Singletons; -import forge.card.ability.AbilityFactory; -import forge.card.spellability.SpellAbility; -import forge.game.GameAction; +import forge.FThreads; import forge.game.GameState; import forge.game.GameType; +import forge.game.MatchController; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; import forge.game.player.Player; @@ -40,6 +35,7 @@ import forge.gui.framework.SDisplayUtil; import forge.gui.match.CMatchUI; import forge.gui.match.nonsingleton.VField; import forge.gui.match.views.VMessage; +import forge.util.Lang; import forge.view.ButtonUtil; /** *

@@ -52,26 +48,33 @@ import forge.view.ButtonUtil; public class InputMulligan extends InputBase { /** Constant serialVersionUID=-8112954303001155622L. */ private static final long serialVersionUID = -8112954303001155622L; - + + private final MatchController match; + + public InputMulligan(MatchController match0, Player humanPlayer) { + super(humanPlayer); + match = match0; + } + /** {@inheritDoc} */ @Override public final void showMessage() { - ButtonUtil.setButtonText("No", "Yes"); + ButtonUtil.setButtonText("Keep", "Mulligan"); ButtonUtil.enableAllFocusOk(); - GameState game = Singletons.getModel().getGame(); + GameState game = match.getCurrentGame(); Player startingPlayer = game.getPhaseHandler().getPlayerTurn(); - Player localPlayer = Singletons.getControl().getPlayer(); StringBuilder sb = new StringBuilder(); - sb.append(startingPlayer.getName()).append(" is going first. "); - - if (!startingPlayer.equals(localPlayer)) { - sb.append("You are going ").append(game.getOrdinalPosition(localPlayer, startingPlayer)).append(". "); + if( startingPlayer == player ) { + sb.append("You are going first.\n"); + } else { + sb.append(startingPlayer.getName()).append(" is going first. "); + sb.append("You are going ").append(Lang.getOrdinal(game.getPosition(player, startingPlayer))).append(".\n"); } - sb.append("Do you want to Mulligan?"); - CMatchUI.SINGLETON_INSTANCE.showMessage(sb.toString()); + sb.append("Do you want to keep your hand?"); + showMessage(sb.toString()); } /** {@inheritDoc} */ @@ -83,10 +86,9 @@ public class InputMulligan extends InputBase { /** {@inheritDoc} */ @Override public final void selectButtonCancel() { - final Player humanPlayer = Singletons.getControl().getPlayer(); - humanPlayer.doMulligan(); + player.doMulligan(); - if (humanPlayer.getCardsIn(ZoneType.Hand).isEmpty()) { + if (player.getCardsIn(ZoneType.Hand).isEmpty()) { this.end(); } else { ButtonUtil.enableAllFocusOk(); @@ -94,9 +96,9 @@ public class InputMulligan extends InputBase { } final void end() { - GameState game = Singletons.getModel().getGame(); // Computer mulligan + final GameState game = match.getCurrentGame(); for (Player p : game.getPlayers()) { if (!(p instanceof AIPlayer)) { continue; @@ -107,83 +109,33 @@ public class InputMulligan extends InputBase { } } - // Human Leylines & Chancellors + ButtonUtil.reset(); - - final GameAction ga = game.getAction(); - for (Player p : game.getPlayers()) { - final List openingHand = new ArrayList(p.getCardsIn(ZoneType.Hand)); - - for (final Card c : openingHand) { - if (p.isHuman()) { - for (String kw : c.getKeyword()) { - if (kw.startsWith("MayEffectFromOpeningHand")) { - final String effName = kw.split(":")[1]; - - final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); - if (GuiDialog.confirm(c, "Use " + c +"'s ability?")) { - // If we ever let the AI memorize cards in the players - // hand, this would be a place to do so. - game.getActionPlay().playSpellAbilityNoStack(p, effect, false); - } - } - } - if (c.getName().startsWith("Leyline of")) { - if (GuiDialog.confirm(c, "Use " + c + "'s ability?")) { - ga.moveToPlay(c); - } - } - } else { // Computer Leylines & Chancellors - if (!c.getName().startsWith("Leyline of")) { - for (String kw : c.getKeyword()) { - if (kw.startsWith("MayEffectFromOpeningHand")) { - final String effName = kw.split(":")[1]; - - final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); - - // Is there a better way for the AI to decide this? - if (effect.doTrigger(false, (AIPlayer)p)) { - GuiDialog.message("Computer reveals " + c.getName() + "(" + c.getUniqueNumber() + ")."); - ComputerUtil.playNoStack((AIPlayer)p, effect, game); - } - } - } - } - if (c.getName().startsWith("Leyline of") - && !(c.getName().startsWith("Leyline of Singularity") - && (Iterables.any(game.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Leyline of Singularity"))))) { - ga.moveToPlay(c); - //ga.checkStateEffects(); - } - } - } - } - - ga.checkStateEffects(); - Player next = game.getPhaseHandler().getPlayerTurn(); - if(game.getType() == GameType.Planechase) { next.initPlane(); } - //Set Field shown to current player. VField nextField = CMatchUI.SINGLETON_INSTANCE.getFieldViewFor(next); SDisplayUtil.showTab(nextField); - - game.setMulliganned(true); - Singletons.getModel().getMatch().getInput().clearInput(); + + FThreads.invokeInNewThread( new Runnable() { + @Override + public void run() { + match.afterMulligans(); + } + }); } @Override public void selectCard(Card c0) { - Zone z0 = Singletons.getModel().getGame().getZoneOf(c0); + Zone z0 = match.getCurrentGame().getZoneOf(c0); if (c0.getName().equals("Serum Powder") && z0.is(ZoneType.Hand)) { if (GuiDialog.confirm(c0, "Use " + c0.getName() + "'s ability?")) { List hand = new ArrayList(c0.getController().getCardsIn(ZoneType.Hand)); for (Card c : hand) { - Singletons.getModel().getGame().getAction().exile(c); + match.getCurrentGame().getAction().exile(c); } c0.getController().drawCards(hand.size()); } diff --git a/src/main/java/forge/control/input/InputPartialParisMulligan.java b/src/main/java/forge/control/input/InputPartialParisMulligan.java new file mode 100644 index 00000000000..637fd292987 --- /dev/null +++ b/src/main/java/forge/control/input/InputPartialParisMulligan.java @@ -0,0 +1,181 @@ +/* + * 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.control.input; + +import java.util.ArrayList; +import java.util.List; + +import forge.Card; +import forge.FThreads; +import forge.game.GameState; +import forge.game.GameType; +import forge.game.MatchController; +import forge.game.player.Player; +import forge.game.zone.Zone; +import forge.game.zone.ZoneType; +import forge.gui.GuiDialog; +import forge.gui.framework.SDisplayUtil; +import forge.gui.match.CMatchUI; +import forge.gui.match.nonsingleton.VField; +import forge.util.Lang; +import forge.view.ButtonUtil; + /** + *

+ * InputMulligan class. + *

+ * + * @author Forge + * @version $Id: InputMulligan.java 20698 2013-04-01 09:56:12Z Max mtg $ + */ +public class InputPartialParisMulligan extends InputBase { + /** Constant serialVersionUID=-8112954303001155622L. */ + private static final long serialVersionUID = -8112954303001155622L; + + private final MatchController match; + + private final List lastExiled = new ArrayList(); + private final List allExiled = new ArrayList(); + + public InputPartialParisMulligan(MatchController match0, Player humanPlayer) { + super(humanPlayer); + match = match0; + } + + /** {@inheritDoc} */ + @Override + public final void showMessage() { + ButtonUtil.setButtonText("Done", "Exile"); + ButtonUtil.enableOnlyOk(); + + GameState game = match.getCurrentGame(); + Player startingPlayer = game.getPhaseHandler().getPlayerTurn(); + + StringBuilder sb = new StringBuilder(); + if( startingPlayer == player ) { + sb.append("You are going first.\n"); + } else { + sb.append(startingPlayer.getName()).append(" is going first. "); + sb.append("You are going ").append(Lang.getOrdinal(game.getPosition(player, startingPlayer))).append(".\n"); + } + + sb.append("Do you want to Mulligan?"); + showMessage(sb.toString()); + } + + /** {@inheritDoc} */ + @Override + public final void selectButtonOK() { + this.end(); + } + + /** {@inheritDoc} */ + @Override + public final void selectButtonCancel() { + for(Card c : lastExiled) + { + match.getCurrentGame().action.exile(c); + } + + player.drawCards(lastExiled.size()-1); + allExiled.addAll(lastExiled); + lastExiled.clear(); + + if (player.getCardsIn(ZoneType.Hand).isEmpty()) { + this.end(); + } else { + ButtonUtil.enableAllFocusOk(); + } + } + + final void end() { + + final GameState game = match.getCurrentGame(); + + for(Card c : allExiled) + { + game.action.moveToLibrary(c); + } + player.shuffle(); + + // Computer mulligan + //TODO: How should AI approach Partial Paris? + /* + for (Player p : game.getPlayers()) { + if (!(p instanceof AIPlayer)) { + continue; + } + AIPlayer ai = (AIPlayer) p; + while (ComputerUtil.wantMulligan(ai)) { + ai.doMulligan(); + } + }*/ + + // Human Leylines & Chancellors + ButtonUtil.reset(); + Player next = game.getPhaseHandler().getPlayerTurn(); + if(game.getType() == GameType.Planechase) + { + next.initPlane(); + } + //Set Field shown to current player. + VField nextField = CMatchUI.SINGLETON_INSTANCE.getFieldViewFor(next); + SDisplayUtil.showTab(nextField); + + FThreads.invokeInNewThread( new Runnable() { + @Override + public void run() { + match.afterMulligans(); + } + }); + } + + @Override + public void selectCard(Card c0) { + if(lastExiled.contains(c0)) + { + lastExiled.remove(c0); + c0.setUsedToPay(false); + } + else + { + Zone z0 = match.getCurrentGame().getZoneOf(c0); + if (c0.getName().equals("Serum Powder") && z0.is(ZoneType.Hand)) { + if (GuiDialog.confirm(c0, "Use " + c0.getName() + "'s ability?")) { + List hand = new ArrayList(c0.getController().getCardsIn(ZoneType.Hand)); + for (Card c : hand) { + match.getCurrentGame().getAction().exile(c); + } + c0.getController().drawCards(hand.size()); + } + else + { + lastExiled.add(c0); + c0.setUsedToPay(true); + } + } else { + lastExiled.add(c0); + c0.setUsedToPay(true); + } + } + + if(lastExiled.size() > 0) + ButtonUtil.enableAllFocusOk(); + else + ButtonUtil.enableOnlyOk(); + } +} diff --git a/src/main/java/forge/control/input/InputPassPriority.java b/src/main/java/forge/control/input/InputPassPriority.java index 4fe8b4bcf64..cb70b4fd519 100644 --- a/src/main/java/forge/control/input/InputPassPriority.java +++ b/src/main/java/forge/control/input/InputPassPriority.java @@ -19,11 +19,9 @@ package forge.control.input; import forge.Card; import forge.FThreads; -import forge.Singletons; import forge.card.spellability.SpellAbility; -import forge.control.FControl; -import forge.game.phase.PhaseType; -import forge.game.player.Player; +import forge.game.phase.PhaseHandler; +import forge.game.player.HumanPlayer; import forge.gui.GuiDisplayUtil; import forge.gui.framework.SDisplayUtil; import forge.gui.match.CMatchUI; @@ -42,26 +40,28 @@ public class InputPassPriority extends InputBase { /** Constant serialVersionUID=-581477682214137181L. */ private static final long serialVersionUID = -581477682214137181L; + /** + * TODO: Write javadoc for Constructor. + * @param player + */ + public InputPassPriority(HumanPlayer human) { + super(human); + } + /** {@inheritDoc} */ @Override public final void showMessage() { GuiDisplayUtil.updateGUI(); ButtonUtil.enableOnlyOk(); - final PhaseType phase = Singletons.getModel().getGame().getPhaseHandler().getPhase(); - final Player player = Singletons.getModel().getGame().getPhaseHandler().getPriorityPlayer(); - - if (player.isComputer()) { - System.err.println(phase + ": Computer in passpriority"); - } - + final PhaseHandler ph = player.getGame().getPhaseHandler(); final StringBuilder sb = new StringBuilder(); - sb.append("Turn : ").append(Singletons.getModel().getGame().getPhaseHandler().getPlayerTurn()).append("\n"); - sb.append("Phase: ").append(phase.Name).append("\n"); + sb.append("Turn : ").append(ph.getPlayerTurn()).append("\n"); + sb.append("Phase: ").append(ph.getPhase().Name).append("\n"); sb.append("Stack: "); - if (Singletons.getModel().getGame().getStack().size() != 0) { - sb.append(Singletons.getModel().getGame().getStack().size()).append(" to Resolve."); + if (player.getGame().getStack().size() != 0) { + sb.append(player.getGame().getStack().size()).append(" to Resolve."); } else { sb.append("Empty"); } @@ -71,22 +71,22 @@ public class InputPassPriority extends InputBase { CMatchUI.SINGLETON_INSTANCE.showMessage(sb.toString()); } + /** {@inheritDoc} */ @Override public final void selectButtonOK() { - FControl.SINGLETON_INSTANCE.getPlayer().getController().passPriority(); + passPriority(); } /** {@inheritDoc} */ @Override public final void selectCard(final Card card) { - final Player player = Singletons.getControl().getPlayer(); final SpellAbility ab = player.getController().getAbilityToPlay(player.getGame().getAbilitesOfCard(card, player)); if ( null != ab) { Runnable execAbility = new Runnable() { @Override public void run() { - player.playSpellAbility(card, ab); + ((HumanPlayer)player).playSpellAbility(card, ab); } }; diff --git a/src/main/java/forge/control/input/InputPayManaBase.java b/src/main/java/forge/control/input/InputPayManaBase.java index 29d43729c83..f70fe3a52b8 100644 --- a/src/main/java/forge/control/input/InputPayManaBase.java +++ b/src/main/java/forge/control/input/InputPayManaBase.java @@ -7,7 +7,6 @@ import forge.Card; import forge.CardUtil; import forge.Constant; import forge.FThreads; -import forge.Singletons; import forge.card.MagicColor; import forge.card.ability.ApiType; import forge.card.mana.ManaCostBeingPaid; @@ -15,7 +14,7 @@ import forge.card.mana.ManaPool; import forge.card.spellability.AbilityManaPart; import forge.card.spellability.SpellAbility; import forge.game.GameState; -import forge.game.player.Player; +import forge.game.player.HumanPlayer; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; import forge.gui.framework.SDisplayUtil; @@ -31,16 +30,15 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I protected int phyLifeToLose = 0; - protected final Player whoPays; protected final GameState game; protected ManaCostBeingPaid manaCost; protected final SpellAbility saPaidFor; boolean bPaid = false; - protected InputPayManaBase(final GameState game, SpellAbility saToPayFor) { - this.game = game; - this.whoPays = saToPayFor.getActivatingPlayer(); + protected InputPayManaBase(SpellAbility saToPayFor) { + super(saToPayFor.getActivatingPlayer()); + this.game = player.getGame(); this.saPaidFor = saToPayFor; } @@ -116,7 +114,7 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I * @return ManaCost the amount of mana remaining to be paid after the mana is activated */ protected void useManaFromPool(String color, ManaCostBeingPaid manaCost) { - ManaPool mp = Singletons.getControl().getPlayer().getManaPool(); + ManaPool mp = player.getManaPool(); // Convert Color to short String String manaStr = "1"; @@ -145,7 +143,7 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I */ protected void activateManaAbility(final Card card, ManaCostBeingPaid manaCost) { // make sure computer's lands aren't selected - if (card.getController() != whoPays) { + if (card.getController() != player) { return; } @@ -169,7 +167,7 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I // you can't remove unneeded abilities inside a for(am:abilities) loop :( for (SpellAbility ma : card.getManaAbility()) { - ma.setActivatingPlayer(whoPays); + ma.setActivatingPlayer(player); AbilityManaPart m = null; SpellAbility tail = ma; while(m == null && tail != null) @@ -293,8 +291,7 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I Runnable proc = new Runnable() { @Override public void run() { - final Player p = chosen.getActivatingPlayer(); - p.getGame().getActionPlay().playSpellAbility(chosen, p); + ((HumanPlayer)chosen.getActivatingPlayer()).playSpellAbility(chosen); onManaAbilityPlayed(chosen); } }; @@ -304,11 +301,11 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I public void onManaAbilityPlayed(final SpellAbility saPaymentSrc) { if ( saPaymentSrc != null) // null comes when they've paid from pool - this.manaCost = whoPays.getManaPool().payManaFromAbility(saPaidFor, manaCost, saPaymentSrc); + this.manaCost = player.getManaPool().payManaFromAbility(saPaidFor, manaCost, saPaymentSrc); onManaAbilityPaid(); if ( saPaymentSrc != null ) - whoPays.getZone(ZoneType.Battlefield).updateObservers(); + player.getZone(ZoneType.Battlefield).updateObservers(); } protected final void checkIfAlredyPaid() { @@ -323,7 +320,7 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I @Override public String toString() { - return String.format("PayManaBase %s (out of %s)", manaCost.toString(), manaCost.getStartingCost() ); + return String.format("PayManaBase %s left", manaCost.toString() ); } protected void handleConvokedCards(boolean isCancelled) { diff --git a/src/main/java/forge/control/input/InputPayManaExecuteCommands.java b/src/main/java/forge/control/input/InputPayManaExecuteCommands.java index 0214bc8177a..4f51d8133fe 100644 --- a/src/main/java/forge/control/input/InputPayManaExecuteCommands.java +++ b/src/main/java/forge/control/input/InputPayManaExecuteCommands.java @@ -34,7 +34,7 @@ import forge.view.ButtonUtil; * @author Forge * @version $Id$ */ -public class InputPayManaExecuteCommands extends InputPayManaBase implements InputPayment { +public class InputPayManaExecuteCommands extends InputPayManaBase { /** * Constant serialVersionUID=3836655722696348713L. */ @@ -62,7 +62,7 @@ public class InputPayManaExecuteCommands extends InputPayManaBase implements Inp * a {@link forge.Command} object. */ public InputPayManaExecuteCommands(final Player p, final String prompt, final ManaCost manaCost2) { - super(p.getGame(), new SpellAbility(null) { + super(new SpellAbility(null) { @Override public void resolve() {} @@ -80,18 +80,10 @@ public class InputPayManaExecuteCommands extends InputPayManaBase implements Inp } - /** - *

- * resetManaCost. - *

- */ - public final void resetManaCost() { - this.manaCost = new ManaCostBeingPaid(this.originalManaCost); - } @Override - public void selectPlayer(final Player player) { - if (player == whoPays) { + public void selectPlayer(final Player selectedPlayer) { + if (player == selectedPlayer) { if (player.canPayLife(this.phyLifeToLose + 2) && manaCost.payPhyrexian()) { this.phyLifeToLose += 2; } @@ -109,7 +101,6 @@ public class InputPayManaExecuteCommands extends InputPayManaBase implements Inp if (this.phyLifeToLose > 0) { Singletons.getControl().getPlayer().payLife(this.phyLifeToLose, null); } - this.resetManaCost(); Singletons.getControl().getPlayer().getManaPool().clearManaPaid(this.saPaidFor, false); bPaid = true; this.stop(); @@ -118,8 +109,6 @@ public class InputPayManaExecuteCommands extends InputPayManaBase implements Inp /** {@inheritDoc} */ @Override public final void selectButtonCancel() { - - this.resetManaCost(); Singletons.getControl().getPlayer().getManaPool().refundManaPaid(this.saPaidFor, true); bPaid = false; this.stop(); diff --git a/src/main/java/forge/control/input/InputPayManaOfCostPayment.java b/src/main/java/forge/control/input/InputPayManaOfCostPayment.java index b241ebf074a..b3f8cbaddc7 100644 --- a/src/main/java/forge/control/input/InputPayManaOfCostPayment.java +++ b/src/main/java/forge/control/input/InputPayManaOfCostPayment.java @@ -2,7 +2,6 @@ package forge.control.input; import forge.Card; import forge.Singletons; -import forge.card.cost.CostPartMana; import forge.card.mana.ManaCostBeingPaid; import forge.card.spellability.SpellAbility; import forge.game.GameState; @@ -11,18 +10,17 @@ import forge.view.ButtonUtil; public class InputPayManaOfCostPayment extends InputPayManaBase { - public InputPayManaOfCostPayment(final GameState game, CostPartMana costMana, SpellAbility spellAbility, int toAdd) { - super(game, spellAbility); - manaCost = new ManaCostBeingPaid(costMana.getManaToPay()); - manaCost.increaseColorlessMana(toAdd); + public InputPayManaOfCostPayment(final GameState game, ManaCostBeingPaid cost, SpellAbility spellAbility) { + super(spellAbility); + manaCost = cost; } private static final long serialVersionUID = 3467312982164195091L; private int phyLifeToLose = 0; @Override - public void selectPlayer(final Player player) { - if (player == whoPays) { + public void selectPlayer(final Player selectedPlayer) { + if (player == selectedPlayer) { if (player.canPayLife(this.phyLifeToLose + 2) && manaCost.payPhyrexian()) { this.phyLifeToLose += 2; } diff --git a/src/main/java/forge/control/input/InputPayManaSimple.java b/src/main/java/forge/control/input/InputPayManaSimple.java index 712f0ebfe4d..3f502812d7f 100644 --- a/src/main/java/forge/control/input/InputPayManaSimple.java +++ b/src/main/java/forge/control/input/InputPayManaSimple.java @@ -37,7 +37,7 @@ public class InputPayManaSimple extends InputPayManaBase { private final String originalManaCost; public InputPayManaSimple(final GameState game, final SpellAbility sa, final ManaCostBeingPaid manaCostToPay) { - super(game, sa); + super(sa); this.originalManaCost = manaCostToPay.toString(); // Change this.originalCard = sa.getSourceCard(); @@ -68,9 +68,9 @@ public class InputPayManaSimple extends InputPayManaBase { /** {@inheritDoc} */ @Override - public final void selectPlayer(final Player player) { + public final void selectPlayer(final Player selectedPlayer) { - if (player == whoPays) { + if (player == selectedPlayer) { if (player.canPayLife(this.phyLifeToLose + 2) && manaCost.payPhyrexian()) { this.phyLifeToLose += 2; } @@ -88,10 +88,10 @@ public class InputPayManaSimple extends InputPayManaBase { @Override protected void done() { if (this.phyLifeToLose > 0) { - whoPays.payLife(this.phyLifeToLose, this.originalCard); + player.payLife(this.phyLifeToLose, this.originalCard); } if (!this.saPaidFor.getSourceCard().isCopiedSpell()) { - whoPays.getManaPool().clearManaPaid(this.saPaidFor, false); + player.getManaPool().clearManaPaid(this.saPaidFor, false); this.resetManaCost(); if (this.saPaidFor.isSpell()) { @@ -110,8 +110,8 @@ public class InputPayManaSimple extends InputPayManaBase { handleConvokedCards(true); this.resetManaCost(); - whoPays.getManaPool().refundManaPaid(this.saPaidFor, true); - whoPays.getZone(ZoneType.Battlefield).updateObservers(); // DO + player.getManaPool().refundManaPaid(this.saPaidFor, true); + player.getZone(ZoneType.Battlefield).updateObservers(); // DO this.stop(); } diff --git a/src/main/java/forge/control/input/InputPayManaX.java b/src/main/java/forge/control/input/InputPayManaX.java index 75d1a3bc544..fc77d6fd52b 100644 --- a/src/main/java/forge/control/input/InputPayManaX.java +++ b/src/main/java/forge/control/input/InputPayManaX.java @@ -3,7 +3,6 @@ package forge.control.input; import forge.Card; import forge.card.mana.ManaCostBeingPaid; import forge.card.spellability.SpellAbility; -import forge.game.GameState; import forge.view.ButtonUtil; public class InputPayManaX extends InputPayManaBase { @@ -15,9 +14,9 @@ public class InputPayManaX extends InputPayManaBase { private final boolean xCanBe0; - public InputPayManaX(final GameState game, final SpellAbility sa0, final int amountX, final boolean xCanBe0) + public InputPayManaX(final SpellAbility sa0, final int amountX, final boolean xCanBe0) { - super(game, sa0); + super(sa0); xPaid = 0; colorX = saPaidFor.hasParam("XColor") ? saPaidFor.getParam("XColor") : ""; diff --git a/src/main/java/forge/control/input/InputSelectManyBase.java b/src/main/java/forge/control/input/InputSelectManyBase.java index 84c58f000c7..06e04d39727 100644 --- a/src/main/java/forge/control/input/InputSelectManyBase.java +++ b/src/main/java/forge/control/input/InputSelectManyBase.java @@ -5,6 +5,7 @@ import java.util.List; import forge.Card; import forge.GameEntity; +import forge.Singletons; import forge.view.ButtonUtil; public abstract class InputSelectManyBase extends InputSyncronizedBase implements InputSelectMany { @@ -23,6 +24,7 @@ public abstract class InputSelectManyBase extends InputSyn protected InputSelectManyBase(int min, int max) { + super(Singletons.getControl().getPlayer()); selected = new ArrayList(); if (min > max) { throw new IllegalArgumentException("Min must not be greater than Max"); diff --git a/src/main/java/forge/control/input/InputSelectTargets.java b/src/main/java/forge/control/input/InputSelectTargets.java new file mode 100644 index 00000000000..629c1cc95cd --- /dev/null +++ b/src/main/java/forge/control/input/InputSelectTargets.java @@ -0,0 +1,232 @@ +package forge.control.input; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import forge.Card; +import forge.GameEntity; +import forge.card.ability.ApiType; +import forge.card.spellability.SpellAbility; +import forge.card.spellability.Target; +import forge.game.player.Player; +import forge.gui.GuiChoose; +import forge.view.ButtonUtil; + +/** + * TODO: Write javadoc for this type. + * + */ +public final class InputSelectTargets extends InputSyncronizedBase { + private final List choices; + // some cards can be targeted several times (eg: distribute damage as you choose) + private final Map targetDepth = new HashMap(); + private final Target tgt; + private final SpellAbility sa; + private boolean bCancel = false; + private boolean bOk = false; + private final boolean mandatory; + private static final long serialVersionUID = -1091595663541356356L; + + public final boolean hasCancelled() { return bCancel; } + public final boolean hasPressedOk() { return bOk; } + /** + * TODO: Write javadoc for Constructor. + * @param select + * @param choices + * @param req + * @param alreadyTargeted + * @param targeted + * @param tgt + * @param sa + * @param mandatory + */ + public InputSelectTargets(List choices, SpellAbility sa, boolean mandatory) { + super(sa.getActivatingPlayer()); + this.choices = choices; + this.tgt = sa.getTarget(); + this.sa = sa; + this.mandatory = mandatory; + } + + @Override + public void showMessage() { + final StringBuilder sb = new StringBuilder(); + sb.append("Targeted:\n"); + for (final Entry o : targetDepth.entrySet()) { + sb.append(o.getKey()); + if( o.getValue() > 1 ) + sb.append(" (").append(o.getValue()).append(" times)"); + sb.append("\n"); + } + //sb.append(tgt.getTargetedString()).append("\n"); + sb.append(tgt.getVTSelection()); + + int maxTargets = tgt.getMaxTargets(sa.getSourceCard(), sa); + int targeted = tgt.getNumTargeted(); + if(maxTargets > 1) + sb.append("\n(").append(maxTargets - targeted).append(" more can be targeted)"); + + showMessage(sb.toString()); + + // If reached Minimum targets, enable OK button + if (!tgt.isMinTargetsChosen(sa.getSourceCard(), sa) || tgt.isDividedAsYouChoose()) { + if (mandatory && tgt.hasCandidates(sa, true)) { + // Player has to click on a target + ButtonUtil.disableAll(); + } else { + ButtonUtil.enableOnlyCancel(); + } + } else { + if (mandatory && tgt.hasCandidates(sa, true)) { + // Player has to click on a target or ok + ButtonUtil.enableOnlyOk(); + } else { + ButtonUtil.enableAllFocusOk(); + } + } + } + + @Override + public void selectButtonCancel() { + bCancel = true; + this.done(); + } + + @Override + public void selectButtonOK() { + bOk = true; + this.done(); + } + + @Override + public void selectCard(final Card card) { + if (!tgt.isUniqueTargets() && targetDepth.containsKey(card)) { + return; + } + + // leave this in temporarily, there some seriously wrong things going on here + if (!card.canBeTargetedBy(sa)) { + showMessage("Cannot target this card (Shroud? Protection? Restrictions?)."); + return; + } + if (!choices.contains(card)) { + showMessage("This card is not a valid choice for some other reason besides (Shroud? Protection? Restrictions?)."); + return; + } + + if (tgt.isDividedAsYouChoose()) { + final int stillToDivide = tgt.getStillToDivide(); + int allocatedPortion = 0; + // allow allocation only if the max targets isn't reached and there are more candidates + if ((tgt.getNumTargeted() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa)) + && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { + final Integer[] choices = new Integer[stillToDivide]; + for (int i = 1; i <= stillToDivide; i++) { + choices[i - 1] = i; + } + String apiBasedMessage = "Distribute how much to "; + if (sa.getApi() == ApiType.DealDamage) { + apiBasedMessage = "Select how much damage to deal to "; + } else if (sa.getApi() == ApiType.PreventDamage) { + apiBasedMessage = "Select how much damage to prevent to "; + } else if (sa.getApi() == ApiType.PutCounter) { + apiBasedMessage = "Select how many counters to distribute to "; + } + final StringBuilder sb = new StringBuilder(); + sb.append(apiBasedMessage); + sb.append(card.toString()); + Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices); + if (null == chosen) { + return; + } + allocatedPortion = chosen; + } else { // otherwise assign the rest of the damage/protection + allocatedPortion = stillToDivide; + } + tgt.setStillToDivide(stillToDivide - allocatedPortion); + tgt.addDividedAllocation(card, allocatedPortion); + } + addTarget(card); + } // selectCard() + + @Override + public void selectPlayer(final Player player) { + if (!tgt.isUniqueTargets() && targetDepth.containsKey(player)) { + return; + } + + if (!sa.canTarget(player)) { + showMessage("Cannot target this player (Hexproof? Protection? Restrictions?)."); + return; + } + + if (tgt.isDividedAsYouChoose()) { + final int stillToDivide = tgt.getStillToDivide(); + int allocatedPortion = 0; + // allow allocation only if the max targets isn't reached and there are more candidates + if ((tgt.getNumTargeted() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa)) && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { + final Integer[] choices = new Integer[stillToDivide]; + for (int i = 1; i <= stillToDivide; i++) { + choices[i - 1] = i; + } + String apiBasedMessage = "Distribute how much to "; + if (sa.getApi() == ApiType.DealDamage) { + apiBasedMessage = "Select how much damage to deal to "; + } else if (sa.getApi() == ApiType.PreventDamage) { + apiBasedMessage = "Select how much damage to prevent to "; + } + final StringBuilder sb = new StringBuilder(); + sb.append(apiBasedMessage); + sb.append(player.getName()); + Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices); + if (null == chosen) { + return; + } + allocatedPortion = chosen; + } else { // otherwise assign the rest of the damage/protection + allocatedPortion = stillToDivide; + } + tgt.setStillToDivide(stillToDivide - allocatedPortion); + tgt.addDividedAllocation(player, allocatedPortion); + } + addTarget(player); + } + + private void addTarget(GameEntity ge) { + tgt.addTarget(ge); + if(ge instanceof Card) { + ((Card) ge).setUsedToPay(true); + } + Integer val = targetDepth.get(ge); + targetDepth.put(ge, val == null ? Integer.valueOf(1) : Integer.valueOf(val.intValue() + 1) ); + + if(hasAllTargets()) { + bOk = true; + this.done(); + } + else + this.showMessage(); + } + + /* (non-Javadoc) + * @see forge.control.input.InputSyncronizedBase#afterStop() + */ + @Override + protected void afterStop() { + for(GameEntity c : targetDepth.keySet()) + if( c instanceof Card) + ((Card)c).setUsedToPay(false); + super.afterStop(); + + } + + private void done() { + this.stop(); + } + + private boolean hasAllTargets() { + return tgt.isMaxTargetsChosen(sa.getSourceCard(), sa) || ( tgt.getStillToDivide() == 0 && tgt.isDividedAsYouChoose()); + } +} \ No newline at end of file diff --git a/src/main/java/forge/control/input/InputSyncronizedBase.java b/src/main/java/forge/control/input/InputSyncronizedBase.java index 6b975f19ec7..7d3f1928c51 100644 --- a/src/main/java/forge/control/input/InputSyncronizedBase.java +++ b/src/main/java/forge/control/input/InputSyncronizedBase.java @@ -4,13 +4,15 @@ import java.util.concurrent.CountDownLatch; import forge.FThreads; import forge.error.BugReporter; +import forge.game.player.Player; public abstract class InputSyncronizedBase extends InputBase implements InputSynchronized { private static final long serialVersionUID = 8756177361251703052L; private final CountDownLatch cdlDone; - public InputSyncronizedBase() { + public InputSyncronizedBase(Player player) { + super(player); cdlDone = new CountDownLatch(1); } diff --git a/src/main/java/forge/deck/CardCollections.java b/src/main/java/forge/deck/CardCollections.java index ae30b624c1b..e456b8f6831 100644 --- a/src/main/java/forge/deck/CardCollections.java +++ b/src/main/java/forge/deck/CardCollections.java @@ -40,6 +40,7 @@ public class CardCollections { private final IStorage cube; private final IStorage scheme; private final IStorage plane; + private final IStorage commander; /** * TODO: Write javadoc for Constructor. @@ -56,9 +57,10 @@ public class CardCollections { this.cube = new StorageImmediatelySerialized(new DeckSerializer(new File(NewConstants.DECK_CUBE_DIR))); this.scheme = new StorageImmediatelySerialized(new DeckSerializer(new File(NewConstants.DECK_SCHEME_DIR))); this.plane = new StorageImmediatelySerialized(new DeckSerializer(new File(NewConstants.DECK_PLANE_DIR))); - + this.commander = new StorageImmediatelySerialized(new DeckSerializer(new File(NewConstants.DECK_COMMANDER_DIR))); + sw.stop(); - System.out.printf("Read decks (%d ms): %d constructed, %d sealed, %d draft, %d cubes, %d scheme, %d planar.%n", sw.getTime(), constructed.size(), sealed.size(), draft.size(), cube.size(), scheme.size(), plane.size()); + System.out.printf("Read decks (%d ms): %d constructed, %d sealed, %d draft, %d cubes, %d scheme, %d planar, %d commander.%n", sw.getTime(), constructed.size(), sealed.size(), draft.size(), cube.size(), scheme.size(), plane.size(),commander.size()); // int sum = constructed.size() + sealed.size() + draft.size() + cube.size() + scheme.size() + plane.size(); // FSkin.setProgessBarMessage(String.format("Loaded %d decks in %f sec", sum, sw.getTime() / 1000f )); // remove this after most people have been switched to new layout @@ -116,5 +118,12 @@ public class CardCollections { public IStorage getPlane() { return plane; } + + /** + * @return the plane + */ + public IStorage getCommander() { + return commander; + } } diff --git a/src/main/java/forge/deck/DeckFormat.java b/src/main/java/forge/deck/DeckFormat.java index 4e4ee50cc4a..f0514d80c90 100644 --- a/src/main/java/forge/deck/DeckFormat.java +++ b/src/main/java/forge/deck/DeckFormat.java @@ -17,6 +17,7 @@ */ package forge.deck; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map.Entry; @@ -25,6 +26,7 @@ import org.apache.commons.lang.math.IntRange; import forge.Singletons; import forge.card.CardCoreType; +import forge.card.ColorSet; import forge.item.CardDb; import forge.item.CardPrinted; import forge.item.IPaperCard; @@ -137,18 +139,45 @@ public enum DeckFormat { if (null == cmd || cmd.isEmpty()) { return "is missing a commander"; } - if (!cmd.get(0).getRules().getType().isLegendary()) { + if (!cmd.get(0).getRules().getType().isLegendary() + || !cmd.get(0).getRules().getType().isCreature()) { return "has a commander that is not a legendary creature"; } - //TODO:Enforce color identity + ColorSet cmdCI = cmd.get(0).getRules().getColorIdentity(); + List erroneousCI = new ArrayList(); + + for(Entry cp : deck.get(DeckSection.Main)) { + if(!cp.getKey().getRules().getColorIdentity().hasNoColorsExcept(cmdCI.getColor())) + { + erroneousCI.add(cp.getKey()); + } + } + for(Entry cp : deck.get(DeckSection.Sideboard)) { + if(!cp.getKey().getRules().getColorIdentity().hasNoColorsExcept(cmdCI.getColor())) + { + erroneousCI.add(cp.getKey()); + } + } + + if(erroneousCI.size() > 0) + { + StringBuilder sb = new StringBuilder("contains card that do not match the commanders color identity:"); + + for(CardPrinted cp : erroneousCI) + { + sb.append("\n").append(cp.getName()); + } + + return sb.toString(); + } break; case Planechase: //Must contain at least 10 planes/phenomenons, but max 2 phenomenons. Singleton. final CardPool planes = deck.get(DeckSection.Planes); if (planes == null || planes.countAll() < 10) { - return "should gave at least 10 planes"; + return "should have at least 10 planes"; } int phenoms = 0; for (Entry cp : planes) { diff --git a/src/main/java/forge/error/BugReporter.java b/src/main/java/forge/error/BugReporter.java index 5b8048fea65..3b01511196f 100644 --- a/src/main/java/forge/error/BugReporter.java +++ b/src/main/java/forge/error/BugReporter.java @@ -50,6 +50,7 @@ import net.miginfocom.swing.MigLayout; import org.apache.commons.lang3.StringUtils; +import forge.FThreads; import forge.gui.WrapLayout; import forge.gui.toolbox.FHyperlink; import forge.gui.toolbox.FLabel; @@ -76,7 +77,7 @@ public class BugReporter { return; } if (message != null) { - System.err.println(message); + System.err.printf("%s > %s%n", FThreads.debugGetCurrThreadId(), message); } ex.printStackTrace(); @@ -85,8 +86,7 @@ public class BugReporter { _buildSpoilerHeader(sb, ex.getClass().getSimpleName()); sb.append("\n\n"); if (null != message && !message.isEmpty()) { - sb.append(message); - sb.append("\n"); + sb.append(FThreads.debugGetCurrThreadId()).append(" > ").append(message).append("\n"); } StringWriter sw = new StringWriter(); diff --git a/src/main/java/forge/game/GameAction.java b/src/main/java/forge/game/GameAction.java index 91510ac8824..797b1dfb5ac 100644 --- a/src/main/java/forge/game/GameAction.java +++ b/src/main/java/forge/game/GameAction.java @@ -38,6 +38,7 @@ import forge.CounterType; import forge.GameEntity; import forge.card.CardType; import forge.card.TriggerReplacementBase; +import forge.card.ability.AbilityFactory; import forge.card.ability.effects.AttachEffect; import forge.card.cardfactory.CardFactory; import forge.card.cost.Cost; @@ -57,6 +58,7 @@ import forge.game.event.CardDestroyedEvent; import forge.game.event.CardRegeneratedEvent; import forge.game.event.CardSacrificedEvent; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.player.PlayerType; import forge.game.zone.PlayerZone; @@ -64,6 +66,7 @@ import forge.game.zone.PlayerZoneBattlefield; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; +import forge.gui.GuiDialog; /** * Methods for common actions performed during a game. @@ -127,12 +130,7 @@ public class GameAction { return c; } - boolean suppress; - if (c.isToken()) { - suppress = false; - } else { - suppress = zoneFrom.equals(zoneTo); - } + boolean suppress = !c.isToken() && zoneFrom.equals(zoneTo); Card copied = null; Card lastKnownInfo = null; @@ -508,7 +506,7 @@ public class GameAction { return null; } }; - + abRecover.setActivatingPlayer(recoverable.getController()); final StringBuilder sb = new StringBuilder(); sb.append("Recover ").append(recoverable).append("\n"); @@ -518,7 +516,7 @@ public class GameAction { Player p = recoverable.getController(); if (p.isHuman()) { - if ( GameActionUtil.payCostDuringAbilityResolve(p, abRecover, abRecover.getPayCosts(), null, game) ) + if ( GameActionUtil.payCostDuringAbilityResolve(abRecover, abRecover.getPayCosts(), null, game) ) moveToHand(recoverable); else exile(recoverable); @@ -853,7 +851,7 @@ public class GameAction { } /** */ - private final void checkStaticAbilities() { + public final void checkStaticAbilities() { // remove old effects game.getStaticEffects().clearStaticEffects(); @@ -1020,7 +1018,7 @@ public class GameAction { checkAgain = true; } if (c.getNetDefense() <= 0 || c.getNetDefense() <= c.getDamage()) { - this.destroy(c); + this.destroy(c, null); checkAgain = true; } // Soulbond unpairing @@ -1056,7 +1054,7 @@ public class GameAction { } } - if (game.getTriggerHandler().runWaitingTriggers(true)) { + if (game.getTriggerHandler().runWaitingTriggers()) { checkAgain = true; // Place triggers on stack } @@ -1191,7 +1189,7 @@ public class GameAction { * a {@link forge.Card} object. * @return a boolean. */ - public final boolean destroy(final Card c) { + public final boolean destroy(final Card c, final SpellAbility sa) { if (!c.canBeDestroyed()) { return false; } @@ -1210,7 +1208,7 @@ public class GameAction { return false; } - return this.destroyNoRegeneration(c); + return this.destroyNoRegeneration(c, sa); } /** @@ -1222,7 +1220,8 @@ public class GameAction { * a {@link forge.Card} object. * @return a boolean. */ - public final boolean destroyNoRegeneration(final Card c) { + public final boolean destroyNoRegeneration(final Card c, final SpellAbility sa) { + Player activator = null; if (!c.canBeDestroyed()) return false; @@ -1252,7 +1251,7 @@ public class GameAction { final AbilityStatic ability = new AbilityStatic(crd, ManaCost.ZERO) { @Override public void resolve() { - GameAction.this.destroy(crd); + GameAction.this.destroy(crd, sa); card.setDamage(0); // Play the Destroy sound @@ -1268,9 +1267,17 @@ public class GameAction { return false; } } // totem armor + if (sa != null) { + activator = sa.getActivatingPlayer(); + } // Play the Destroy sound game.getEvents().post(new CardDestroyedEvent()); + // 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); } @@ -1407,6 +1414,58 @@ public class GameAction { } } + void handleLeylinesAndChancellors() { + for (Player p : game.getPlayers()) { + final List openingHand = new ArrayList(p.getCardsIn(ZoneType.Hand)); + + for (final Card c : openingHand) { + if (p.isHuman()) { + for (String kw : c.getKeyword()) { + if (kw.startsWith("MayEffectFromOpeningHand")) { + final String effName = kw.split(":")[1]; + + final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); + if (GuiDialog.confirm(c, "Use " + c +"'s ability?")) { + // If we ever let the AI memorize cards in the players + // hand, this would be a place to do so. + ((HumanPlayer)p).playSpellAbilityNoStack(effect); + } + } + } + if (c.getName().startsWith("Leyline of")) { + if (GuiDialog.confirm(c, "Use " + c + "'s ability?")) { + game.getAction().moveToPlay(c); + } + } + } else { // Computer Leylines & Chancellors + if (!c.getName().startsWith("Leyline of")) { + for (String kw : c.getKeyword()) { + if (kw.startsWith("MayEffectFromOpeningHand")) { + final String effName = kw.split(":")[1]; + + final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); + + // Is there a better way for the AI to decide this? + if (effect.doTrigger(false, (AIPlayer)p)) { + GuiDialog.message("Computer reveals " + c.getName() + "(" + c.getUniqueNumber() + ")."); + ComputerUtil.playNoStack((AIPlayer)p, effect, game); + } + } + } + } + if (c.getName().startsWith("Leyline of") + && !(c.getName().startsWith("Leyline of Singularity") + && (Iterables.any(game.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Leyline of Singularity"))))) { + game.getAction().moveToPlay(c); + //ga.checkStateEffects(); + } + } + } + } + + game.getAction().checkStateEffects(); + } + /** *

* playCardWithoutManaCost. diff --git a/src/main/java/forge/game/GameActionPlay.java b/src/main/java/forge/game/GameActionPlay.java deleted file mode 100644 index f44b02dff81..00000000000 --- a/src/main/java/forge/game/GameActionPlay.java +++ /dev/null @@ -1,511 +0,0 @@ -package forge.game; - -import java.util.ArrayList; -import java.util.List; -import com.google.common.collect.Lists; - -import forge.Card; -import forge.CardCharacteristicName; -import forge.CardColor; -import forge.CardLists; -import forge.CardPredicates; -import forge.FThreads; -import forge.card.MagicColor; -import forge.card.ability.AbilityUtils; -import forge.card.ability.ApiType; -import forge.card.ability.effects.CharmEffect; -import forge.card.cost.Cost; -import forge.card.cost.CostPayment; -import forge.card.mana.ManaCostBeingPaid; -import forge.card.mana.ManaCostShard; -import forge.card.spellability.SpellAbility; -import forge.card.spellability.SpellAbilityRequirements; -import forge.card.spellability.Target; -import forge.card.spellability.TargetSelection; -import forge.card.staticability.StaticAbility; -import forge.control.input.InputPayManaSimple; -import forge.game.ai.ComputerUtilCard; -import forge.game.player.Player; -import forge.game.zone.ZoneType; -import forge.gui.GuiChoose; - -/** - * TODO: Write javadoc for this type. - * - */ -public class GameActionPlay { - - private final GameState game; - - - public GameActionPlay(final GameState game0) { - game = game0; - } - - public final void playCardWithoutManaCost(final Card c, Player player) { - final List choices = c.getBasicSpells(); - // TODO add Buyback, Kicker, ... , spells here - - SpellAbility sa = player.getController().getAbilityToPlay(choices); - - if (sa == null) { - return; - } - - sa.setActivatingPlayer(player); - this.playSpellAbilityWithoutPayingManaCost(sa); - } - - /** - *

- * playSpellAbilityForFree. - *

- * - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - */ - public final void playSpellAbilityWithoutPayingManaCost(final SpellAbility sa) { - FThreads.checkEDT("GameActionPlay.playSpellAbilityWithoutPayingManaCost", false); - final Card source = sa.getSourceCard(); - setSplitCardState(source, sa); // Split card support - - if (sa.getPayCosts() != null) { - if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { - CharmEffect.makeChoices(sa); - } - final TargetSelection ts = new TargetSelection(sa.getTarget(), sa); - final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); - - final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, ts, payment); - req.setFree(true); - req.fillRequirements(); - } else { - if (sa.isSpell()) { - final Card c = sa.getSourceCard(); - if (!c.isCopiedSpell()) { - sa.setSourceCard(game.getAction().moveToStack(c)); - } - } - boolean x = sa.getSourceCard().getManaCost().getShardCount(ManaCostShard.X) > 0; - - game.getStack().add(sa, x); - } - } - - /** - *

- * getSpellCostChange. - *

- * - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - * @param originalCost - * a {@link forge.card.mana.ManaCostBeingPaid} object. - * @return a {@link forge.card.mana.ManaCostBeingPaid} object. - */ - public final ManaCostBeingPaid getSpellCostChange(final SpellAbility sa, final ManaCostBeingPaid originalCost) { - // Beached - final Card originalCard = sa.getSourceCard(); - final SpellAbility spell = sa; - String mana = originalCost.toString(); - ManaCostBeingPaid manaCost = new ManaCostBeingPaid(mana); - if (sa.isXCost() && !originalCard.isCopiedSpell()) { - originalCard.setXManaCostPaid(0); - } - - if (game == null || sa.isTrigger()) { - return manaCost; - } - - if (spell.isSpell()) { - if (spell.isDelve()) { - manaCost = getCostAfterDelve(originalCost, originalCard); - } else if (spell.getSourceCard().hasKeyword("Convoke")) { - ManaCostBeingPaid convokeCost = getCostAfterConvoke(sa, originalCost, spell); - if ( null != convokeCost ) - manaCost = convokeCost; - } - } // 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) { - manaCost = stAb.applyAbility("RaiseCost", spell, manaCost); - } - - // Reduce cost - for (final StaticAbility stAb : reduceAbilities) { - manaCost = stAb.applyAbility("ReduceCost", spell, manaCost); - } - - // Set cost (only used by Trinisphere) is applied last - for (final StaticAbility stAb : setAbilities) { - manaCost = stAb.applyAbility("SetCost", spell, manaCost); - } - - return manaCost; - } // GetSpellCostChange - - private ManaCostBeingPaid getCostAfterConvoke(final SpellAbility sa, final ManaCostBeingPaid originalCost, final SpellAbility spell) { - - List untappedCreats = CardLists.filter(spell.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); - untappedCreats = CardLists.filter(untappedCreats, CardPredicates.Presets.UNTAPPED); - - if (!untappedCreats.isEmpty()) { - final List choices = new ArrayList(); - choices.addAll(untappedCreats); - ArrayList usableColors = new ArrayList(); - ManaCostBeingPaid newCost = new ManaCostBeingPaid(originalCost.toString()); - Card tapForConvoke = null; - if (sa.getActivatingPlayer().isHuman()) { - tapForConvoke = GuiChoose.oneOrNone("Tap for Convoke? " + newCost.toString(), choices); - } else { - // TODO: AI to choose a creature to tap would go here - // Probably along with deciding how many creatures to - // tap - } - while (tapForConvoke != null && !untappedCreats.isEmpty()) { - final Card workingCard = (Card) tapForConvoke; - usableColors = GameActionPlay.getConvokableColors(workingCard, newCost); - - if (usableColors.size() != 0) { - String chosenColor = usableColors.get(0); - if (usableColors.size() > 1) { - if (sa.getActivatingPlayer().isHuman()) { - chosenColor = GuiChoose.one("Convoke for which color?", usableColors); - } else { - // TODO: AI for choosing which color to - // convoke goes here. - } - } - - if (chosenColor.equals("colorless")) { - newCost.decreaseColorlessMana(1); - } else { - String newCostStr = newCost.toString(); - newCostStr = newCostStr.replaceFirst( - MagicColor.toShortString(chosenColor), "").replaceFirst(" ", " "); - newCost = new ManaCostBeingPaid(newCostStr.trim()); - } - - sa.addTappedForConvoke(workingCard); - choices.remove(workingCard); - untappedCreats.remove(workingCard); - if (choices.isEmpty() || (newCost.getConvertedManaCost() == 0)) { - break; - } - } else { - untappedCreats.remove(workingCard); - } - - if (sa.getActivatingPlayer().isHuman()) { - tapForConvoke = GuiChoose.oneOrNone("Tap for Convoke? " + newCost.toString(), choices); - } else { - // TODO: AI to choose a creature to tap would go - // here - } - } - - // will only be null if user cancelled. - if (!sa.getTappedForConvoke().isEmpty()) { - // 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 Card c : sa.getTappedForConvoke()) { - c.setTapped(true); - } - - return newCost; - } - } - return null; - } - - private ManaCostBeingPaid getCostAfterDelve(final ManaCostBeingPaid originalCost, final Card originalCard) { - ManaCostBeingPaid manaCost; - final int cardsInGrave = originalCard.getController().getCardsIn(ZoneType.Graveyard).size(); - - final Player pc = originalCard.getController(); - if (pc.isHuman()) { - final Integer[] cntChoice = new Integer[cardsInGrave + 1]; - for (int i = 0; i <= cardsInGrave; i++) { - cntChoice[i] = Integer.valueOf(i); - } - - final Integer chosenAmount = GuiChoose.one("Exile how many cards?", cntChoice); - System.out.println("Delve for " + chosenAmount); - final List choices = new ArrayList(pc.getCardsIn(ZoneType.Graveyard)); - final List chosen = new ArrayList(); - for (int i = 0; i < chosenAmount; i++) { - final Card nowChosen = GuiChoose.oneOrNone("Exile which card?", choices); - - if (nowChosen == null) { - // User canceled,abort delving. - chosen.clear(); - break; - } - - choices.remove(nowChosen); - chosen.add(nowChosen); - } - - for (final Card c : chosen) { - game.getAction().exile(c); - } - - manaCost = new ManaCostBeingPaid(originalCost.toString()); - manaCost.decreaseColorlessMana(chosenAmount); - } else { - // AI - int numToExile = 0; - final int colorlessCost = originalCost.getColorlessManaAmount(); - - if (cardsInGrave <= colorlessCost) { - numToExile = cardsInGrave; - } else { - numToExile = colorlessCost; - } - - for (int i = 0; i < numToExile; i++) { - final List grave = pc.getZone(ZoneType.Graveyard).getCards(); - 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); - } - - game.getAction().exile(chosen); - } - manaCost = new ManaCostBeingPaid(originalCost.toString()); - manaCost.decreaseColorlessMana(numToExile); - } - return manaCost; - } - - /** - * choose optional additional costs. For HUMAN only - * @param activator - * - * @param original - * the original sa - * @return an ArrayList. - */ - public static SpellAbility chooseOptionalAdditionalCosts(Player activator, final SpellAbility original) { - //final HashMap map = new HashMap(); - final ArrayList abilities = GameActionUtil.getOptionalAdditionalCosts(original); - - if (!original.isSpell()) { - return original; - } - - return activator.getController().getAbilityToPlay(abilities); - } - - /** - *

- * playSpellAbility. - *

- * - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - */ - public final void playSpellAbility(SpellAbility sa, Player activator) { - FThreads.checkEDT("Player.playSpellAbility", false); - sa.setActivatingPlayer(activator); - - final Card source = sa.getSourceCard(); - - // Split card support - setSplitCardState(source, sa); - - if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { - CharmEffect.makeChoices(sa); - } - - sa = chooseOptionalAdditionalCosts(activator, sa); - - if (sa == null) { - return; - } - - // Need to check PayCosts, and Ability + All SubAbilities for Target - boolean newAbility = sa.getPayCosts() != null; - SpellAbility ability = sa; - while ((ability != null) && !newAbility) { - final Target tgt = ability.getTarget(); - - newAbility |= tgt != null; - ability = ability.getSubAbility(); - } - - // System.out.println("Playing:" + sa.getDescription() + " of " + sa.getSourceCard() + " new = " + newAbility); - if (newAbility) { - final TargetSelection ts = new TargetSelection(sa.getTarget(), sa); - CostPayment payment = null; - if (sa.getPayCosts() == null) { - payment = new CostPayment(new Cost(sa.getSourceCard(), "0", sa.isAbility()), sa); - } else { - payment = new CostPayment(sa.getPayCosts(), sa); - } - - final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, ts, payment); - req.fillRequirements(); - } else { - ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost()); - if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) { - manaCost = new ManaCostBeingPaid("0"); - } else { - manaCost = this.getSpellCostChange(sa, new ManaCostBeingPaid(sa.getManaCost())); - } - - if (!manaCost.isPaid()) { - FThreads.setInputAndWait(new InputPayManaSimple(game, sa, manaCost)); - } - - - if (manaCost.isPaid()) { - if (sa.isSpell() && !source.isCopiedSpell()) { - sa.setSourceCard(game.getAction().moveToStack(source)); - } - - game.getStack().add(sa); - } - } - } - - /** - *

- * playSpellAbility_NoStack. - *

- * - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - * @param skipTargeting - * a boolean. - */ - public final void playSpellAbilityNoStack(final Player human, final SpellAbility sa, final boolean skipTargeting) { - sa.setActivatingPlayer(human); - - if (sa.getPayCosts() != null) { - final TargetSelection ts = new TargetSelection(sa.getTarget(), sa); - final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); - - if (!sa.isTrigger()) { - payment.changeCost(); - } - - final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, ts, payment); - req.setSkipStack(true); - req.fillRequirements(skipTargeting); - } else { - ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost()); - if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) { - manaCost = new ManaCostBeingPaid("0"); - } else { - manaCost = this.getSpellCostChange(sa, new ManaCostBeingPaid(sa.getManaCost())); - } - - if( !manaCost.isPaid() ) { - FThreads.setInputAndWait(new InputPayManaSimple(game, sa, getSpellCostChange(sa, new ManaCostBeingPaid(sa.getManaCost())))); - } - - if (manaCost.isPaid()) { - AbilityUtils.resolve(sa, false); - } - - } - } - - - /** - * Gets the convokable colors. - * - * @param cardToConvoke - * the card to convoke - * @param cost - * the cost - * @return the convokable colors - */ - public static ArrayList getConvokableColors(final Card cardToConvoke, final ManaCostBeingPaid cost) { - final ArrayList usableColors = new ArrayList(); - - if (cost.getColorlessManaAmount() > 0) { - usableColors.add("colorless"); - } - for (final CardColor col : cardToConvoke.getColor()) { - for (final String strCol : col.toStringList()) { - if (strCol.equals("colorless")) { - continue; - } - if (cost.toString().contains(MagicColor.toShortString(strCol))) { - usableColors.add(strCol.toString()); - } - } - } - - return usableColors; - } - - private void setSplitCardState(final Card source, SpellAbility sa) { - // Split card support - if (source.isSplitCard()) { - List leftSplitAbilities = source.getState(CardCharacteristicName.LeftSplit).getSpellAbility(); - List rightSplitAbilities = source.getState(CardCharacteristicName.RightSplit).getSpellAbility(); - for (SpellAbility a : leftSplitAbilities) { - if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { - source.setState(CardCharacteristicName.LeftSplit); - break; - } - } - for (SpellAbility a : rightSplitAbilities) { - if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { - source.setState(CardCharacteristicName.RightSplit); - break; - } - } - } - } -} diff --git a/src/main/java/forge/game/GameActionUtil.java b/src/main/java/forge/game/GameActionUtil.java index 01137a5afa6..e685b7651cf 100644 --- a/src/main/java/forge/game/GameActionUtil.java +++ b/src/main/java/forge/game/GameActionUtil.java @@ -51,6 +51,7 @@ import forge.card.cost.CostPutCounter; import forge.card.cost.CostRemoveCounter; import forge.card.cost.CostReturn; import forge.card.cost.CostSacrifice; +import forge.card.cost.CostTapType; import forge.card.cost.CostUtil; import forge.card.mana.ManaCost; import forge.card.spellability.Ability; @@ -65,11 +66,11 @@ import forge.control.input.InputSelectCardsFromList; import forge.game.event.CardDamagedEvent; import forge.game.event.LifeLossEvent; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; import forge.gui.GuiDialog; -import forge.gui.GuiUtils; import forge.sound.SoundEffectType; @@ -102,9 +103,9 @@ public final class GameActionUtil { public void resolve() { final GameState game = Singletons.getModel().getGame(); if ( canRegenerate ) - game.getAction().destroy(affected); + game.getAction().destroy(affected, this); else - game.getAction().destroyNoRegeneration(affected); + game.getAction().destroyNoRegeneration(affected, this); } } @@ -274,7 +275,7 @@ public final class GameActionUtil { if (p.isHuman()) { if (GuiDialog.confirm(rippledCards[i], "Cast " + rippledCards[i].getName() + "?")) { - game.getActionPlay().playCardWithoutManaCost(rippledCards[i], p); + ((HumanPlayer)p).playCardWithoutManaCost(rippledCards[i]); revealed.remove(rippledCards[i]); } } else { @@ -400,8 +401,21 @@ public final class GameActionUtil { * a {@link forge.Command} object. * @param sourceAbility TODO */ - public static boolean payCostDuringAbilityResolve(final Player p, final SpellAbility ability, final Cost cost, SpellAbility sourceAbility, final GameState game) { + public static boolean payCostDuringAbilityResolve(final SpellAbility ability, final Cost cost, SpellAbility sourceAbility, final GameState game) { + + // Only human player pays this way + final Player p = ability.getActivatingPlayer(); final Card source = ability.getSourceCard(); + Card current = null; // Used in spells with RepeatEach effect to distinguish cards, Cut the Tethers + if (!source.getRemembered().isEmpty()) { + if (source.getRemembered().get(0) instanceof Card) { + current = (Card) source.getRemembered().get(0); + } + } + if (!source.getImprinted().isEmpty()) { + current = source.getImprinted().get(0); + } + final List parts = cost.getCostParts(); ArrayList remainingParts = new ArrayList(cost.getCostParts()); CostPart costPart = null; @@ -462,7 +476,7 @@ public final class GameActionUtil { int amount = getAmountFromPartX(part, source, sourceAbility); String plural = amount > 1 ? "s" : ""; - if (!part.canPay(sourceAbility, source, p, cost, game)) + if (!part.canPay(sourceAbility)) return false; if ( false == GuiDialog.confirm(source, "Do you want to remove " + amount + " " + counterType.getName() + " counter" + plural + " from " + source + "?")) @@ -501,73 +515,39 @@ public final class GameActionUtil { } else if (part instanceof CostSacrifice) { - CostSacrifice sacCost = (CostSacrifice) part; - String valid = sacCost.getType(); - int amount = Integer.parseInt(sacCost.getAmount()); - List list = AbilityUtils.filterListByType(p.getCardsIn(ZoneType.Battlefield), valid, ability); - - if (list.size() < amount) { - // unable to pay (not enough cards) - return false; - } - - GuiUtils.clearPanelSelections(); - GuiUtils.setPanelSelection(source); - - List toSac = p.getController().choosePermanentsToSacrifice(list, amount, ability, false, true); - if ( toSac.size() != amount ) - return false; - - CostPartWithList cpl = (CostPartWithList)part; - for(Card c : toSac) { - cpl.executePayment(sourceAbility, c); - } - cpl.reportPaidCardsTo(sourceAbility); + int amount = Integer.parseInt(((CostSacrifice)part).getAmount()); + List list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), p, source); + boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "sacrifice"); + if(!hasPaid) return false; } else if (part instanceof CostReturn) { - List choiceList = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), p, source); + List list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), p, source); int amount = getAmountFromPartX(part, source, sourceAbility); - - InputSelectCards inp = new InputSelectCardsFromList(amount, amount, choiceList); - inp.setMessage("Select %d card(s) to return to hand"); - inp.setCancelAllowed(true); - - FThreads.setInputAndWait(inp); - if( inp.hasCancelled() || inp.getSelected().size() != amount) - return false; - - CostPartWithList cpl = (CostPartWithList)part; - for(Card c : inp.getSelected()) { - cpl.executePayment(sourceAbility, c); - } - cpl.reportPaidCardsTo(sourceAbility); + boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "return to hand"); + if(!hasPaid) return false; } else if (part instanceof CostDiscard) { - List choiceList = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType().split(";"), p, source); + List list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType().split(";"), p, source); int amount = getAmountFromPartX(part, source, sourceAbility); - - InputSelectCards inp = new InputSelectCardsFromList(amount, amount, choiceList); - inp.setMessage("Select %d card(s) to discard"); - inp.setCancelAllowed(true); - - FThreads.setInputAndWait(inp); - if( inp.hasCancelled() || inp.getSelected().size() != amount) - return false; - - CostPartWithList cpl = (CostPartWithList)part; - for(Card c : inp.getSelected()) { - cpl.executePayment(sourceAbility, c); - } - cpl.reportPaidCardsTo(sourceAbility); + boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "discard"); + if(!hasPaid) return false; + } + + else if (part instanceof CostTapType) { + List list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), p, source); + list = CardLists.filter(list, Presets.UNTAPPED); + int amount = getAmountFromPartX(part, source, sourceAbility); + boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "tap"); + if(!hasPaid) return false; } else if (part instanceof CostPartMana ) { if (!((CostPartMana) part).getManaToPay().equals("0")) // non-zero costs require input mayRemovePart = false; } else - throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - An unhandled type of cost has ocurred: " + part.getClass()); + throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - An unhandled type of cost was met: " + part.getClass()); if( mayRemovePart ) remainingParts.remove(part); @@ -585,11 +565,31 @@ public final class GameActionUtil { if (!(costPart instanceof CostPartMana )) throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - The remaining payment type is not Mana."); - InputPayment toSet = new InputPayManaExecuteCommands(p, source + "\r\n", ability.getManaCost()); + InputPayment toSet = current == null + ? new InputPayManaExecuteCommands(p, source + "\r\n", ability.getManaCost()) + : new InputPayManaExecuteCommands(p, source + "\r\n" + "Current Card: " + current + "\r\n" , ability.getManaCost()); FThreads.setInputAndWait(toSet); return toSet.isPaid(); } + private static boolean payCostPart(SpellAbility sourceAbility, CostPartWithList cpl, int amount, List list, String actionName) { + if (list.size() < amount) return false; // unable to pay (not enough cards) + + InputSelectCards inp = new InputSelectCardsFromList(amount, amount, list); + inp.setMessage("Select %d " + cpl.getDescriptiveType() + " card(s) to " + actionName); + inp.setCancelAllowed(true); + + FThreads.setInputAndWait(inp); + if( inp.hasCancelled() || inp.getSelected().size() != amount) + return false; + + for(Card c : inp.getSelected()) { + cpl.executePayment(sourceAbility, c); + } + cpl.reportPaidCardsTo(sourceAbility); + return true; + } + // not restricted to combat damage, not restricted to dealing damage to // creatures/players /** diff --git a/src/main/java/forge/game/GameAge.java b/src/main/java/forge/game/GameAge.java new file mode 100644 index 00000000000..c31770d935f --- /dev/null +++ b/src/main/java/forge/game/GameAge.java @@ -0,0 +1,8 @@ +package forge.game; + +public enum GameAge { + BeforeMulligan, + Mulligan, + Play, + GameOver +} \ No newline at end of file diff --git a/src/main/java/forge/game/GameLossReason.java b/src/main/java/forge/game/GameLossReason.java index 652ccc90b70..05700eb0f1f 100644 --- a/src/main/java/forge/game/GameLossReason.java +++ b/src/main/java/forge/game/GameLossReason.java @@ -33,6 +33,8 @@ public enum GameLossReason { // 104.3e and others /** The Spell effect. */ SpellEffect, + + EdhGeneralDamage, OpponentWon diff --git a/src/main/java/forge/game/GameNew.java b/src/main/java/forge/game/GameNew.java index eaa8c82c76e..fb0be418347 100644 --- a/src/main/java/forge/game/GameNew.java +++ b/src/main/java/forge/game/GameNew.java @@ -11,6 +11,8 @@ import java.util.Set; import javax.swing.JOptionPane; +import org.apache.commons.lang.time.StopWatch; + import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; @@ -19,6 +21,7 @@ import com.google.common.collect.Lists; import forge.Card; import forge.CardLists; import forge.CardPredicates; +import forge.FThreads; import forge.Singletons; import forge.card.trigger.TriggerHandler; import forge.card.trigger.TriggerType; @@ -117,7 +120,13 @@ public class GameNew { boolean canSideBoard = !isFirstGame && gameType.isSideboardingAllowed() && hasSideboard; if (canSideBoard) { - psc.setCurrentDeck(player.getController().sideboard(psc.getCurrentDeck(), gameType)); + StopWatch sw = new StopWatch(); + sw.start(); + System.out.println(FThreads.debugGetCurrThreadId() + " > " + "entering sideboard routine"); + Deck sideboarded = player.getController().sideboard(psc.getCurrentDeck(), gameType); + sw.stop(); + System.out.println(FThreads.debugGetCurrThreadId() + " > " + "sideboard routine returned after " + sw.getTime() + " ms"); + psc.setCurrentDeck(sideboarded); } else { psc.restoreOriginalDeck(); } @@ -211,7 +220,7 @@ public class GameNew { playersConditions.put(p, players.get(p.getLobbyPlayer())); } - game.setMulliganned(false); + game.setAge(GameAge.Mulligan); match.getInput().clearInput(); //Card.resetUniqueNumber(); diff --git a/src/main/java/forge/game/GameState.java b/src/main/java/forge/game/GameState.java index 395469436ae..d7d2ccc2348 100644 --- a/src/main/java/forge/game/GameState.java +++ b/src/main/java/forge/game/GameState.java @@ -18,6 +18,7 @@ package forge.game; import java.util.ArrayList; + import java.util.Collections; import java.util.List; import java.util.Map; @@ -54,9 +55,8 @@ import forge.game.zone.PlayerZone; import forge.game.zone.Zone; import forge.game.zone.ZoneType; -/** - * Represents the state of a single game and is - * "cleaned up" at each new game. + /** + * Represents the state of a single game, a new instance is created for each game. */ public class GameState { private final GameType type; @@ -82,14 +82,12 @@ public class GameState { private final GameLog gameLog = new GameLog(); private final ColorChanger colorChanger = new ColorChanger(); - private boolean gameOver = false; - private final Zone stackZone = new Zone(ZoneType.Stack); private long timestamp = 0; public final GameAction action; - public final GameActionPlay actionPlay; private final MatchController match; + private GameAge age = GameAge.BeforeMulligan; /** * Constructor. @@ -109,7 +107,6 @@ public class GameState { allPlayers = Collections.unmodifiableList(players); roIngamePlayers = Collections.unmodifiableList(ingamePlayers); action = new GameAction(this); - actionPlay = new GameActionPlay(this); stack = new MagicStack(this); phaseHandler = new PhaseHandler(this); @@ -293,7 +290,7 @@ public class GameState { * @return the gameOver */ public synchronized boolean isGameOver() { - return gameOver; + return age == GameAge.GameOver; } /** @@ -301,7 +298,7 @@ public class GameState { * @param go the gameOver to set */ public synchronized void setGameOver(GameEndReason reason) { - this.gameOver = true; + this.age = GameAge.GameOver; for (Player p : roIngamePlayers) { p.onGameOver(); } @@ -484,19 +481,10 @@ public class GameState { } - public String getOrdinalPosition(Player player, Player startingPlayer) { + public int getPosition(Player player, Player startingPlayer) { int startPosition = roIngamePlayers.indexOf(startingPlayer); int position = (roIngamePlayers.indexOf(player) + startPosition) % roIngamePlayers.size() + 1; - String[] sufixes = new String[] { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" }; - switch (position % 100) { - case 11: - case 12: - case 13: - return position + "th"; - default: - return position + sufixes[position % 10]; - - } + return position; } /** @@ -663,16 +651,11 @@ public class GameState { } } - /** - * TODO: Write javadoc for this method. - * @return - */ - public GameActionPlay getActionPlay() { - // TODO Auto-generated method stub - return actionPlay; + public GameAge getAge() { + return age; } - public boolean mulliganned = false; - public boolean hasMulliganned(){ return mulliganned; } - public void setMulliganned(boolean value) { mulliganned = value; } + void setAge(GameAge value) { + age = value; + } } diff --git a/src/main/java/forge/game/GameType.java b/src/main/java/forge/game/GameType.java index c6cbf5230e1..3342002eb00 100644 --- a/src/main/java/forge/game/GameType.java +++ b/src/main/java/forge/game/GameType.java @@ -16,7 +16,8 @@ public enum GameType { Constructed ( DeckFormat.Constructed, false, true ), Archenemy ( DeckFormat.Archenemy, false, false ), Planechase ( DeckFormat.Planechase, false, false ), - Vanguard ( DeckFormat.Vanguard, true, true ); + Vanguard ( DeckFormat.Vanguard, true, true ), + Commander ( DeckFormat.Commander, false, false); private final DeckFormat decksFormat; private final boolean bCardpoolLimited; diff --git a/src/main/java/forge/game/MatchController.java b/src/main/java/forge/game/MatchController.java index 1119eb528d6..eae422cd7fa 100644 --- a/src/main/java/forge/game/MatchController.java +++ b/src/main/java/forge/game/MatchController.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; + import forge.Constant.Preferences; import forge.Singletons; import forge.control.FControl; @@ -16,6 +17,7 @@ import forge.error.BugReporter; import forge.game.ai.AiProfileUtil; import forge.game.event.DuelOutcomeEvent; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.LobbyPlayer; import forge.game.player.Player; import forge.game.player.PlayerStatistics; @@ -128,8 +130,7 @@ public class MatchController { */ public void startRound() { - // Deal with circular dependencies here - input = new InputControl(); + input = new InputControl(this); currentGame = Singletons.getModel().newGame(players.keySet(),gameType, this); Map startConditions = new HashMap(); @@ -156,7 +157,7 @@ public class MatchController { try { - Player localHuman = Aggregates.firstFieldEquals(currentGame.getPlayers(), Player.Accessors.FN_GET_TYPE, PlayerType.HUMAN); + HumanPlayer localHuman = (HumanPlayer) Aggregates.firstFieldEquals(currentGame.getPlayers(), Player.Accessors.FN_GET_TYPE, PlayerType.HUMAN); FControl.SINGLETON_INSTANCE.setPlayer(localHuman); CMatchUI.SINGLETON_INSTANCE.initMatch(currentGame.getRegisteredPlayers(), localHuman); CDock.SINGLETON_INSTANCE.onGameStarts(currentGame, localHuman); @@ -177,6 +178,7 @@ public class MatchController { final boolean canRandomFoil = Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_RANDOM_FOIL) && gameType == GameType.Constructed; GameNew.newGame(this, startConditions, currentGame, canRandomFoil); + currentGame.setAge(GameAge.Mulligan); getInput().clearInput(); //getInput().setNewInput(currentGame); @@ -338,4 +340,11 @@ public class MatchController { public static int getPoisonCountersAmountToLose() { return 10; } + + public void afterMulligans() + { + currentGame.getAction().handleLeylinesAndChancellors(); + currentGame.setAge(GameAge.Play); + getInput().clearInput(); + } } diff --git a/src/main/java/forge/game/MatchStartHelper.java b/src/main/java/forge/game/MatchStartHelper.java index add9e4f39e8..e9b75c6d4ca 100644 --- a/src/main/java/forge/game/MatchStartHelper.java +++ b/src/main/java/forge/game/MatchStartHelper.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.Map; import forge.deck.Deck; +import forge.deck.DeckSection; import forge.game.player.LobbyPlayer; import forge.item.CardPrinted; @@ -33,6 +34,15 @@ public class MatchStartHelper { players.put(player, start); } + + public void addCommanderPlayer(final LobbyPlayer player, final Deck deck) + { + PlayerStartConditions start = new PlayerStartConditions(deck); + start.setStartingLife(40); + start.setCardsInCommand(deck.get(DeckSection.Commander).toFlatList()); + + players.put(player, start); + } public void addArchenemy(final LobbyPlayer player, final Deck deck, final Iterable schemes) { PlayerStartConditions start = new PlayerStartConditions(deck); diff --git a/src/main/java/forge/game/ai/AiController.java b/src/main/java/forge/game/ai/AiController.java index 4b36151e2dd..1c17d34b6fe 100644 --- a/src/main/java/forge/game/ai/AiController.java +++ b/src/main/java/forge/game/ai/AiController.java @@ -45,6 +45,7 @@ import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellPermanent; import forge.game.GameActionUtil; import forge.game.GameState; +import forge.game.phase.CombatUtil; import forge.game.phase.PhaseType; import forge.game.player.AIPlayer; import forge.game.player.Player; @@ -467,7 +468,14 @@ public class AiController { // This is for playing spells regularly (no Cascade/Ripple etc.) private boolean canPlayAndPayFor(final SpellAbility sa) { - return sa.canPlay() && sa.canPlayAI() && ComputerUtilCost.canPayCost(sa, player); + boolean canPlay = sa.canPlay(); + if (!canPlay) + return false; + //System.out.printf("Ai thinks of %s @ %s >>> ", sa, sa.getActivatingPlayer().getGame().getPhaseHandler().debugPrintState()); + boolean aiWouldPlay = sa.canPlayAI(); + boolean canPay = ComputerUtilCost.canPayCost(sa, player); + //System.out.printf("wouldPlay: %s, canPay: %s%n", aiWouldPlay, canPay); + return aiWouldPlay && canPay; } // not sure "playing biggest spell" matters? @@ -534,12 +542,12 @@ public class AiController { if ((uTypes != null) && (sa != null)) { hand = CardLists.getValidCards(hand, uTypes, sa.getActivatingPlayer(), sa.getSourceCard()); } - return getCardsToDiscard(numDiscard, hand, sa); + return getCardsToDiscard(numDiscard, numDiscard, hand, sa); } - public List getCardsToDiscard(final int numDiscard, final List validCards, final SpellAbility sa) { + public List getCardsToDiscard(final int min, final int max, final List validCards, final SpellAbility sa) { - if (validCards.size() < numDiscard) { + if (validCards.size() < min) { return null; } @@ -551,7 +559,7 @@ public class AiController { } // look for good discards - while (count < numDiscard) { + while (count < min) { Card prefCard = null; if (sa != null && sa.getActivatingPlayer() != null && sa.getActivatingPlayer().isOpponentOf(player)) { for (Card c : validCards) { @@ -574,7 +582,7 @@ public class AiController { } } - final int discardsLeft = numDiscard - count; + final int discardsLeft = min - count; // choose rest for (int i = 0; i < discardsLeft; i++) { @@ -654,20 +662,17 @@ public class AiController { case Encode: if (logic == null) { - // Base Logic is choose "best" - choice = ComputerUtilCard.getBestAI(options); - } else if ("WorstCard".equals(logic)) { - choice = ComputerUtilCard.getWorstAI(options); - } else if (logic.equals("BestBlocker")) { - if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) { - options = CardLists.filter(options, Presets.UNTAPPED); + List attackers = CardLists.filter(options, new Predicate() { + @Override + public boolean apply(final Card c) { + return CombatUtil.canAttackNextTurn(c); + } + }); + if (attackers.isEmpty()) { + choice = ComputerUtilCard.getBestAI(options); + } else { + choice = ComputerUtilCard.getBestAI(attackers); } - choice = ComputerUtilCard.getBestCreatureAI(options); - } else if (logic.equals("Clone")) { - if (!CardLists.getValidCards(options, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host).isEmpty()) { - options = CardLists.getValidCards(options, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host); - } - choice = ComputerUtilCard.getBestAI(options); } return choice; @@ -743,6 +748,7 @@ public class AiController { /** 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(mandatory, withoutPayingManaCost)) { @@ -778,9 +784,11 @@ public class AiController { if (!player.isUnlimitedHandSize()) { int max = Math.min(player.getZone(ZoneType.Hand).size(), size - player.getMaxHandSize()); - final List toDiscard = player.getAi().getCardsToDiscard(max, (String[])null, null); - for (int i = 0; i < toDiscard.size(); i++) { - player.discard(toDiscard.get(i), null); + if( max > 0) { + final List toDiscard = player.getAi().getCardsToDiscard(max, (String[])null, null); + for (int i = 0; i < toDiscard.size(); i++) { + player.discard(toDiscard.get(i), null); + } } game.getStack().chooseOrderOfSimultaneousStackEntryAll(); } @@ -856,5 +864,35 @@ public class AiController { } while ( sa != null ); } + 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; + } } diff --git a/src/main/java/forge/game/ai/AiInputBlock.java b/src/main/java/forge/game/ai/AiInputBlock.java index 341818aa4a8..55873b3bfe7 100644 --- a/src/main/java/forge/game/ai/AiInputBlock.java +++ b/src/main/java/forge/game/ai/AiInputBlock.java @@ -14,21 +14,17 @@ import forge.game.player.Player; * */ public class AiInputBlock extends InputBase { - private final GameState game; /** * TODO: Write javadoc for Constructor. * @param game * @param player */ - public AiInputBlock(GameState game, Player player) { - super(); - this.game = game; - this.player = player; + public AiInputBlock(Player human) { + super(human); + this.game = human.getGame(); } - private final Player player; - private static final long serialVersionUID = -2253562658069995572L; @Override diff --git a/src/main/java/forge/game/ai/AiInputCommon.java b/src/main/java/forge/game/ai/AiInputCommon.java index 277e1f77e90..ca32bbbbf65 100644 --- a/src/main/java/forge/game/ai/AiInputCommon.java +++ b/src/main/java/forge/game/ai/AiInputCommon.java @@ -43,6 +43,7 @@ public class AiInputCommon extends InputBase implements AiInput { * a {@link forge.game.player.Computer} object. */ public AiInputCommon(final AiController iComputer) { + super(iComputer.getPlayer()); this.computer = iComputer; } diff --git a/src/main/java/forge/game/ai/ComputerUtil.java b/src/main/java/forge/game/ai/ComputerUtil.java index 5b218e2d817..d11420407fc 100644 --- a/src/main/java/forge/game/ai/ComputerUtil.java +++ b/src/main/java/forge/game/ai/ComputerUtil.java @@ -254,7 +254,7 @@ public class ComputerUtil { final SpellAbility newSA = sa.copyWithNoManaCost(); newSA.setActivatingPlayer(ai); - if (!ComputerUtilCost.canPayAdditionalCosts(newSA, ai, game)) { + if (!ComputerUtilCost.canPayAdditionalCosts(newSA, ai)) { return; } @@ -278,6 +278,7 @@ public class ComputerUtil { * a {@link forge.card.spellability.SpellAbility} object. */ public static final void playNoStack(final AIPlayer ai, final SpellAbility sa, final GameState 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(); @@ -285,8 +286,6 @@ public class ComputerUtil { sa.setSourceCard(game.getAction().moveToStack(source)); } - sa.setActivatingPlayer(ai); - final Cost cost = sa.getPayCosts(); if (cost == null) { ComputerUtilMana.payManaCost(ai, sa); @@ -1217,7 +1216,7 @@ public class ComputerUtil { * @param min * @return */ - public static List getCardsToDiscardFromOpponent(AIPlayer chooser, Player discarder, SpellAbility sa, List validCards, int min) { + public static List getCardsToDiscardFromOpponent(AIPlayer 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) { @@ -1258,11 +1257,11 @@ public class ComputerUtil { * @param min * @return */ - public static List getCardsToDiscardFromFriend(AIPlayer aiChooser, Player p, SpellAbility sa, List validCards, int min) { + public static List getCardsToDiscardFromFriend(AIPlayer aiChooser, Player p, SpellAbility sa, List validCards, int min, int max) { if (p instanceof AIPlayer) { // ask that ai player what he would like to discard - return ((AIPlayer) p).getAi().getCardsToDiscard(min, validCards, sa); + return ((AIPlayer) p).getAi().getCardsToDiscard(min, max, validCards, sa); } // no special options for human or remote friends - return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min); + return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min, max); } } diff --git a/src/main/java/forge/game/ai/ComputerUtilCost.java b/src/main/java/forge/game/ai/ComputerUtilCost.java index 5e0b6d9cf00..077bdae73ec 100644 --- a/src/main/java/forge/game/ai/ComputerUtilCost.java +++ b/src/main/java/forge/game/ai/ComputerUtilCost.java @@ -8,7 +8,6 @@ import org.apache.commons.lang3.StringUtils; import forge.Card; import forge.CardLists; import forge.CounterType; -import forge.Singletons; import forge.card.ability.AbilityUtils; import forge.card.cost.Cost; import forge.card.cost.CostDamage; @@ -21,7 +20,6 @@ import forge.card.cost.CostRemoveCounter; import forge.card.cost.CostSacrifice; import forge.card.spellability.Spell; import forge.card.spellability.SpellAbility; -import forge.game.GameState; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.util.MyRandom; @@ -338,8 +336,6 @@ public class ComputerUtilCost { * @return a boolean. */ public static boolean canPayCost(final SpellAbility sa, final Player player) { - - final GameState game = Singletons.getModel().getGame(); // Check for stuff like Nether Void int extraManaNeeded = 0; if (sa instanceof Spell) { @@ -361,7 +357,7 @@ public class ComputerUtilCost { return false; } - return ComputerUtilCost.canPayAdditionalCosts(sa, player, game); + return ComputerUtilCost.canPayAdditionalCosts(sa, player); } // canPayCost() /** @@ -375,7 +371,7 @@ public class ComputerUtilCost { * a {@link forge.game.player.Player} object. * @return a boolean. */ - public static boolean canPayAdditionalCosts(final SpellAbility sa, final Player player, final GameState game) { + public static boolean canPayAdditionalCosts(final SpellAbility sa, final Player player) { if (sa.getActivatingPlayer() == null) { final StringBuilder sb = new StringBuilder(); sb.append(sa.getSourceCard()); @@ -383,7 +379,7 @@ public class ComputerUtilCost { System.out.println(sb.toString()); sa.setActivatingPlayer(player); } - return CostPayment.canPayAdditionalCosts(game, sa.getPayCosts(), sa); + return CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa); } } diff --git a/src/main/java/forge/game/ai/ComputerUtilMana.java b/src/main/java/forge/game/ai/ComputerUtilMana.java index b85b2e725b2..bf8effe3422 100644 --- a/src/main/java/forge/game/ai/ComputerUtilMana.java +++ b/src/main/java/forge/game/ai/ComputerUtilMana.java @@ -13,21 +13,21 @@ import forge.Card; import forge.CardLists; import forge.CardUtil; import forge.Constant; -import forge.Singletons; import forge.card.MagicColor; import forge.card.ability.AbilityUtils; import forge.card.ability.ApiType; +import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.cost.CostPayment; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostBeingPaid; import forge.card.mana.ManaCostShard; import forge.card.mana.ManaPool; +import forge.card.spellability.Ability; import forge.card.spellability.AbilityManaPart; import forge.card.spellability.AbilitySub; import forge.card.spellability.SpellAbility; import forge.game.GameActionUtil; -import forge.game.GameState; import forge.game.player.AIPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -58,8 +58,6 @@ public class ComputerUtilMana { */ public static boolean payManaCost(final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable) { ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, test, extraMana); - - final GameState game = Singletons.getModel().getGame(); final ManaPool manapool = ai.getManaPool(); cost = manapool.payManaFromPool(sa, cost); @@ -69,7 +67,7 @@ public class ComputerUtilMana { manapool.clearManaPaid(sa, test); return true; } - + // get map of mana abilities final Map> manaAbilityMap = ComputerUtilMana.mapManaSources(ai, checkPlayable); // initialize ArrayList list for mana needed @@ -109,7 +107,7 @@ public class ComputerUtilMana { ma.setActivatingPlayer(ai); // if the AI can't pay the additional costs skip the mana ability if (ma.getPayCosts() != null && checkPlayable) { - if (!ComputerUtilCost.canPayAdditionalCosts(ma, ai, game)) { + if (!ComputerUtilCost.canPayAdditionalCosts(ma, ai)) { continue; } } else if (sourceCard.isTapped() && checkPlayable) { @@ -186,7 +184,7 @@ public class ComputerUtilMana { // Pay additional costs if (ma.getPayCosts() != null) { final CostPayment pay = new CostPayment(ma.getPayCosts(), ma); - if (!pay.payComputerCosts((AIPlayer)ai, game)) { + if (!pay.payComputerCosts((AIPlayer)ai, ai.getGame())) { continue; } } else { @@ -241,6 +239,22 @@ public class ComputerUtilMana { } // payManaCost() + // TODO: this code is disconnected now, it was moved here from MagicStack, where X cost is not processed any more + public static void computerPayX(final SpellAbility sa, AIPlayer player, int xCost) { + final int neededDamage = CardFactoryUtil.getNeededXDamage(sa); + final Ability ability = new Ability(sa.getSourceCard(), ManaCost.get(xCost)) { + @Override + public void resolve() { + sa.getSourceCard().addXManaCostPaid(1); + } + }; + + while (ComputerUtilCost.canPayCost(ability, player) && (neededDamage != sa.getSourceCard().getXManaCostPaid())) { + ComputerUtil.playNoStack(player, ability, player.getGame()); + } + + } + /** *

* payManaCost. @@ -401,8 +415,7 @@ public class ComputerUtilMana { final ManaCost mana = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : sa.getManaCost(); ManaCostBeingPaid cost = new ManaCostBeingPaid(mana); - - cost = Singletons.getModel().getGame().getActionPlay().getSpellCostChange(sa, cost); + cost.applySpellCostChange(sa); final Card card = sa.getSourceCard(); // Tack xMana Payments into mana here if X is a set value @@ -458,7 +471,6 @@ public class ComputerUtilMana { //This method is currently used by AI to estimate human's available mana private static List getAvailableMana(final Player ai, final boolean checkPlayable) { - final GameState game = Singletons.getModel().getGame(); final List list = ai.getCardsIn(ZoneType.Battlefield); list.addAll(ai.getCardsIn(ZoneType.Hand)); final List manaSources = CardLists.filter(list, new Predicate() { @@ -523,7 +535,7 @@ public class ComputerUtilMana { // ability m.setActivatingPlayer(ai); if (cost != null) { - if (!ComputerUtilCost.canPayAdditionalCosts(m, ai, game)) { + if (!ComputerUtilCost.canPayAdditionalCosts(m, ai)) { continue; } } diff --git a/src/main/java/forge/game/phase/Combat.java b/src/main/java/forge/game/phase/Combat.java index acc7d96ad0e..8bbede8cdad 100644 --- a/src/main/java/forge/game/phase/Combat.java +++ b/src/main/java/forge/game/phase/Combat.java @@ -51,7 +51,7 @@ public class Combat { private final Map> blockerMap = new TreeMap>(); private final Set blocked = new HashSet(); - private final HashMap> unblockedMap = new HashMap>(); + private final Set unblocked = new HashSet(); private final HashMap defendingDamageMap = new HashMap(); // Defenders are the Defending Player + Each controlled Planeswalker @@ -83,7 +83,7 @@ public class Combat { this.resetAttackers(); this.blocked.clear(); - this.unblockedMap.clear(); + this.unblocked.clear(); this.defendingDamageMap.clear(); this.attackingPlayer = null; @@ -453,6 +453,13 @@ public class Combat { return this.blocked.contains(attacker); } + public final void setBlocked(final Card attacker) { + if (!this.blocked.contains(attacker)) { + this.blocked.add(attacker); + this.unblocked.remove(attacker); + } + } + /** *

* addBlocker. @@ -771,8 +778,6 @@ public class Combat { public void dealAssignedDamage() { // This function handles both Regular and First Strike combat assignment - final boolean bFirstStrike = Singletons.getModel().getGame().getPhaseHandler().is(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE); - final HashMap defMap = this.getDefendingDamageMap(); for (final Entry entry : defMap.entrySet()) { @@ -784,18 +789,6 @@ public class Combat { } } - final List unblocked = new ArrayList(bFirstStrike ? this.getUnblockedAttackers() : this.getUnblockedFirstStrikeAttackers()); - - for (int j = 0; j < unblocked.size(); j++) { - if (bFirstStrike) { - CombatUtil.checkUnblockedAttackers(unblocked.get(j)); - } else { - if (!unblocked.get(j).hasFirstStrike() && !unblocked.get(j).hasDoubleStrike()) { - CombatUtil.checkUnblockedAttackers(unblocked.get(j)); - } - } - } - // this can be much better below here... final List combatants = new ArrayList(); @@ -840,7 +833,7 @@ public class Combat { * @return a boolean. */ public final boolean isUnblocked(final Card att) { - return this.unblockedMap.containsKey(att); + return this.unblocked.contains(att); } /** @@ -852,7 +845,7 @@ public class Combat { */ public final List getUnblockedAttackers() { final List out = new ArrayList(); - for (Card c : this.unblockedMap.keySet()) { + for (Card c : this.unblocked) { if (!c.hasFirstStrike()) { out.add(c); } @@ -869,7 +862,7 @@ public class Combat { */ public final List getUnblockedFirstStrikeAttackers() { final List out = new ArrayList(); - for (Card c : this.unblockedMap.keySet()) { // only add creatures without firstStrike to this + for (Card c : this.unblocked) { // only add creatures without firstStrike to this if (c.hasFirstStrike() || c.hasDoubleStrike()) { out.add(c); } @@ -886,7 +879,9 @@ public class Combat { * a {@link forge.Card} object. */ public final void addUnblockedAttacker(final Card c) { - this.unblockedMap.put(c, new ArrayList()); + if (!this.unblocked.contains(c)) { + this.unblocked.add(c); + } } public boolean isPlayerAttacked(Player priority) { diff --git a/src/main/java/forge/game/phase/CombatUtil.java b/src/main/java/forge/game/phase/CombatUtil.java index d78e99e53df..f1e674fe80e 100644 --- a/src/main/java/forge/game/phase/CombatUtil.java +++ b/src/main/java/forge/game/phase/CombatUtil.java @@ -1152,7 +1152,7 @@ public class CombatUtil { ability.setActivatingPlayer(c.getController()); if (c.getController().isHuman()) { - if ( GameActionUtil.payCostDuringAbilityResolve(c.getController(), ability, attackCost, null, game) ) { + if ( GameActionUtil.payCostDuringAbilityResolve(ability, attackCost, null, game) ) { if (!crd.hasKeyword("Vigilance")) { crd.tap(); } } else { game.getCombat().removeFromCombat(crd); @@ -1207,7 +1207,7 @@ public class CombatUtil { final Player opponent = Singletons.getModel().getGame().getCombat().getDefendingPlayerRelatedTo(c).get(0); //List list = AbilityUtils.filterListByType(opponent.getCardsIn(ZoneType.Battlefield), "Permanent", this); final List list = opponent.getCardsIn(ZoneType.Battlefield); - List toSac = opponent.getController().choosePermanentsToSacrifice(list, a, this, false, false); + List toSac = opponent.getController().choosePermanentsToSacrifice(list, "Card", a, this, false, false); for(Card sacd : toSac) { final GameState game = Singletons.getModel().getGame(); @@ -1293,22 +1293,6 @@ public class CombatUtil { c.getController().incrementAttackersDeclaredThisTurn(); } // checkDeclareAttackers - /** - *

- * checkUnblockedAttackers. - *

- * - * @param c - * a {@link forge.Card} object. - */ - public static void checkUnblockedAttackers(final Card c) { - - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("Card", c); - Singletons.getModel().getGame().getTriggerHandler().runTrigger(TriggerType.AttackerUnblocked, runParams, false); - } - /** *

* checkDeclareBlockers. diff --git a/src/main/java/forge/game/phase/EndOfTurn.java b/src/main/java/forge/game/phase/EndOfTurn.java index 10f411dd34c..0ddb0ee19b4 100644 --- a/src/main/java/forge/game/phase/EndOfTurn.java +++ b/src/main/java/forge/game/phase/EndOfTurn.java @@ -96,7 +96,7 @@ public class EndOfTurn extends Phase { @Override public void resolve() { if (card.isInPlay()) { - game.getAction().destroy(card); + game.getAction().destroy(card, this); } } }; @@ -116,7 +116,7 @@ public class EndOfTurn extends Phase { @Override public void resolve() { if (card.isInPlay()) { - game.getAction().destroy(card); + game.getAction().destroy(card, this); } } }; diff --git a/src/main/java/forge/game/phase/PhaseHandler.java b/src/main/java/forge/game/phase/PhaseHandler.java index e6561528d27..4b2ca17178f 100644 --- a/src/main/java/forge/game/phase/PhaseHandler.java +++ b/src/main/java/forge/game/phase/PhaseHandler.java @@ -709,6 +709,7 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable { *

*/ public final void passPriority() { + FThreads.checkEDT("PhaseHandler.passPriority", false); // stop game if it's outcome is clear if (game.isGameOver()) { return; diff --git a/src/main/java/forge/game/phase/Upkeep.java b/src/main/java/forge/game/phase/Upkeep.java index 4f0bab96f3c..d3d92dad5f9 100644 --- a/src/main/java/forge/game/phase/Upkeep.java +++ b/src/main/java/forge/game/phase/Upkeep.java @@ -94,7 +94,6 @@ public class Upkeep extends Phase { Upkeep.upkeepDemonicHordes(game); Upkeep.upkeepTangleWire(game); - Upkeep.upkeepKarma(game); Upkeep.upkeepOathOfDruids(game); Upkeep.upkeepOathOfGhouls(game); Upkeep.upkeepSuspend(game); @@ -171,6 +170,7 @@ public class Upkeep extends Phase { final Card c = list.get(i); if (c.hasStartOfKeyword("(Echo unpaid)")) { final Ability blankAbility = Upkeep.BlankAbility(c, c.getEchoCost()); + blankAbility.setActivatingPlayer(c.getController()); final StringBuilder sb = new StringBuilder(); sb.append("Echo for ").append(c).append("\n"); @@ -182,7 +182,7 @@ public class Upkeep extends Phase { Player controller = c.getController(); if (controller.isHuman()) { Cost cost = new Cost(c, c.getEchoCost().trim(), true); - if ( !GameActionUtil.payCostDuringAbilityResolve(controller, blankAbility, cost, null, game) ) + if ( !GameActionUtil.payCostDuringAbilityResolve(blankAbility, cost, null, game) ) game.getAction().sacrifice(c, null);; } else { // computer @@ -285,7 +285,7 @@ public class Upkeep extends Phase { if (c.getName().equals("Cosmic Horror")) { controller.addDamage(7, c); } - game.getAction().destroy(c); + game.getAction().destroy(c, this); } } @@ -324,7 +324,7 @@ public class Upkeep extends Phase { @Override public void resolve() { if (controller.isHuman()) { - if ( !GameActionUtil.payCostDuringAbilityResolve(controller, blankAbility, blankAbility.getPayCosts(), this, game)) + if ( !GameActionUtil.payCostDuringAbilityResolve(blankAbility, blankAbility.getPayCosts(), this, game)) game.getAction().sacrifice(c, null); } else { // computer if (ComputerUtilCost.shouldPayCost(controller, c, upkeepCost) && ComputerUtilCost.canPayCost(blankAbility, controller)) { @@ -444,20 +444,20 @@ public class Upkeep extends Phase { chooseArt.setMessage(abyss.getName() + " - Select one nonartifact creature to destroy"); FThreads.setInputAndWait(chooseArt); // Input if (!chooseArt.hasCancelled()) { - game.getAction().destroyNoRegeneration(chooseArt.getSelected().get(0)); + game.getAction().destroyNoRegeneration(chooseArt.getSelected().get(0), this); } } else { // computer final List indestruct = CardLists.getKeyword(targets, "Indestructible"); if (indestruct.size() > 0) { - game.getAction().destroyNoRegeneration(indestruct.get(0)); + game.getAction().destroyNoRegeneration(indestruct.get(0), this); } else if (targets.size() > 0) { final Card target = ComputerUtilCard.getWorstCreatureAI(targets); if (null == target) { // must be nothing valid to destroy } else { - game.getAction().destroyNoRegeneration(target); + game.getAction().destroyNoRegeneration(target, this); } } } @@ -504,11 +504,11 @@ public class Upkeep extends Phase { inp.setMessage("Select creature with power: " + power + " to sacrifice."); FThreads.setInputAndWait(inp); if(!inp.hasCancelled()) - game.getAction().destroyNoRegeneration(inp.getSelected().get(0)); + game.getAction().destroyNoRegeneration(inp.getSelected().get(0), this); } else { // computer final Card compyTarget = this.getCompyCardToDestroy(creatures); - game.getAction().destroyNoRegeneration(compyTarget); + game.getAction().destroyNoRegeneration(compyTarget, this); } } } // resolve @@ -874,46 +874,6 @@ public class Upkeep extends Phase { } } // Oath of Ghouls - /** - *

- * upkeepKarma. - *

- */ - private static void upkeepKarma(final GameState game) { - final Player player = game.getPhaseHandler().getPlayerTurn(); - final List karmas = - CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Karma")); - final List swamps = CardLists.getType(player.getCardsIn(ZoneType.Battlefield), "Swamp"); - - // determine how much damage to deal the current player - final int damage = swamps.size(); - - // if there are 1 or more Karmas on the - // battlefield have each of them deal damage. - if (0 < karmas.size()) { - for (final Card karma : karmas) { - final Ability ability = new Ability(karma, ManaCost.ZERO) { - @Override - public void resolve() { - if (damage > 0) { - player.addDamage(damage, karma); - } - } - }; // Ability - if (damage > 0) { - - final StringBuilder sb = new StringBuilder(); - sb.append("Karma deals ").append(damage).append(" damage to ").append(player); - ability.setStackDescription(sb.toString()); - ability.setDescription(sb.toString()); - ability.setActivatingPlayer(karma.getController()); - - game.getStack().addSimultaneousStackEntry(ability); - } - } - } // if - } // upkeepKarma() - /** *

* upkeepPowerSurge. diff --git a/src/main/java/forge/game/player/HumanPlayer.java b/src/main/java/forge/game/player/HumanPlayer.java index 26722254817..11a0651574b 100644 --- a/src/main/java/forge/game/player/HumanPlayer.java +++ b/src/main/java/forge/game/player/HumanPlayer.java @@ -17,13 +17,26 @@ */ package forge.game.player; +import java.util.ArrayList; import java.util.List; import forge.Card; import forge.FThreads; +import forge.card.ability.AbilityUtils; +import forge.card.ability.ApiType; +import forge.card.ability.effects.CharmEffect; +import forge.card.cost.Cost; +import forge.card.cost.CostPayment; +import forge.card.mana.ManaCostBeingPaid; +import forge.card.mana.ManaCostShard; +import forge.card.spellability.Ability; +import forge.card.spellability.HumanPlaySpellAbility; import forge.card.spellability.SpellAbility; +import forge.card.spellability.Target; +import forge.control.input.InputPayManaSimple; import forge.control.input.InputSelectCards; import forge.control.input.InputSelectCardsFromList; +import forge.game.GameActionUtil; import forge.game.GameState; import forge.game.zone.ZoneType; @@ -57,6 +70,19 @@ public class HumanPlayer extends Player { c.getController().discard(c, sa); } // input_discardNumUnless + /** + * TODO: Write javadoc for this method. + * @param card + * @param ab + */ + public void playSpellAbility(Card c, SpellAbility ab) { + if (ab == Ability.PLAY_LAND_SURROGATE) + this.playLand(c); + else { + this.playSpellAbility(ab); + } + game.getPhaseHandler().setPriority(this); + } @Override public PlayerType getType() { @@ -65,5 +91,191 @@ public class HumanPlayer extends Player { public PlayerController getController() { return controller; } + + /** + *

+ * playSpellAbility. + *

+ * + * @param sa + * a {@link forge.card.spellability.SpellAbility} object. + */ + public final void playSpellAbility(SpellAbility sa) { + FThreads.checkEDT("Player.playSpellAbility", false); + sa.setActivatingPlayer(this); + + final Card source = sa.getSourceCard(); + + source.setSplitStateToPlayAbility(sa); + + if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { + CharmEffect.makeChoices(sa); + } + + sa = chooseOptionalAdditionalCosts(sa); + + if (sa == null) { + return; + } + + // Need to check PayCosts, and Ability + All SubAbilities for Target + boolean newAbility = sa.getPayCosts() != null; + SpellAbility ability = sa; + while ((ability != null) && !newAbility) { + final Target tgt = ability.getTarget(); + + newAbility |= tgt != null; + ability = ability.getSubAbility(); + } + + // System.out.println("Playing:" + sa.getDescription() + " of " + sa.getSourceCard() + " new = " + newAbility); + if (newAbility) { + CostPayment payment = null; + if (sa.getPayCosts() == null) { + payment = new CostPayment(new Cost(sa.getSourceCard(), "0", sa.isAbility()), sa); + } else { + payment = new CostPayment(sa.getPayCosts(), sa); + } + + final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment); + req.fillRequirements(false, false, false); + } else { + ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost()); + if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) { + manaCost = new ManaCostBeingPaid("0"); + } else { + manaCost = new ManaCostBeingPaid(sa.getManaCost()); + manaCost.applySpellCostChange(sa); + } + + if (!manaCost.isPaid()) { + FThreads.setInputAndWait(new InputPayManaSimple(game, sa, manaCost)); + } + + + if (manaCost.isPaid()) { + if (sa.isSpell() && !source.isCopiedSpell()) { + sa.setSourceCard(game.getAction().moveToStack(source)); + } + + game.getStack().add(sa); + } + } + } + + /** + *

+ * playSpellAbility_NoStack. + *

+ * + * @param sa + * a {@link forge.card.spellability.SpellAbility} object. + * @param skipTargeting + * a boolean. + */ + public final void playSpellAbilityNoStack( final SpellAbility sa) { + playSpellAbilityNoStack(sa, false); + } + public final void playSpellAbilityNoStack(final SpellAbility sa, boolean useOldTargets) { + sa.setActivatingPlayer(this); + + if (sa.getPayCosts() != null) { + final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); + + if (!sa.isTrigger()) { + payment.changeCost(); + } + + final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment); + + req.fillRequirements(useOldTargets, false, true); + } else { + ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost()); + if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) { + manaCost = new ManaCostBeingPaid("0"); + } else { + manaCost = new ManaCostBeingPaid(sa.getManaCost()); + manaCost.applySpellCostChange(sa); + } + + if( !manaCost.isPaid() ) { + FThreads.setInputAndWait(new InputPayManaSimple(game, sa, manaCost)); + } + + if (manaCost.isPaid()) { + AbilityUtils.resolve(sa, false); + } + + } + } + + /** + * choose optional additional costs. For HUMAN only + * @param activator + * + * @param original + * the original sa + * @return an ArrayList. + */ + public SpellAbility chooseOptionalAdditionalCosts(final SpellAbility original) { + //final HashMap map = new HashMap(); + final ArrayList abilities = GameActionUtil.getOptionalAdditionalCosts(original); + + if (!original.isSpell()) { + return original; + } + + return getController().getAbilityToPlay(abilities); + } + + + public final void playCardWithoutManaCost(final Card c) { + final List choices = c.getBasicSpells(); + // TODO add Buyback, Kicker, ... , spells here + + SpellAbility sa = controller.getAbilityToPlay(choices); + + if (sa == null) { + return; + } + + sa.setActivatingPlayer(this); + this.playSpellAbilityWithoutPayingManaCost(sa); + } + + /** + *

+ * playSpellAbilityForFree. + *

+ * + * @param sa + * a {@link forge.card.spellability.SpellAbility} object. + */ + public final void playSpellAbilityWithoutPayingManaCost(final SpellAbility sa) { + FThreads.checkEDT("GameActionPlay.playSpellAbilityWithoutPayingManaCost", false); + final Card source = sa.getSourceCard(); + + source.setSplitStateToPlayAbility(sa); + + if (sa.getPayCosts() != null) { + if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { + CharmEffect.makeChoices(sa); + } + final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); + + final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment); + req.fillRequirements(false, true, false); + } else { + if (sa.isSpell()) { + final Card c = sa.getSourceCard(); + if (!c.isCopiedSpell()) { + sa.setSourceCard(game.getAction().moveToStack(c)); + } + } + boolean x = sa.getSourceCard().getManaCost().getShardCount(ManaCostShard.X) > 0; + + game.getStack().add(sa, x); + } + } } // end HumanPlayer class diff --git a/src/main/java/forge/game/player/Player.java b/src/main/java/forge/game/player/Player.java index d1a9de973bb..29b0cbef43b 100644 --- a/src/main/java/forge/game/player/Player.java +++ b/src/main/java/forge/game/player/Player.java @@ -641,7 +641,7 @@ public abstract class Player extends GameEntity implements Comparable { } } - this.addAssignedDamage(damageToDo, source); + this.assignedDamage.put(source, damageToDo); GameActionUtil.executeDamageDealingEffects(source, damageToDo); GameActionUtil.executeDamageToPlayerEffects(this, source, damageToDo); @@ -916,20 +916,6 @@ public abstract class Player extends GameEntity implements Comparable { this.assignedDamage.clear(); } - /** - *

- * addAssignedDamage. - *

- * - * @param n - * a int. - * @param source - * a {@link forge.Card} object. - */ - public final void addAssignedDamage(final int n, final Card source) { - this.assignedDamage.put(source, n); - } - /** *

* Getter for the field assignedDamage. @@ -1384,7 +1370,7 @@ public abstract class Player extends GameEntity implements Comparable { this.numDrawnThisDrawStep++; // Miracle draws - if (this.numDrawnThisTurn == 1 && game.getPhaseHandler().getTurn() != 0) { + if (this.numDrawnThisTurn == 1 && game.getPhaseHandler().getTurn() != 1) { drawMiracle(c); } @@ -2904,20 +2890,6 @@ public abstract class Player extends GameEntity implements Comparable { this.startingHandSize = shs; } - /** - * TODO: Write javadoc for this method. - * @param card - * @param ab - */ - public void playSpellAbility(Card c, SpellAbility ab) { - if (ab == Ability.PLAY_LAND_SURROGATE) - this.playLand(c); - else { - game.getActionPlay().playSpellAbility(ab, this); - } - game.getPhaseHandler().setPriority(this); - } - /** * * Takes the top plane of the planar deck and put it face up in the command zone. diff --git a/src/main/java/forge/game/player/PlayerController.java b/src/main/java/forge/game/player/PlayerController.java index 728d000af4a..993699bda94 100644 --- a/src/main/java/forge/game/player/PlayerController.java +++ b/src/main/java/forge/game/player/PlayerController.java @@ -29,13 +29,15 @@ public abstract class PlayerController { private PhaseType autoPassUntil = null; - public PlayerController(GameState game0) { game = game0; + } + public abstract Input getDefaultInput(); public abstract Input getBlockInput(); public abstract Input getCleanupInput(); + public abstract Input getAutoPassPriorityInput(); /** @@ -92,9 +94,8 @@ public abstract class PlayerController { public abstract Map assignCombatDamage(Card attacker, List blockers, int damageDealt, GameEntity defender); - public abstract String announceRequirements(SpellAbility ability, String announce); - - public abstract List choosePermanentsToSacrifice(List validTargets, int amount, SpellAbility sa, boolean destroy, boolean isOptional); + public abstract Integer announceRequirements(SpellAbility ability, String announce, boolean allowZero); + public abstract List choosePermanentsToSacrifice(List validTargets, String validMessage, int amount, SpellAbility sa, boolean destroy, boolean isOptional); public Card chooseSingleCardForEffect(List sourceList, SpellAbility sa, String title) { return chooseSingleCardForEffect(sourceList, sa, title, false); } public abstract Card chooseSingleCardForEffect(List sourceList, SpellAbility sa, String title, boolean isOptional); @@ -111,9 +112,10 @@ public abstract class PlayerController { public abstract boolean willPutCardOnTop(Card c); /** p = target player, validCards - possible discards, min cards to discard */ - public abstract List chooseCardsToDiscardFrom(Player p, SpellAbility sa, List validCards, int min); + public abstract List chooseCardsToDiscardFrom(Player playerDiscard, SpellAbility sa, List validCards, int min, int max); public abstract Card chooseCardToDredge(List dredgers); public abstract void playMiracle(SpellAbility miracle, Card card); public abstract void playMadness(SpellAbility madness); + public abstract List chooseCardsToDelve(int colorLessAmount, List grave); } diff --git a/src/main/java/forge/game/player/PlayerControllerAi.java b/src/main/java/forge/game/player/PlayerControllerAi.java index 242ffed7898..47806016f05 100644 --- a/src/main/java/forge/game/player/PlayerControllerAi.java +++ b/src/main/java/forge/game/player/PlayerControllerAi.java @@ -13,6 +13,7 @@ import forge.GameEntity; import forge.card.spellability.Spell; import forge.card.spellability.SpellAbility; import forge.control.input.Input; +import forge.control.input.InputAutoPassPriority; import forge.deck.Deck; import forge.game.GameState; import forge.game.GameType; @@ -36,6 +37,7 @@ public class PlayerControllerAi extends PlayerController { private Input defaultInput; private Input blockInput; private Input cleanupInput; + private Input autoPassPriorityInput; private final AiController brains; private final AIPlayer player; @@ -52,9 +54,9 @@ public class PlayerControllerAi extends PlayerController { brains = new AiController(p, game); defaultInput = new AiInputCommon(brains); - blockInput = new AiInputBlock(game, getPlayer()); + blockInput = new AiInputBlock(getPlayer()); cleanupInput = getDefaultInput(); - + autoPassPriorityInput = new InputAutoPassPriority(getPlayer()); } /** @@ -66,7 +68,7 @@ public class PlayerControllerAi extends PlayerController { } else if (abilities.size() == 1) { return abilities.get(0); } else { - return GuiChoose.oneOrNone("Choose", abilities); // some day network interaction will be here + return GuiChoose.oneOrNone("Choose ability for AI to play", abilities); // some day network interaction will be here } } @@ -75,6 +77,11 @@ public class PlayerControllerAi extends PlayerController { return blockInput; } + @Override + public Input getAutoPassPriorityInput() { + return autoPassPriorityInput; + } + /** * @return the cleanupInput */ @@ -142,13 +149,13 @@ public class PlayerControllerAi extends PlayerController { } @Override - public String announceRequirements(SpellAbility ability, String announce) { + public Integer announceRequirements(SpellAbility ability, String announce, boolean allowZero) { // For now, these "announcements" are made within the AI classes of the appropriate SA effects - return null; + return null; // return incorrect value to indicate that } @Override - public List choosePermanentsToSacrifice(List validTargets, int amount, SpellAbility sa, boolean destroy, boolean isOptional) { + public List choosePermanentsToSacrifice(List validTargets, String validMessage, int amount, SpellAbility sa, boolean destroy, boolean isOptional) { return ComputerUtil.choosePermanentsToSacrifice(player, validTargets, amount, sa, destroy, isOptional); } @@ -213,12 +220,12 @@ public class PlayerControllerAi extends PlayerController { } @Override - public List chooseCardsToDiscardFrom(Player p, SpellAbility sa, List validCards, int min) { + public List chooseCardsToDiscardFrom(Player p, SpellAbility sa, List validCards, int min, int max) { boolean isTargetFriendly = !p.isOpponentOf(getPlayer()); return isTargetFriendly - ? ComputerUtil.getCardsToDiscardFromFriend(player, p, sa, validCards, min) - : ComputerUtil.getCardsToDiscardFromOpponent(player, p, sa, validCards, min); + ? ComputerUtil.getCardsToDiscardFromFriend(player, p, sa, validCards, min, max) + : ComputerUtil.getCardsToDiscardFromOpponent(player, p, sa, validCards, min, max); } @Override @@ -237,4 +244,12 @@ public class PlayerControllerAi extends PlayerController { getAi().chooseAndPlaySa(false, false, madness); } + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseCardsToDelve(int, java.util.List) + */ + @Override + public List chooseCardsToDelve(int colorlessCost, List grave) { + return getAi().chooseCardsToDelve(colorlessCost, grave); + } + } diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index ba777e5b0db..39081958b49 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -16,6 +16,7 @@ import forge.FThreads; import forge.GameEntity; import forge.card.spellability.SpellAbility; import forge.control.input.Input; +import forge.control.input.InputAutoPassPriority; import forge.control.input.InputBlock; import forge.control.input.InputCleanup; import forge.control.input.InputPassPriority; @@ -45,8 +46,9 @@ public class PlayerControllerHuman extends PlayerController { private final Input defaultInput; private final Input blockInput; private final Input cleanupInput; + private final Input autoPassPriorityInput; private final HumanPlayer player; - + public final Input getDefaultInput() { return defaultInput; } @@ -55,9 +57,10 @@ public class PlayerControllerHuman extends PlayerController { super(game0); player = p; - defaultInput = new InputPassPriority(); + defaultInput = new InputPassPriority(player); blockInput = new InputBlock(getPlayer()); - cleanupInput = new InputCleanup(game); + cleanupInput = new InputCleanup(getPlayer()); + autoPassPriorityInput = new InputAutoPassPriority(getPlayer()); } @Override @@ -83,6 +86,11 @@ public class PlayerControllerHuman extends PlayerController { return blockInput; } + @Override + public Input getAutoPassPriorityInput() { + return autoPassPriorityInput; + } + /** * @return the cleanupInput */ @@ -96,7 +104,7 @@ public class PlayerControllerHuman extends PlayerController { */ public void playFromSuspend(Card c) { c.setSuspendCast(true); - game.getActionPlay().playCardWithoutManaCost(c, c.getOwner()); + player.playCardWithoutManaCost(c); } /* (non-Javadoc) @@ -114,7 +122,7 @@ public class PlayerControllerHuman extends PlayerController { boolean result = GuiDialog.confirm(cascadedCard, question.toString()); if ( result ) - game.getActionPlay().playCardWithoutManaCost(cascadedCard, getPlayer()); + player.playCardWithoutManaCost(cascadedCard); return result; } @@ -123,7 +131,7 @@ public class PlayerControllerHuman extends PlayerController { */ @Override public void playSpellAbilityForFree(SpellAbility copySA) { - game.getActionPlay().playSpellAbilityWithoutPayingManaCost(copySA); + player.playSpellAbilityWithoutPayingManaCost(copySA); } /** @@ -203,26 +211,43 @@ public class PlayerControllerHuman extends PlayerController { * @see forge.game.player.PlayerController#announceRequirements(java.lang.String) */ @Override - public String announceRequirements(SpellAbility ability, String announce) { - StringBuilder sb = new StringBuilder(ability.getSourceCard().getName()); - sb.append(" - How much will you announce for "); - sb.append(announce); - sb.append("?"); - return JOptionPane.showInputDialog(sb.toString()); + public Integer announceRequirements(SpellAbility ability, String announce, boolean canChooseZero) { + List options = new ArrayList(); + for(int i = canChooseZero ? 0 : 1; i < 10; i++) + options.add(Integer.valueOf(i)); + options.add("Other amount"); + + + Object chosen = GuiChoose.oneOrNone("Choose " + announce + " for " + ability.getSourceCard().getName(), options); + if (chosen instanceof Integer || chosen == null) + return (Integer)chosen; + + String message = String.format("How much will you announce for %s?%s", announce, canChooseZero ? "" : " (X cannot be 0)"); + while(true){ + String str = JOptionPane.showInputDialog(null, message, ability.getSourceCard().getName(), JOptionPane.QUESTION_MESSAGE); + if (null == str) return null; // that is 'cancel' + + if(StringUtils.isNumeric(str)) { + Integer val = Integer.valueOf(str); + if (val == 0 && canChooseZero || val > 0) + return val; + } + JOptionPane.showMessageDialog(null, "You have to enter a valid number", "Announce value", JOptionPane.WARNING_MESSAGE); + } } /* (non-Javadoc) * @see forge.game.player.PlayerController#choosePermanentsToSacrifice(java.util.List, int, forge.card.spellability.SpellAbility, boolean, boolean) */ @Override - public List choosePermanentsToSacrifice(List validTargets, int amount, SpellAbility sa, boolean destroy, boolean isOptional) { + public List choosePermanentsToSacrifice(List validTargets, String validMessage, int amount, SpellAbility sa, boolean destroy, boolean isOptional) { int max = Math.min(amount, validTargets.size()); if (max == 0) return new ArrayList(); InputSelectCards inp = new InputSelectCardsFromList(isOptional ? 0 : amount, max, validTargets); // TODO: Either compose a message here, or pass it as parameter from caller. - inp.setMessage("Select %d card(s) to sacrifice"); + inp.setMessage("Select %d " + validMessage + "(s) to sacrifice"); FThreads.setInputAndWait(inp); if( inp.hasCancelled() ) @@ -316,14 +341,13 @@ public class PlayerControllerHuman extends PlayerController { } @Override - public List chooseCardsToDiscardFrom(Player p, SpellAbility sa, List valid, int minDiscard) { + public List chooseCardsToDiscardFrom(Player p, SpellAbility sa, List valid, int min, int max) { if ( p != getPlayer() ) { - int cntToKeepInHand = minDiscard == 0 ? -1 : valid.size() - minDiscard; + int cntToKeepInHand = min == 0 ? -1 : valid.size() - min; return GuiChoose.order("Choose cards to Discard", "Discarded", cntToKeepInHand, valid, null, null); } - int max = minDiscard == 0 ? Integer.MAX_VALUE : minDiscard; - InputSelectCards inp = new InputSelectCardsFromList(minDiscard, max, valid); + InputSelectCards inp = new InputSelectCardsFromList(min, max, valid); inp.setMessage("Discard %d cards"); FThreads.setInputAndWait(inp); return inp.getSelected(); @@ -340,14 +364,41 @@ public class PlayerControllerHuman extends PlayerController { @Override public void playMiracle(SpellAbility miracle, Card card) { if (GuiDialog.confirm(card, card + " - Drawn. Play for Miracle Cost?")) { - game.getActionPlay().playSpellAbility(miracle, getPlayer()); + player.playSpellAbility(miracle); } } @Override public void playMadness(SpellAbility madness) { if (GuiDialog.confirm(madness.getSourceCard(), madness.getSourceCard() + " - Discarded. Pay Madness Cost?")) { - game.getActionPlay().playSpellAbility(madness, getPlayer()); + player.playSpellAbility(madness); } } + + @Override + public List chooseCardsToDelve(int colorLessAmount, List grave) { + List toExile = new ArrayList(); + int cardsInGrave = grave.size(); + final Integer[] cntChoice = new Integer[cardsInGrave + 1]; + for (int i = 0; i <= cardsInGrave; i++) { + cntChoice[i] = Integer.valueOf(i); + } + + final Integer chosenAmount = GuiChoose.one("Exile how many cards?", cntChoice); + System.out.println("Delve for " + chosenAmount); + + for (int i = 0; i < chosenAmount; i++) { + final Card nowChosen = GuiChoose.oneOrNone("Exile which card?", grave); + + if (nowChosen == null) { + // User canceled,abort delving. + toExile.clear(); + break; + } + + grave.remove(nowChosen); + toExile.add(nowChosen); + } + return toExile; + } } diff --git a/src/main/java/forge/game/player/PlayerOutcome.java b/src/main/java/forge/game/player/PlayerOutcome.java index 05ce59e09f0..73a6926c2b1 100644 --- a/src/main/java/forge/game/player/PlayerOutcome.java +++ b/src/main/java/forge/game/player/PlayerOutcome.java @@ -72,6 +72,7 @@ public class PlayerOutcome { case Poisoned: return "lost because of obtaining 10 poison counters"; case OpponentWon: return "lost because an opponent has won by spell '" + loseConditionSpell + "'"; case SpellEffect: return "lost due to effect of spell '" + loseConditionSpell + "'"; + case EdhGeneralDamage: return "lost due to accumulation of 21 damage from generals"; } return "lost for unknown reason (this is a bug)"; } diff --git a/src/main/java/forge/game/zone/IZone.java b/src/main/java/forge/game/zone/IZone.java index 4f5252416fd..c537e03fa56 100644 --- a/src/main/java/forge/game/zone/IZone.java +++ b/src/main/java/forge/game/zone/IZone.java @@ -121,16 +121,6 @@ interface IZone { * @return true, if successful */ boolean contains(Card c); - - /** - * gets the positon of a specific card in this zone, null if it doesn't exist. - * - * @param c - * the card - * @return position - */ - Integer getPosition(Card c); - /** * isEmpty returns true if given zone contains no cards. * diff --git a/src/main/java/forge/game/zone/MagicStack.java b/src/main/java/forge/game/zone/MagicStack.java index 037c94a5d4c..b469895b9be 100644 --- a/src/main/java/forge/game/zone/MagicStack.java +++ b/src/main/java/forge/game/zone/MagicStack.java @@ -42,14 +42,11 @@ import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.Target; import forge.card.spellability.TargetChoices; -import forge.card.spellability.TargetSelection; import forge.card.trigger.Trigger; import forge.card.trigger.TriggerType; import forge.control.input.InputPayManaExecuteCommands; -import forge.control.input.InputPayManaX; import forge.control.input.InputSelectCards; import forge.control.input.InputSelectCardsFromList; -import forge.control.input.InputSynchronized; import forge.game.GameActionUtil; import forge.game.GameState; import forge.game.ai.ComputerUtil; @@ -58,6 +55,7 @@ import forge.game.ai.ComputerUtilCost; import forge.game.event.SpellResolvedEvent; import forge.game.phase.PhaseType; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.gui.GuiChoose; import forge.gui.framework.EDocID; @@ -203,7 +201,7 @@ public class MagicStack extends MyObservable { this.add(sa); } // Add all waiting triggers onto the stack - checkState |= Singletons.getModel().getGame().getTriggerHandler().runWaitingTriggers(false); + checkState |= Singletons.getModel().getGame().getTriggerHandler().runWaitingTriggers(); if (checkState) { this.chooseOrderOfSimultaneousStackEntryAll(); game.getAction().checkStateEffects(); @@ -381,76 +379,37 @@ public class MagicStack extends MyObservable { } if (sp.getSourceCard().isCopiedSpell()) { this.push(sp); - } else if (!sp.isMultiKicker() && !sp.isReplicate() && !sp.isXCost()) { + } else if (!sp.isMultiKicker() && !sp.isReplicate()) { this.push(sp); - } else if ((sp.getPayCosts() != null) && !sp.isMultiKicker() && !sp.isReplicate()) { - this.push(sp); - } else if (sp.isXCost()) { - // TODO: convert any X costs to use abCost so it happens earlier - final SpellAbility sa = sp; - final int xCost = sa.getXManaCost(); - Player player = sp.getSourceCard().getController(); - if (player.isHuman()) { - InputSynchronized inp = new InputPayManaX(game, sa, xCost, true); - FThreads.setInputAndWait(inp); - MagicStack.this.push(sa); - } else { - // computer - final int neededDamage = CardFactoryUtil.getNeededXDamage(sa); - final Ability ability = new Ability(sp.getSourceCard(), ManaCost.get(xCost)) { - @Override - public void resolve() { - final Card crd = this.getSourceCard(); - crd.addXManaCostPaid(1); - } - }; - - while (ComputerUtilCost.canPayCost(ability, player) && (neededDamage != sa.getSourceCard().getXManaCostPaid())) { - ComputerUtil.playNoStack((AIPlayer)player, ability, game); - } - this.push(sa); - } } else if (sp.isMultiKicker()) { - // TODO: convert multikicker support in abCost so this doesn't - // happen here - // both X and multi is not supported yet - final SpellAbility sa = sp; - final Ability abilityIncreaseMultikicker = new Ability(sp.getSourceCard(), sp.getMultiKickerManaCost()) { - @Override - public void resolve() { - this.getSourceCard().addMultiKickerMagnitude(1); - } - }; - final Player activating = sp.getActivatingPlayer(); if (activating.isHuman()) { - sa.getSourceCard().addMultiKickerMagnitude(-1); - final Runnable paidCommand = new Runnable() { - @Override - public void run() { - abilityIncreaseMultikicker.resolve(); - int mkMagnitude = sa.getSourceCard().getMultiKickerMagnitude(); - String prompt = String.format("Multikicker for %s\r\nTimes Kicked: %d\r\n", sa.getSourceCard(), mkMagnitude ); - InputPayManaExecuteCommands toSet = new InputPayManaExecuteCommands(activating, prompt, sp.getMultiKickerManaCost()); - FThreads.setInputAndWait(toSet); - if ( toSet.isPaid() ) { - this.run(); - } else - MagicStack.this.push(sa); - } - }; - paidCommand.run(); + while(true) { + int mkMagnitude = sa.getSourceCard().getMultiKickerMagnitude(); + String prompt = String.format("Multikicker for %s\r\nTimes Kicked: %d\r\n", sa.getSourceCard(), mkMagnitude ); + InputPayManaExecuteCommands toSet = new InputPayManaExecuteCommands(activating, prompt, sp.getMultiKickerManaCost()); + FThreads.setInputAndWait(toSet); + if ( !toSet.isPaid() ) + break; + + sa.getSourceCard().addMultiKickerMagnitude(1); + } } else { // computer + final Ability abilityIncreaseMultikicker = new Ability(sp.getSourceCard(), sp.getMultiKickerManaCost()) { + @Override + public void resolve() { + this.getSourceCard().addMultiKickerMagnitude(1); + } + }; while (ComputerUtilCost.canPayCost(abilityIncreaseMultikicker, activating)) { ComputerUtil.playNoStack((AIPlayer)activating, abilityIncreaseMultikicker, game); } - - this.push(sa); } + this.push(sa); } else if (sp.isReplicate()) { // TODO: convert multikicker/replicate support in abCost so this // doesn't happen here @@ -885,7 +844,7 @@ public class MagicStack extends MyObservable { } else if (o instanceof SpellAbility) { final SpellAbility tgtSA = (SpellAbility) o; - invalidTarget = !(TargetSelection.matchSpellAbility(sa, tgtSA, tgt)); + invalidTarget = !(sa.canTargetSpellAbility(tgtSA)); // TODO Remove target? if (invalidTarget) { choices.removeTarget(tgtSA); @@ -1125,7 +1084,7 @@ public class MagicStack extends MyObservable { for (int i = size - 1; i >= 0; i--) { SpellAbility next = orderedSAs.get(i); if (next.isTrigger()) { - game.getActionPlay().playSpellAbility(next, activePlayer); + ((HumanPlayer)activePlayer).playSpellAbility(next); } else { this.add(next); } diff --git a/src/main/java/forge/game/zone/Zone.java b/src/main/java/forge/game/zone/Zone.java index 9e84ec4e44d..8f8d88b0735 100644 --- a/src/main/java/forge/game/zone/Zone.java +++ b/src/main/java/forge/game/zone/Zone.java @@ -171,18 +171,8 @@ public class Zone extends MyObservable implements IZone, Observer, java.io.Seria return this.cardList.contains(c); } - /* - * (non-Javadoc) - * - * @see forge.IPlayerZone#getPosition(forge.Card) - */ - @Override - public final Integer getPosition(final Card c) { - int index = this.cardList.indexOf(c); - if (index == -1) { - return null; - } - return index; + public final int getPosition(final Card c) { + return this.cardList.indexOf(c); } /** diff --git a/src/main/java/forge/gui/GuiChoose.java b/src/main/java/forge/gui/GuiChoose.java index 714165b3d88..607c810f090 100644 --- a/src/main/java/forge/gui/GuiChoose.java +++ b/src/main/java/forge/gui/GuiChoose.java @@ -7,6 +7,8 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; import javax.swing.JDialog; import javax.swing.JFrame; @@ -16,6 +18,7 @@ import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import forge.Card; +import forge.FThreads; import forge.gui.match.CMatchUI; import forge.item.InventoryItem; @@ -102,25 +105,39 @@ public class GuiChoose { } } - ListChooser c = new ListChooser(message, min, max, choices); - final JList list = c.getJList(); - list.addListSelectionListener(new ListSelectionListener() { + Callable> showChoice = new Callable>() { @Override - public void valueChanged(final ListSelectionEvent ev) { - if (list.getSelectedValue() instanceof Card) { - CMatchUI.SINGLETON_INSTANCE.setCard((Card) list.getSelectedValue()); - - GuiUtils.clearPanelSelections(); - GuiUtils.setPanelSelection((Card) list.getSelectedValue()); - } - if (list.getSelectedValue() instanceof InventoryItem) { - CMatchUI.SINGLETON_INSTANCE.setCard((InventoryItem) list.getSelectedValue()); - } + public List call() { + ListChooser c = new ListChooser(message, min, max, choices); + final JList list = c.getJList(); + list.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(final ListSelectionEvent ev) { + if (list.getSelectedValue() instanceof Card) { + CMatchUI.SINGLETON_INSTANCE.setCard((Card) list.getSelectedValue()); + + GuiUtils.clearPanelSelections(); + GuiUtils.setPanelSelection((Card) list.getSelectedValue()); + } + if (list.getSelectedValue() instanceof InventoryItem) { + CMatchUI.SINGLETON_INSTANCE.setCard((InventoryItem) list.getSelectedValue()); + } + } + }); + c.show(); + GuiUtils.clearPanelSelections(); + return c.getSelectedValues(); } - }); - c.show(); - GuiUtils.clearPanelSelections(); - return c.getSelectedValues(); + }; + + FutureTask> future = new FutureTask>(showChoice); + FThreads.invokeInEDTAndWait(future); + try { + return future.get(); + } catch (Exception e) { // should be no exception here + e.printStackTrace(); + } + return null; } // Nothing to choose here. Code uses this to just show a card. @@ -142,39 +159,54 @@ public class GuiChoose { } - public static List order(final String title, final String top, int remainingObjects, - final List sourceChoices, List destChoices, Card referenceCard, boolean sideboardingMode) { + public static List order(final String title, final String top, final int remainingObjects, + final List sourceChoices, final List destChoices, final Card referenceCard, final boolean sideboardingMode) { // An input box for handling the order of choices. - final JFrame frame = new JFrame(); - DualListBox dual = new DualListBox(remainingObjects, sourceChoices, destChoices); - dual.setSecondColumnLabelText(top); + + Callable> callable = new Callable>() { + @Override + public List call() throws Exception { + final JFrame frame = new JFrame(); + DualListBox dual = new DualListBox(remainingObjects, sourceChoices, destChoices); + dual.setSecondColumnLabelText(top); + + frame.setLayout(new BorderLayout()); + frame.setSize(dual.getPreferredSize()); + frame.add(dual); + frame.setTitle(title); + frame.setVisible(false); + + dual.setSideboardMode(sideboardingMode); + + final JDialog dialog = new JDialog(frame, true); + dialog.setTitle(title); + dialog.setContentPane(dual); + dialog.setSize(dual.getPreferredSize()); + dialog.setLocationRelativeTo(null); + dialog.pack(); + dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + if (referenceCard != null) { + CMatchUI.SINGLETON_INSTANCE.setCard(referenceCard); + // MARKED FOR UPDATE + } + dialog.setVisible(true); + + List objects = dual.getOrderedList(); + + dialog.dispose(); + GuiUtils.clearPanelSelections(); + return objects; + } + }; - frame.setLayout(new BorderLayout()); - frame.setSize(dual.getPreferredSize()); - frame.add(dual); - frame.setTitle(title); - frame.setVisible(false); - - dual.setSideboardMode(sideboardingMode); - - final JDialog dialog = new JDialog(frame, true); - dialog.setTitle(title); - dialog.setContentPane(dual); - dialog.setSize(dual.getPreferredSize()); - dialog.setLocationRelativeTo(null); - dialog.pack(); - dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - if (referenceCard != null) { - CMatchUI.SINGLETON_INSTANCE.setCard(referenceCard); - // MARKED FOR UPDATE + FutureTask> ft = new FutureTask>(callable); + FThreads.invokeInEDTAndWait(ft); + try { + return ft.get(); + } catch (Exception e) { // we have waited enough + e.printStackTrace(); } - dialog.setVisible(true); - - List objects = dual.getOrderedList(); - - dialog.dispose(); - GuiUtils.clearPanelSelections(); - return objects; + return null; } // If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine diff --git a/src/main/java/forge/gui/GuiDisplayUtil.java b/src/main/java/forge/gui/GuiDisplayUtil.java index 543e5506373..5648033c35f 100644 --- a/src/main/java/forge/gui/GuiDisplayUtil.java +++ b/src/main/java/forge/gui/GuiDisplayUtil.java @@ -632,9 +632,10 @@ public final class GuiDisplayUtil { FThreads.invokeInNewThread(new Runnable() { @Override public void run() { + game.getAction().moveToHand(forgeCard); // this is really needed (for rollbacks at least) + // Human player is choosing targets for an ability controlled by chosen player. sa.setActivatingPlayer(p); - game.getAction().moveToHand(forgeCard); // this is really needed - game.getActionPlay().playSpellAbilityWithoutPayingManaCost(sa); + Singletons.getControl().getPlayer().playSpellAbilityWithoutPayingManaCost(sa); } }); } @@ -683,7 +684,7 @@ public final class GuiDisplayUtil { PlanarDice.roll(p, res); - Singletons.getModel().getGame().getStack().chooseOrderOfSimultaneousStackEntryAll(); + p.getGame().getStack().chooseOrderOfSimultaneousStackEntryAll(); } } // end class GuiDisplayUtil diff --git a/src/main/java/forge/gui/InputProxy.java b/src/main/java/forge/gui/InputProxy.java index 17b2e107906..590e31f3c1c 100644 --- a/src/main/java/forge/gui/InputProxy.java +++ b/src/main/java/forge/gui/InputProxy.java @@ -26,7 +26,6 @@ import forge.FThreads; import forge.control.input.Input; import forge.game.GameState; import forge.game.MatchController; -import forge.game.ai.AiInput; import forge.game.phase.PhaseHandler; import forge.game.player.Player; @@ -50,35 +49,37 @@ public class InputProxy implements Observer { @Override public final synchronized void update(final Observable observable, final Object obj) { - this.input.set(null); final GameState game = match.getCurrentGame(); final PhaseHandler ph = game.getPhaseHandler(); + //System.out.printf("%s > InputProxy.update() =>%n", FThreads.debugGetCurrThreadId()); - //System.out.print((FThreads.isEDT() ? "EDT > " : "TRD > ") + ph.debugPrintState()); if ( match.getInput().isEmpty() && ph.hasPhaseEffects()) { - //System.out.println(" handle begin phase"); - FThreads.invokeInNewThread(new Runnable() { - @Override public void run() { + Runnable rPhase = new Runnable() { + @Override + public void run() { + //System.out.printf("\t%s > handle begin phase during %s%n", FThreads.debugGetCurrThreadId(), ph.debugPrintState()); ph.handleBeginPhase(); - update(observable, obj); - } - }, true); + update(null, null); + } + }; + FThreads.invokeInNewThread(rPhase); return; } final Input nextInput = match.getInput().getActualInput(game); - //System.out.printf(" input is %s \t stack = %s%n", nextInput == null ? "null" : nextInput.getClass().getSimpleName(), match.getInput().printInputStack()); - - if (nextInput != null) { - this.input.set(nextInput); - Runnable showMessage = new Runnable() { @Override public void run() { nextInput.showMessage(); } }; + //System.out.printf("\tinput is %s during %s, \tstack = %s%n", nextInput == null ? "null" : nextInput.getClass().getSimpleName(), ph.debugPrintState(), match.getInput().printInputStack()); + + this.input.set(nextInput); + Runnable showMessage = new Runnable() { + @Override public void run() { + //System.out.printf("%s > showMessage @ %s during %s%n", FThreads.debugGetCurrThreadId(), nextInput.getClass().getSimpleName(), ph.debugPrintState()); + nextInput.showMessage(); + } + }; // if( nextInput instanceof AiInput ) // FThreads.invokeInNewThread(showMessage, true); // else - FThreads.invokeInEDT(showMessage); - } else if (!ph.isPlayerPriorityAllowed()) { - ph.getPriorityPlayer().getController().passPriority(); - } + FThreads.invokeInEDT(showMessage); } /** *

diff --git a/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java b/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java index a2683e15652..aecb26d2dc4 100644 --- a/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java +++ b/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java @@ -68,6 +68,7 @@ public final class CEditorConstructed extends ACEditorBase { private final ItemPoolView avatarPool; private final ItemPoolView planePool; private final ItemPoolView schemePool; + private final ItemPoolView commanderPool; //=========== Constructor /** @@ -89,7 +90,8 @@ public final class CEditorConstructed extends ACEditorBase { avatarPool = ItemPool.createFrom(CardDb.variants().getAllCards(Predicates.compose(CardRulesPredicates.Presets.IS_VANGUARD, CardPrinted.FN_GET_RULES)),CardPrinted.class); planePool = ItemPool.createFrom(CardDb.variants().getAllCards(Predicates.compose(CardRulesPredicates.Presets.IS_PLANE_OR_PHENOMENON, CardPrinted.FN_GET_RULES)),CardPrinted.class); schemePool = ItemPool.createFrom(CardDb.variants().getAllCards(Predicates.compose(CardRulesPredicates.Presets.IS_SCHEME, CardPrinted.FN_GET_RULES)),CardPrinted.class); - + commanderPool = ItemPool.createFrom(CardDb.instance().getAllCards(Predicates.compose(Predicates.and(CardRulesPredicates.Presets.IS_CREATURE,CardRulesPredicates.Presets.IS_LEGENDARY), CardPrinted.FN_GET_RULES)),CardPrinted.class); + boolean wantUnique = SEditorIO.getPref(EditorPreference.display_unique_only); final EditorTableView tblCatalog = new EditorTableView(wantUnique, CardPrinted.class); @@ -203,7 +205,6 @@ public final class CEditorConstructed extends ACEditorBase { /** * Switch between the main deck and the sideboard editor. */ - @SuppressWarnings("incomplete-switch") public void cycleEditorMode() { int curindex = allSections.indexOf(sectionMode); @@ -276,6 +277,15 @@ public final class CEditorConstructed extends ACEditorBase { title = "Scheme"; tabtext = "Card Catalog"; break; + case Commander: + lstCatalogCols.remove(SColumnUtil.getColumn(ColumnName.CAT_QUANTITY)); + this.getTableCatalog().setAvailableColumns(lstCatalogCols); + this.getTableCatalog().setDeck(commanderPool,true); + this.getTableDeck().setDeck(this.controller.getModel().getOrCreate(DeckSection.Commander)); + showOptions = false; + title = "Commander"; + tabtext = "Card Catalog"; + break; } VCardCatalog.SINGLETON_INSTANCE.getTabLabel().setText(tabtext); diff --git a/src/main/java/forge/gui/match/CMatchUI.java b/src/main/java/forge/gui/match/CMatchUI.java index 6256c649b87..618f34c5897 100644 --- a/src/main/java/forge/gui/match/CMatchUI.java +++ b/src/main/java/forge/gui/match/CMatchUI.java @@ -29,6 +29,7 @@ import forge.GameEntity; import forge.ImageCache; import forge.Singletons; import forge.game.phase.PhaseType; +import forge.game.player.HumanPlayer; import forge.game.player.LobbyPlayer; import forge.game.player.Player; import forge.gui.framework.EDocID; @@ -77,7 +78,7 @@ public enum CMatchUI { * @param numFieldPanels int * @param numHandPanels int */ - public void initMatch(final List players, Player localPlayer) { + public void initMatch(final List players, HumanPlayer localPlayer) { // TODO fix for use with multiplayer final String[] indices = Singletons.getModel().getPreferences().getPref(FPref.UI_AVATARS).split(","); @@ -236,8 +237,12 @@ public enum CMatchUI { } public void setCard(final Card c) { + setCard(c, false); + } + + public void setCard(final Card c, final boolean showFlipped ) { CDetail.SINGLETON_INSTANCE.showCard(c); - CPicture.SINGLETON_INSTANCE.showCard(c); + CPicture.SINGLETON_INSTANCE.showCard(c, showFlipped); } public void setCard(final InventoryItem c) { diff --git a/src/main/java/forge/gui/match/controllers/CDetail.java b/src/main/java/forge/gui/match/controllers/CDetail.java index f34b628af02..d27cbcc0d04 100644 --- a/src/main/java/forge/gui/match/controllers/CDetail.java +++ b/src/main/java/forge/gui/match/controllers/CDetail.java @@ -22,6 +22,7 @@ import java.awt.event.MouseEvent; import forge.Card; import forge.Command; +import forge.Singletons; import forge.gui.framework.ICDoc; import forge.gui.match.views.VDetail; import forge.item.IPaperCard; @@ -44,7 +45,7 @@ public enum CDetail implements ICDoc { * @param c   Card object */ public void showCard(final Card c) { - view.getLblFlipcard().setVisible(c != null && (c.isDoubleFaced() || c.isFlipCard())); + view.getLblFlipcard().setVisible(c != null && (c.isDoubleFaced() || c.isFlipCard() || c.isFaceDown() && c.canBeShownTo(Singletons.getControl().getPlayer()))); view.getPnlDetail().setCard(c); view.getParentCell().repaintSelf(); } diff --git a/src/main/java/forge/gui/match/controllers/CPicture.java b/src/main/java/forge/gui/match/controllers/CPicture.java index 8dae6eb2f74..6444324dddb 100644 --- a/src/main/java/forge/gui/match/controllers/CPicture.java +++ b/src/main/java/forge/gui/match/controllers/CPicture.java @@ -22,6 +22,7 @@ import java.awt.event.MouseEvent; import forge.Card; import forge.CardCharacteristicName; import forge.Command; +import forge.Singletons; import forge.gui.framework.ICDoc; import forge.gui.match.views.VPicture; import forge.item.IPaperCard; @@ -38,6 +39,7 @@ public enum CPicture implements ICDoc { private Card currentCard = null; private boolean flipped = false; private boolean canFlip = false; + private boolean cameFaceDown = false; /** * Shows card details and/or picture in sidebar cardview tabber. @@ -45,18 +47,22 @@ public enum CPicture implements ICDoc { * @param c *   Card object */ - public void showCard(final Card c) { - canFlip = c != null && (c.isDoubleFaced() || c.isFlipCard()); + public void showCard(final Card c, boolean showFlipped) { + cameFaceDown = c.isFaceDown() && c.canBeShownTo(Singletons.getControl().getPlayer()); + canFlip = c != null && (c.isDoubleFaced() || c.isFlipCard() || cameFaceDown); currentCard = c; flipped = canFlip && (c.getCurState() == CardCharacteristicName.Transformed || - c.getCurState() == CardCharacteristicName.Flipped); + c.getCurState() == CardCharacteristicName.Flipped || c.getCurState() == CardCharacteristicName.FaceDown); VPicture.SINGLETON_INSTANCE.getLblFlipcard().setVisible(canFlip); VPicture.SINGLETON_INSTANCE.getPnlPicture().setCard(c); + + if ( showFlipped && canFlip ) + flipCard(); } public void showCard(final InventoryItem item) { if (item instanceof IPaperCard) { - showCard(((IPaperCard)item).getMatchingForgeCard()); + showCard(((IPaperCard)item).getMatchingForgeCard(), false); return; } @@ -101,6 +107,8 @@ public enum CPicture implements ICDoc { newState = CardCharacteristicName.Transformed; } else if (currentCard.isFlipCard()) { newState = CardCharacteristicName.Flipped; + } else if ( cameFaceDown ) { + newState = CardCharacteristicName.FaceDown; } else { // if this is hit, then then showCard has been modified to handle additional types, but // this function is missing an else if statement above diff --git a/src/main/java/forge/gui/match/nonsingleton/CCommand.java b/src/main/java/forge/gui/match/nonsingleton/CCommand.java index 99546328178..0d5559ecf49 100644 --- a/src/main/java/forge/gui/match/nonsingleton/CCommand.java +++ b/src/main/java/forge/gui/match/nonsingleton/CCommand.java @@ -17,6 +17,7 @@ */ package forge.gui.match.nonsingleton; +import java.awt.Event; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; @@ -99,9 +100,10 @@ public class CCommand implements ICDoc { /** */ private void cardoverAction(MouseEvent e) { + boolean isShiftDown = (e.getModifiers() & Event.SHIFT_MASK) != 0; final Card c = CCommand.this.view.getTabletop().getHoveredCard(e); if (c != null) { - CMatchUI.SINGLETON_INSTANCE.setCard(c); + CMatchUI.SINGLETON_INSTANCE.setCard(c, isShiftDown); } } diff --git a/src/main/java/forge/gui/match/nonsingleton/CField.java b/src/main/java/forge/gui/match/nonsingleton/CField.java index 6b3a0561f9e..46411578830 100644 --- a/src/main/java/forge/gui/match/nonsingleton/CField.java +++ b/src/main/java/forge/gui/match/nonsingleton/CField.java @@ -17,6 +17,7 @@ */ package forge.gui.match.nonsingleton; +import java.awt.Event; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -42,7 +43,9 @@ import forge.control.input.Input; import forge.control.input.InputAttack; import forge.control.input.InputBlock; import forge.control.input.InputPayManaBase; +import forge.game.GameState; import forge.game.phase.CombatUtil; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.PlayerZone; import forge.game.zone.ZoneType; @@ -62,7 +65,7 @@ public class CField implements ICDoc { // The one who owns cards on this side of table private final Player player; // Tho one who looks at screen and 'performs actions' - private final Player playerViewer; + private final HumanPlayer playerViewer; private final VField view; private boolean initializedAlready = false; @@ -154,9 +157,9 @@ public class CField implements ICDoc { * @param v0   {@link forge.gui.match.nonsingleton.VField} * @param playerViewer */ - public CField(final Player p0, final VField v0, Player playerViewer) { + public CField(final Player p0, final VField v0, HumanPlayer playerViewer) { this.player = p0; - this.playerViewer = playerViewer;; + this.playerViewer = playerViewer; this.view = v0; } @@ -340,13 +343,11 @@ public class CField implements ICDoc { @Override protected void doAction(final Card c) { - if ( CField.this.player != CField.this.playerViewer ) - return; - - final SpellAbility ab = player.getController().getAbilityToPlay(player.getGame().getAbilitesOfCard(c, player)); + final GameState game = player.getGame(); + final SpellAbility ab = CField.this.playerViewer.getController().getAbilityToPlay(game.getAbilitesOfCard(c, CField.this.playerViewer)); if ( null != ab) { FThreads.invokeInNewThread(new Runnable(){ @Override public void run(){ - player.playSpellAbility(c, ab); + CField.this.playerViewer.playSpellAbility(c, ab); }}); } } @@ -375,9 +376,10 @@ public class CField implements ICDoc { /** */ private void cardoverAction(MouseEvent e) { + boolean isShiftDown = (e.getModifiers() & Event.SHIFT_MASK) != 0; final Card c = CField.this.view.getTabletop().getHoveredCard(e); if (c != null) { - CMatchUI.SINGLETON_INSTANCE.setCard(c); + CMatchUI.SINGLETON_INSTANCE.setCard(c, isShiftDown); } } diff --git a/src/main/java/forge/gui/match/nonsingleton/VField.java b/src/main/java/forge/gui/match/nonsingleton/VField.java index 1efc4e95d9f..c414639ec33 100644 --- a/src/main/java/forge/gui/match/nonsingleton/VField.java +++ b/src/main/java/forge/gui/match/nonsingleton/VField.java @@ -36,6 +36,7 @@ import net.miginfocom.swing.MigLayout; import forge.card.cardfactory.CardFactoryUtil; import forge.card.mana.ManaPool; import forge.game.phase.PhaseType; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.framework.DragCell; @@ -111,7 +112,7 @@ public class VField implements IVDoc { * @param playerOnwer   {@link forge.game.player.Player} * @param id0   {@link forge.gui.framework.EDocID} */ - public VField(final EDocID id0, final Player playerOnwer, Player playerViewer) { + public VField(final EDocID id0, final Player playerOnwer, HumanPlayer playerViewer) { this.docID = id0; id0.setDoc(this); diff --git a/src/main/java/forge/item/PreconDeck.java b/src/main/java/forge/item/PreconDeck.java index f5966a3054d..c35f0b9c602 100644 --- a/src/main/java/forge/item/PreconDeck.java +++ b/src/main/java/forge/item/PreconDeck.java @@ -77,17 +77,14 @@ public class PreconDeck implements InventoryItemFromSet { final Map> sections = FileSection.parseSections(deckLines); this.deck = Deck.fromSections(sections); - String setProxy = "n/a"; + FileSection kv = FileSection.parse(sections.get("metadata"), "="); imageFilename = kv.get("Image"); description = kv.get("Description"); - if (Singletons.getModel().getEditions().get(kv.get("set").toUpperCase()) != null) { - setProxy = kv.get("set"); - } - - this.set = setProxy; + String deckEdition = kv.get("set"); + this.set = deckEdition == null || Singletons.getModel().getEditions().get(deckEdition.toUpperCase()) == null ? "n/a" : deckEdition; this.recommendedDeals = new SellRules(sections.get("shop")); } diff --git a/src/main/java/forge/properties/NewConstants.java b/src/main/java/forge/properties/NewConstants.java index 3a387fef040..c9bb7dd5fe8 100644 --- a/src/main/java/forge/properties/NewConstants.java +++ b/src/main/java/forge/properties/NewConstants.java @@ -75,6 +75,7 @@ public final class NewConstants { public static final String DECK_SEALED_DIR = DECK_BASE_DIR + "sealed/"; public static final String DECK_SCHEME_DIR = DECK_BASE_DIR + "scheme/"; public static final String DECK_PLANE_DIR = DECK_BASE_DIR + "planar/"; + public static final String DECK_COMMANDER_DIR = DECK_BASE_DIR + "commander/"; public static final String QUEST_SAVE_DIR = USER_QUEST_DIR + "saves/"; public static final String MAIN_PREFS_FILE = USER_PREFS_DIR + "forge.preferences"; public static final String QUEST_PREFS_FILE = USER_PREFS_DIR + "quest.preferences"; diff --git a/src/main/java/forge/util/Lang.java b/src/main/java/forge/util/Lang.java new file mode 100644 index 00000000000..843c56a7d81 --- /dev/null +++ b/src/main/java/forge/util/Lang.java @@ -0,0 +1,45 @@ +package forge.util; + +import java.util.List; + +import com.google.common.base.Function; + +/** + * TODO: Write javadoc for this type. + * + */ +public class Lang { + + /** + * TODO: Write javadoc for this method. + * @param position + * @return + */ + public static String getOrdinal(int position) { + String[] sufixes = new String[] { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" }; + switch (position % 100) { + case 11: + case 12: + case 13: + return position + "th"; + default: + return position + sufixes[position % 10]; + } + } + + public static String joinHomogenous(List objects) { return joinHomogenous(objects, null); } + public static String joinHomogenous(List objects, Function accessor) { + int remaining = objects.size(); + StringBuilder sb = new StringBuilder(); + for(T obj : objects) { + remaining--; + if( accessor != null ) + sb.append(accessor.apply(obj)); + else + sb.append(obj); + if( remaining > 1 ) sb.append(", "); + if( remaining == 1 ) sb.append(" and "); + } + return sb.toString(); + } +}